Update UI & Worker

This commit is contained in:
johnathan 2025-05-20 17:57:03 +08:00
parent 6662bba2b1
commit 3734c83d21
9 changed files with 116 additions and 81 deletions

View File

@ -6,7 +6,7 @@ const props = defineProps<{ model: AmazonDetailItem }>();
<template> <template>
<div class="detail-description"> <div class="detail-description">
<n-descriptions label-placement="left" bordered :column="4"> <n-descriptions label-placement="left" bordered :column="4" label-style="min-width: 100px">
<n-descriptions-item label="ASIN" :span="2"> <n-descriptions-item label="ASIN" :span="2">
{{ props.model.asin }} {{ props.model.asin }}
</n-descriptions-item> </n-descriptions-item>
@ -28,11 +28,19 @@ const props = defineProps<{ model: AmazonDetailItem }>();
<n-descriptions-item label="排名"> <n-descriptions-item label="排名">
{{ props.model.category2?.rank || '-' }} {{ props.model.category2?.rank || '-' }}
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="图片链接"> <n-descriptions-item label="图片链接" :span="4">
<div v-for="link in props.model.imageUrls"> <div v-for="link in props.model.imageUrls">
{{ link }} {{ link }}
</div> </div>
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="评论" :span="2">
<div v-for="review in props.model.topReviews" style="margin-bottom: 5px">
<h5 style="margin: 0">{{ review.username }}:</h5>
<div v-for="paragraph in review.content.split('\n')">
{{ paragraph }}
</div>
</div>
</n-descriptions-item>
</n-descriptions> </n-descriptions>
</div> </div>
</template> </template>

View File

@ -1,35 +0,0 @@
<script setup lang="ts">
const openOptionsPage = async () => {
await browser.runtime.openOptionsPage();
};
</script>
<template>
<div class="header-menu">
<n-button class="setting-button" round @click="openOptionsPage" size="small">
<template #icon>
<n-icon size="18" color="#0f0f0f">
<stash:search-results />
</n-icon>
</template>
<template #default> 数据 </template>
</n-button>
</div>
</template>
<style scoped lang="scss">
.header-menu {
width: 100%;
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
.setting-button {
margin-right: 20px;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,52 @@
<script setup lang="ts">
const openOptionsPage = async () => {
await browser.runtime.openOptionsPage();
};
</script>
<template>
<div class="header-title">
<div class="header-menu">
<n-button class="setting-button" round @click="openOptionsPage" size="small">
<template #icon>
<n-icon size="18" color="#0f0f0f">
<stash:search-results />
</n-icon>
</template>
<template #default> 数据 </template>
</n-button>
</div>
<n-space class="app-title">
<mdi-cat style="font-size: 60px; color: black" />
<h1><slot></slot></h1>
</n-space>
</div>
</template>
<style scoped lang="scss">
.header-title {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.header-menu {
width: 100%;
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
.setting-button {
margin-right: 20px;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
}
.app-title {
margin-top: 20px;
}
</style>

View File

@ -177,11 +177,12 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2 seconds. await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2 seconds.
//#endregion //#endregion
//#region Fetch Top Reviews //#region Fetch Top Reviews
// const reviews = await injector.getReviews(); const reviews = await injector.getTopReviews();
// reviews.length > 0 && reviews.length > 0 &&
// this.channel.emit('item-top-reviews-collected', { this.channel.emit('item-top-reviews-collected', {
// reviews: reviews.map((r) => ({ asin: params.asin, ...r })), asin: params.asin,
// }); topReviews: reviews.map((r) => ({ asin: params.asin, ...r })),
});
//#endregion //#endregion
} }

View File

@ -57,7 +57,7 @@ interface AmazonPageWorkerEvents {
/** /**
* The event is fired when top reviews collected * The event is fired when top reviews collected
*/ */
['item-top-reviews-collected']: { reviews: AmazonReview[] }; ['item-top-reviews-collected']: Pick<AmazonDetailItem, 'asin' | 'topReviews'>;
/** /**
* Error event that occurs when there is an issue with the Amazon page worker. * Error event that occurs when there is an issue with the Amazon page worker.

View File

@ -45,7 +45,7 @@ worker.channel.on('item-rating-collected', (ev) => {
time: new Date().toLocaleString(), time: new Date().toLocaleString(),
content: `评分: ${ev.rating};评价数:${ev.ratingCount}`, content: `评分: ${ev.rating};评价数:${ev.ratingCount}`,
}); });
createOrUpdateDetailItem(ev); updateDetailItems(ev);
}); });
worker.channel.on('item-category-rank-collected', (ev) => { worker.channel.on('item-category-rank-collected', (ev) => {
timelines.value.push({ timelines.value.push({
@ -57,7 +57,7 @@ worker.channel.on('item-category-rank-collected', (ev) => {
ev.category2 ? `#${ev.category2.rank} in ${ev.category2.name}` : '', ev.category2 ? `#${ev.category2.rank} in ${ev.category2.name}` : '',
].join('\n'), ].join('\n'),
}); });
createOrUpdateDetailItem(ev); updateDetailItems(ev);
}); });
worker.channel.on('item-images-collected', (ev) => { worker.channel.on('item-images-collected', (ev) => {
timelines.value.push({ timelines.value.push({
@ -66,7 +66,16 @@ worker.channel.on('item-images-collected', (ev) => {
time: new Date().toLocaleString(), time: new Date().toLocaleString(),
content: `图片数: ${ev.imageUrls!.length}`, content: `图片数: ${ev.imageUrls!.length}`,
}); });
createOrUpdateDetailItem(ev); updateDetailItems(ev);
});
worker.channel.on('item-top-reviews-collected', (ev) => {
timelines.value.push({
type: 'success',
title: `商品${ev.asin}精选评论`,
time: new Date().toLocaleString(),
content: `精选评论数: ${ev.topReviews!.length}`,
});
updateDetailItems(ev);
}); });
const handleImportAsin: UploadOnChange = ({ fileList }) => { const handleImportAsin: UploadOnChange = ({ fileList }) => {
@ -139,7 +148,7 @@ const handleInterrupt = () => {
message.info('已触发中断,正在等待当前任务完成。', { duration: 2000 }); message.info('已触发中断,正在等待当前任务完成。', { duration: 2000 });
}; };
const createOrUpdateDetailItem = (info: AmazonDetailItem) => { const updateDetailItems = (info: AmazonDetailItem) => {
const targetIndex = detailItems.value.findLastIndex((item) => info.asin === item.asin); const targetIndex = detailItems.value.findLastIndex((item) => info.asin === item.asin);
if (targetIndex > -1) { if (targetIndex > -1) {
const origin = detailItems.value[targetIndex]; const origin = detailItems.value[targetIndex];
@ -152,12 +161,8 @@ const createOrUpdateDetailItem = (info: AmazonDetailItem) => {
</script> </script>
<template> <template>
<div class="detail-page-worker"> <div class="detail-page-entry">
<header-menu /> <header-title>Detail Page</header-title>
<div class="title">
<mdi-cat style="color: black; font-size: 60px" />
<h1 style="font-size: 30px; color: black">Detail Page</h1>
</div>
<div class="interative-section"> <div class="interative-section">
<n-space> <n-space>
<n-upload @change="handleImportAsin" accept=".txt" :max="1"> <n-upload @change="handleImportAsin" accept=".txt" :max="1">
@ -210,24 +215,13 @@ const createOrUpdateDetailItem = (info: AmazonDetailItem) => {
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.detail-page-worker { .detail-page-entry {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
.title {
margin: 20px 0 30px 0;
font-size: 60px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
gap: 10px;
}
.interative-section { .interative-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -0,0 +1,18 @@
<script lang="ts" setup></script>
<template>
<div class="review-page-entry">
<header-title>Review Page</header-title>
</div>
</template>
<style lang="scss" scoped>
.review-page-entry {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 20px;
}
</style>

View File

@ -82,12 +82,8 @@ const handleInterrupt = () => {
</script> </script>
<template> <template>
<main class="search-page-worker"> <div class="search-page-entry">
<header-menu /> <header-title>Search Page</header-title>
<n-space class="app-title">
<mdi-cat style="font-size: 60px; color: black" />
<h1>Search Page</h1>
</n-space>
<div class="interactive-section"> <div class="interactive-section">
<n-dynamic-input <n-dynamic-input
:disabled="running" :disabled="running"
@ -121,11 +117,11 @@ const handleInterrupt = () => {
<n-alert title="Warning" type="warning"> 警告在插件运行期间请勿与浏览器交互 </n-alert> <n-alert title="Warning" type="warning"> 警告在插件运行期间请勿与浏览器交互 </n-alert>
</div> </div>
<progress-report class="progress-report" :timelines="timelines" /> <progress-report class="progress-report" :timelines="timelines" />
</main> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.search-page-worker { .search-page-entry {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -133,10 +129,6 @@ const handleInterrupt = () => {
align-items: center; align-items: center;
gap: 20px; gap: 20px;
.app-title {
margin-top: 20px;
}
.interactive-section { .interactive-section {
border-radius: 10px; border-radius: 10px;
width: 80%; width: 80%;

View File

@ -1,15 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import DetailPageWorker from './DetailPageWorker.vue'; import DetailPageEntry from './DetailPageEntry.vue';
import SearchPageWorker from './SearchPageWorker.vue'; import SearchPageEntry from './SearchPageEntry.vue';
import ReviewPageEntry from './ReviewPageEntry.vue';
const tabs = [ const tabs = [
{ {
name: '搜索页', name: '搜索页',
component: SearchPageWorker, component: SearchPageEntry,
}, },
{ {
name: '详情页', name: '详情页',
component: DetailPageWorker, component: DetailPageEntry,
},
{
name: '评论页',
component: ReviewPageEntry,
}, },
]; ];
const selectedTab = ref(tabs[0].name); const selectedTab = ref(tabs[0].name);