From 6645cd2b758a67039ae724e7b8cd72b1fe6a620f Mon Sep 17 00:00:00 2001 From: johnathan <952508490@qq.com> Date: Fri, 20 Jun 2025 17:26:24 +0800 Subject: [PATCH] Update --- package.json | 2 +- src/components/DetailDescription.vue | 40 +--- .../{AsinsInput.vue => IdsInput.vue} | 26 ++- src/components/ProgressReport.vue | 14 +- src/components/ResultTable.vue | 96 ++++++++ src/components/ReviewPreview.vue | 2 +- src/env.ts | 2 +- src/logic/page-worker/types.d.ts | 1 - src/logic/{storage.ts => storages/amazon.ts} | 2 +- src/logic/storages/global.ts | 3 + src/logic/storages/homedepot.ts | 22 ++ src/logic/web-injectors/amazon.ts | 19 +- src/options/Options.vue | 39 +++- src/options/views/AmazonResultTable.vue | 81 +------ src/options/views/HomedepotResultTable.vue | 220 ++++++++++++++++++ src/router/index.ts | 15 +- src/sidepanel/App.vue | 4 + .../views/AmazonEntries/DetailPageEntry.vue | 16 +- .../views/AmazonEntries/ReviewPageEntry.vue | 16 +- .../views/AmazonEntries/SearchPageEntry.vue | 16 +- src/sidepanel/views/AmazonSidepanel.vue | 2 +- src/sidepanel/views/HomedepotSidepanel.vue | 92 +++++++- src/styles/main.scss | 3 + 23 files changed, 543 insertions(+), 190 deletions(-) rename src/components/{AsinsInput.vue => IdsInput.vue} (77%) create mode 100644 src/components/ResultTable.vue rename src/logic/{storage.ts => storages/amazon.ts} (98%) create mode 100644 src/logic/storages/global.ts create mode 100644 src/logic/storages/homedepot.ts create mode 100644 src/options/views/HomedepotResultTable.vue diff --git a/package.json b/package.json index ae41214..e8db2f1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azon-seeker", "displayName": "Azon Seeker", - "version": "0.2.0", + "version": "0.3.0", "private": true, "description": "Starter modify by honestfox101", "scripts": { diff --git a/src/components/DetailDescription.vue b/src/components/DetailDescription.vue index 420c567..b8a5df6 100644 --- a/src/components/DetailDescription.vue +++ b/src/components/DetailDescription.vue @@ -1,26 +1,7 @@ @@ -50,25 +31,6 @@ const handleLoadMore = () => { - diff --git a/src/components/AsinsInput.vue b/src/components/IdsInput.vue similarity index 77% rename from src/components/AsinsInput.vue rename to src/components/IdsInput.vue index 919b6a4..2e08afc 100644 --- a/src/components/AsinsInput.vue +++ b/src/components/IdsInput.vue @@ -2,12 +2,18 @@ import { useFileDialog } from '@vueuse/core'; import type { FormItemRule } from 'naive-ui'; -withDefaults( +const props = withDefaults( defineProps<{ disabled?: boolean; + matchPattern?: RegExp; + placeholder?: string; + validateMessage?: string; }>(), { disabled: false, + matchPattern: () => /^[A-Z0-9]{10}((\n|\s|,|;)[A-Z0-9]{10})*\n?$/g, + placeholder: '输入ASINs', + validateMessage: '请输入格式正确的ASIN', }, ); @@ -19,20 +25,20 @@ const formItemRef = useTemplateRef('detail-form-item'); const formItemRule: FormItemRule = { required: true, trigger: ['submit', 'blur'], - message: '请输入格式正确的ASIN', + message: props.validateMessage, validator: () => { - return modelValue.value.match(/^[A-Z0-9]{10}((\n|\s|,|;)[A-Z0-9]{10})*\n?$/g) !== null; + return props.matchPattern.exec(modelValue.value) !== null; }, }; const fileDialog = useFileDialog({ accept: '.txt', multiple: false }); fileDialog.onChange((fileList) => { const file = fileList?.item(0); - file && handleImportAsin(file); + file && handleImportIds(file); fileDialog.reset(); }); -const handleImportAsin = (file: File) => { +const handleImportIds = (file: File) => { const reader = new FileReader(); reader.onload = (e) => { const content = e.target?.result; @@ -43,10 +49,10 @@ const handleImportAsin = (file: File) => { reader.readAsText(file, 'utf-8'); }; -const handleExportAsin = () => { +const handleExportIds = () => { const blob = new Blob([modelValue.value], { type: 'text/plain' }); const url = URL.createObjectURL(blob); - const filename = `asin-${new Date().toISOString()}.txt`; + const filename = `${new Date().toISOString()}.txt`; const link = document.createElement('a'); link.href = url; link.download = filename; @@ -75,7 +81,7 @@ defineExpose({ - + @@ -83,7 +89,7 @@ defineExpose({ 导入 - + @@ -95,7 +101,7 @@ defineExpose({ diff --git a/src/components/ProgressReport.vue b/src/components/ProgressReport.vue index a0295b1..05f4e02 100644 --- a/src/components/ProgressReport.vue +++ b/src/components/ProgressReport.vue @@ -1,11 +1,13 @@ diff --git a/src/components/ResultTable.vue b/src/components/ResultTable.vue new file mode 100644 index 0000000..bfd31d6 --- /dev/null +++ b/src/components/ResultTable.vue @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + 还没有数据哦 + + + + + + + + + + + + + diff --git a/src/components/ReviewPreview.vue b/src/components/ReviewPreview.vue index 6d205fc..0deb3ee 100644 --- a/src/components/ReviewPreview.vue +++ b/src/components/ReviewPreview.vue @@ -2,7 +2,7 @@ import { useElementSize } from '@vueuse/core'; import { exportToXLSX, Header, importFromXLSX } from '~/logic/data-io'; import type { AmazonReview } from '~/logic/page-worker/types'; -import { reviewItems } from '~/logic/storage'; +import { reviewItems } from '~/logic/storages/amazon'; const props = defineProps<{ asin: string }>(); diff --git a/src/env.ts b/src/env.ts index b9ad559..22128e7 100644 --- a/src/env.ts +++ b/src/env.ts @@ -13,4 +13,4 @@ export function isForbiddenUrl(url: string): boolean { export const isFirefox = navigator.userAgent.includes('Firefox'); -export const remoteHost = __DEV__ ? '127.0.0.1:8000' : '127.0.0.1:8000'; +export const remoteHost = __DEV__ ? '127.0.0.1:8000' : '47.251.4.191:8000'; diff --git a/src/logic/page-worker/types.d.ts b/src/logic/page-worker/types.d.ts index 6ec86ad..5045267 100644 --- a/src/logic/page-worker/types.d.ts +++ b/src/logic/page-worker/types.d.ts @@ -121,7 +121,6 @@ type HomedepotDetailItem = { title: string; price: string; rate?: string; - innerText: string; reviewCount?: number; mainImageUrl: string; modelInfo?: string; diff --git a/src/logic/storage.ts b/src/logic/storages/amazon.ts similarity index 98% rename from src/logic/storage.ts rename to src/logic/storages/amazon.ts index 2abefc5..428adbb 100644 --- a/src/logic/storage.ts +++ b/src/logic/storages/amazon.ts @@ -4,7 +4,7 @@ import type { AmazonItem, AmazonReview, AmazonSearchItem, -} from './page-worker/types'; +} from '~/logic/page-worker/types'; export const keywordsList = useWebExtensionStorage('keywordsList', ['']); diff --git a/src/logic/storages/global.ts b/src/logic/storages/global.ts new file mode 100644 index 0000000..892dbea --- /dev/null +++ b/src/logic/storages/global.ts @@ -0,0 +1,3 @@ +import { useWebExtensionStorage } from '~/composables/useWebExtensionStorage'; + +export const site = useWebExtensionStorage<'amazon' | 'homedepot'>('site', 'amazon'); diff --git a/src/logic/storages/homedepot.ts b/src/logic/storages/homedepot.ts new file mode 100644 index 0000000..34f1f82 --- /dev/null +++ b/src/logic/storages/homedepot.ts @@ -0,0 +1,22 @@ +import { useWebExtensionStorage } from '~/composables/useWebExtensionStorage'; +import { HomedepotDetailItem } from '../page-worker/types'; + +export const detailItems = useWebExtensionStorage>( + 'homedepot-details', + new Map(), + { + listenToStorageChanges: 'options', + }, +); + +export const allItems = computed({ + get() { + return Array.from(detailItems.value.values()); + }, + set(newValue) { + detailItems.value = newValue.reduce((m, c) => { + m.set(c.OSMID, c); + return m; + }, new Map()); + }, +}); diff --git a/src/logic/web-injectors/amazon.ts b/src/logic/web-injectors/amazon.ts index 01984bd..6f5d1a0 100644 --- a/src/logic/web-injectors/amazon.ts +++ b/src/logic/web-injectors/amazon.ts @@ -226,17 +226,22 @@ export class AmazonDetailPageInjector extends BaseInjector { await new Promise((resolve) => setTimeout(resolve, 1000)); } } - const script = document.evaluate( - `//script[starts-with(text(), "\nP.when(\'A\').register")]`, + let script = document.evaluate( + `//script[starts-with(text(), "\nP.when(\'A\').register") or contains(text(), "\nP.when('A').register")]`, document, null, XPathResult.STRING_TYPE, ).stringValue; - const urls = [ - ...script.matchAll( - /(?<="hiRes":")https:\/\/m.media-amazon.com\/images\/I\/[\w\d\.\-+]+(?=")/g, - ), - ].map((e) => e[0]); + const extractUrls = (pattern: RegExp) => + Array.from(script.matchAll(pattern)).map((e) => e[0]); + let urls = extractUrls( + /(?<="hiRes":")https:\/\/m.media-amazon.com\/images\/I\/[\w\d\.\-+]+(?=")/g, + ); + if (urls.length === 0) { + urls = extractUrls( + /(?<="large":")https:\/\/m.media-amazon.com\/images\/I\/[\w\d\.\-+]+(?=")/g, + ); + } return urls; }); } diff --git a/src/options/Options.vue b/src/options/Options.vue index 4577b25..0986c95 100644 --- a/src/options/Options.vue +++ b/src/options/Options.vue @@ -1,12 +1,37 @@ - - 采集结果 - + + + + + + + + + + + + + 采集结果 + + @@ -18,6 +43,8 @@ header { display: flex; flex-direction: row; justify-content: space-between; + align-items: center; + .header-title { cursor: default; } @@ -28,9 +55,7 @@ main { flex-direction: column; align-items: center; - .result-table { - height: 90vh; - width: 95vw; - } + height: 90vh; + width: 95vw; } diff --git a/src/options/views/AmazonResultTable.vue b/src/options/views/AmazonResultTable.vue index 3e84e10..b55f5f9 100644 --- a/src/options/views/AmazonResultTable.vue +++ b/src/options/views/AmazonResultTable.vue @@ -1,17 +1,15 @@ + + + + + + + Homedepot数据 + + + + + + + + + + + + 本地导出 + + + 不包含图片 + + + + + + + + 云端导出 + + + 包含图片 + + + + + + + {{ cloudExporter.progress.current }}/{{ cloudExporter.progress.total }} + + + 停止 + + + + + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index e758103..1b21a35 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,14 +1,21 @@ import { Plugin } from 'vue'; -import { createRouter, createMemoryHistory, RouteRecordRaw } from 'vue-router'; +import { + createRouter, + createWebHashHistory, + createMemoryHistory, + RouteRecordRaw, +} from 'vue-router'; import { useAppContext } from '~/composables/useAppContext'; +import { site } from '~/logic/storages/global'; const routeObj: Record = { options: [ - { path: '/', redirect: '/amazon' }, + { path: '/', redirect: `/${site.value}` }, { path: '/amazon', component: () => import('~/options/views/AmazonResultTable.vue') }, + { path: '/homedepot', component: () => import('~/options/views/HomedepotResultTable.vue') }, ], sidepanel: [ - { path: '/', redirect: '/amazon' }, + { path: '/', redirect: `/${site.value}` }, { path: '/amazon', component: () => import('~/sidepanel/views/AmazonSidepanel.vue') }, { path: '/homedepot', component: () => import('~/sidepanel/views/HomedepotSidepanel.vue') }, ], @@ -19,7 +26,7 @@ export const router: Plugin = { const { appContext: context } = useAppContext(); const routes = routeObj[context]; const router = createRouter({ - history: createMemoryHistory(), + history: context === 'sidepanel' ? createMemoryHistory() : createWebHashHistory(), routes, }); app.use(router); diff --git a/src/sidepanel/App.vue b/src/sidepanel/App.vue index 3aac03a..3885312 100644 --- a/src/sidepanel/App.vue +++ b/src/sidepanel/App.vue @@ -2,6 +2,7 @@ import type { GlobalThemeOverrides } from 'naive-ui'; import { useRouter } from 'vue-router'; import { useCurrentUrl } from '~/composables/useCurrentUrl'; +import { site } from '~/logic/storages/global'; const theme: GlobalThemeOverrides = { common: { @@ -21,11 +22,14 @@ watch(currentUrl, (newVal) => { switch (url.hostname) { case 'www.amazon.com': router.push('/amazon'); + site.value = 'amazon'; break; case 'www.homedepot.com': router.push('/homedepot'); + site.value = 'homedepot'; break; default: + router.push(`/${site.value}`); break; } } diff --git a/src/sidepanel/views/AmazonEntries/DetailPageEntry.vue b/src/sidepanel/views/AmazonEntries/DetailPageEntry.vue index dc11852..456413f 100644 --- a/src/sidepanel/views/AmazonEntries/DetailPageEntry.vue +++ b/src/sidepanel/views/AmazonEntries/DetailPageEntry.vue @@ -1,19 +1,13 @@ - Hello World! - - Test! - + Homedepot + + + + + + + 开始 + + + + + + 停止 + + + + 警告,在插件运行期间请勿与浏览器交互。 + + @@ -28,5 +79,30 @@ const handleStart = () => { display: flex; flex-direction: column; align-items: center; + width: 100vw; +} + +.interative-section { + display: flex; + flex-direction: column; + padding: 15px; + align-items: stretch; + justify-content: center; + width: 85%; + border-radius: 10px; + border: 1px #00000020 dashed; + margin: 0 0 10px 0; +} + +.running-tip-section { + margin: 10px 0 0 0; + height: 100px; + border-radius: 10px; + cursor: wait; +} + +.progress-report { + margin-top: 10px; + width: 95%; } diff --git a/src/styles/main.scss b/src/styles/main.scss index 0ebb5d6..1e079cf 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -3,6 +3,9 @@ body, #app { margin: 0; padding: 0; + display: flex; + flex-direction: column; + align-items: center; } .btn {