diff --git a/package.json b/package.json index 2410fe8..7267f85 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azon-seeker", "displayName": "Azon Seeker", - "version": "0.3.0", + "version": "0.4.1", "private": true, "description": "Starter modify by honestfox101", "scripts": { diff --git a/src/components/ControlStrip.vue b/src/components/ControlStrip.vue index ce4cdf5..d630cc6 100644 --- a/src/components/ControlStrip.vue +++ b/src/components/ControlStrip.vue @@ -76,6 +76,17 @@ const emit = defineEmits<{ + + + + diff --git a/src/components/IdsInput.vue b/src/components/IdsInput.vue index 754d758..71754bd 100644 --- a/src/components/IdsInput.vue +++ b/src/components/IdsInput.vue @@ -27,7 +27,7 @@ const formItemRule: FormItemRule = { trigger: ['submit'], message: props.validateMessage, validator: () => { - return props.matchPattern.exec(modelValue.value) !== null; + return props.matchPattern && props.matchPattern.exec(modelValue.value) !== null; }, }; diff --git a/src/env.ts b/src/env.ts index ea86683..3be77b2 100644 --- a/src/env.ts +++ b/src/env.ts @@ -14,4 +14,5 @@ export function isForbiddenUrl(url: string): boolean { export const isFirefox = navigator.userAgent.includes('Firefox'); -export const remoteHost = __DEV__ ? '127.0.0.1:8000' : '47.251.4.191:8000'; +// export const remoteHost = __DEV__ ? '127.0.0.1:8000' : '47.251.4.191:8000'; +export const remoteHost = '47.251.4.191:8000'; diff --git a/src/logic/execute-script.ts b/src/logic/execute-script.ts index e195552..717b094 100644 --- a/src/logic/execute-script.ts +++ b/src/logic/execute-script.ts @@ -47,13 +47,11 @@ export async function exec>( ): Promise { const { timeout = 30000 } = options; return new Promise(async (resolve, reject) => { - if (isFirefox) { - while (true) { - await new Promise((r) => setTimeout(r, 200)); - const tab = await browser.tabs.get(tabId); - if (tab.status === 'complete') { - break; - } + while (true) { + await new Promise((r) => setTimeout(r, 200)); + const tab = await browser.tabs.get(tabId); + if (tab.status === 'complete') { + break; } } setTimeout(() => reject('脚本运行超时'), timeout); diff --git a/src/logic/storages/amazon.ts b/src/logic/storages/amazon.ts index 5f91928..68b0b8c 100644 --- a/src/logic/storages/amazon.ts +++ b/src/logic/storages/amazon.ts @@ -6,6 +6,10 @@ export const detailAsinInput = useWebExtensionStorage('detailAsinInputTe export const reviewAsinInput = useWebExtensionStorage('reviewAsinInputText', ''); +export const itemColumnSettings = useWebExtensionStorage< + Set> +>('itemColumnSettings', new Set(['keywords', 'page', 'rank', 'createTime'])); + export const searchItems = useWebExtensionStorage('searchItems', []); export const detailWorkerSettings = useWebExtensionStorage<{ aplus: boolean }>( diff --git a/src/logic/upload.ts b/src/logic/upload.ts index 07fd9f2..69c158b 100644 --- a/src/logic/upload.ts +++ b/src/logic/upload.ts @@ -23,12 +23,13 @@ export async function uploadImage( formData.append('file', blob, filename); const url = `http://${remoteHost}/upload/image/${encodeURIComponent(filename)}`; - return fetch(url, { + const resp = (await fetch(url, { method: 'POST', body: formData, - }) - .then((response) => response.json()) - .then((data) => { - return data.file ? `http://${remoteHost}${data.file}` : undefined; - }); + }).catch((err) => undefined)) as Response | undefined; + if (!resp) { + return undefined; + } + const data = await resp.json(); + return `http://${remoteHost}${data.file}`; } diff --git a/src/logic/web-injectors/base.ts b/src/logic/web-injectors/base.ts index 02443e2..f61364e 100644 --- a/src/logic/web-injectors/base.ts +++ b/src/logic/web-injectors/base.ts @@ -26,14 +26,14 @@ export class BaseInjector { } } - protected async screenshot(params: ProtocolMap['html-to-image']['data']) { + protected async screenshot( + params: ProtocolMap['html-to-image']['data'], + ): Promise { const sender = await this.getMessageSender(); - return Promise.resolve( - sender.sendMessage('html-to-image', params, { - context: 'content-script', - tabId: this._tab.id!, - }), - ); + return sender!.sendMessage('html-to-image', params, { + context: 'content-script', + tabId: this._tab.id!, + }); } protected run>( diff --git a/src/options/views/AmazonResultTable.vue b/src/options/views/AmazonResultTable.vue index 40fb958..90caad9 100644 --- a/src/options/views/AmazonResultTable.vue +++ b/src/options/views/AmazonResultTable.vue @@ -3,7 +3,7 @@ import { NButton, NSpace } from 'naive-ui'; import type { TableColumn } from '~/components/ResultTable.vue'; import { useCloudExporter } from '~/composables/useCloudExporter'; import { castRecordsByHeaders, createWorkbook, Header, importFromXLSX } from '~/logic/excel'; -import { allItems, reviewItems } from '~/logic/storages/amazon'; +import { allItems, itemColumnSettings, reviewItems } from '~/logic/storages/amazon'; const message = useMessage(); const modal = useModal(); @@ -21,96 +21,108 @@ const onFilterReset = () => { filter.value = {}; }; -const columns: TableColumn[] = [ - { - type: 'expand', - expandable: (row) => row.hasDetail, - renderExpand(row) { - return ; +const columns = computed(() => { + return [ + { + type: 'expand', + expandable: (row) => row.hasDetail, + renderExpand(row) { + return ; + }, }, - }, - { - title: '关键词', - key: 'keywords', - minWidth: 120, - }, - { - title: '页码', - key: 'page', - minWidth: 60, - }, - { - title: '排位', - key: 'rank', - minWidth: 60, - }, - { - title: 'ASIN', - key: 'asin', - minWidth: 130, - }, - { - title: '标题', - key: 'title', - }, - { - title: '价格', - key: 'price', - minWidth: 100, - }, - { - title: '封面图', - key: 'imageSrc', - hidden: true, - }, - { - title: '获取日期', - key: 'createTime', - minWidth: 160, - }, - { - title: '查看', - key: 'actions', - minWidth: 100, - render(row) { - return ( - - {[ - { - text: '评论', - disabled: !reviewItems.value.has(row.asin), - onClick: () => { - const asin = row.asin; - modal.create({ - title: `${asin}评论`, - preset: 'card', - style: { - width: '80vw', - height: '85vh', - }, - content: () => , - }); - }, - }, - { - text: '链接', - onClick: () => { - browser.tabs.create({ - active: true, - url: row.link, - }); - }, - }, - ].map(({ text, onClick, disabled }) => ( - - {text} - - ))} - - ); + { + title: '关键词', + key: 'keywords', + minWidth: 120, + hidden: !itemColumnSettings.value.has('keywords'), }, - }, -]; + { + title: '页码', + key: 'page', + minWidth: 60, + hidden: !itemColumnSettings.value.has('page'), + }, + { + title: '排位', + key: 'rank', + minWidth: 60, + hidden: !itemColumnSettings.value.has('rank'), + }, + { + title: 'ASIN', + key: 'asin', + minWidth: 130, + }, + { + title: '标题', + key: 'title', + }, + { + title: '价格', + key: 'price', + minWidth: 100, + }, + { + title: '封面图', + key: 'imageSrc', + hidden: true, + }, + { + title: '获取日期', + key: 'createTime', + minWidth: 160, + hidden: !itemColumnSettings.value.has('createTime'), + }, + { + title: '获取日期(详情页)', + key: 'timestamp', + minWidth: 160, + hidden: !itemColumnSettings.value.has('timestamp'), + }, + { + title: '查看', + key: 'actions', + minWidth: 100, + render(row) { + return ( + + {[ + { + text: '评论', + disabled: !reviewItems.value.has(row.asin), + onClick: () => { + const asin = row.asin; + modal.create({ + title: `${asin}评论`, + preset: 'card', + style: { + width: '80vw', + height: '85vh', + }, + content: () => , + }); + }, + }, + { + text: '链接', + onClick: () => { + browser.tabs.create({ + active: true, + url: row.link, + }); + }, + }, + ].map(({ text, onClick, disabled }) => ( + + {text} + + ))} + + ); + }, + }, + ]; +}); const extraHeaders: Header[] = [ { prop: 'link', label: '商品链接' }, @@ -155,7 +167,7 @@ const reviewHeaders: Header[] = [ ]; const getItemHeaders = () => { - return columns + return columns.value .filter((col: Record) => col.key !== 'actions') .reduce( (p, v: Record) => { @@ -240,7 +252,7 @@ const handleCloudExport = async () => { const mappedData1 = await castRecordsByHeaders(items, itemHeaders); const mappedData2 = await castRecordsByHeaders(reviews, reviewHeaders); const fragments = [ - { data: mappedData1, imageColumn: ['商品图片链接', 'A+截图'], name: 'items' }, + { data: mappedData1, imageColumn: ['A+截图', '商品图片链接'], name: 'items' }, { data: mappedData2, imageColumn: '图片链接', name: 'reviews' }, ]; const filename = await cloudExporter.doExport(fragments); @@ -369,18 +381,32 @@ const handleClearData = async () => { + + + + + + + + + + + @@ -445,7 +471,7 @@ const handleClearData = async () => { } .filter-section { - max-width: 360px; + max-width: 500px; .filter-title { font-size: 18px; diff --git a/src/page-worker/error-handler.ts b/src/page-worker/error-handler.ts index 2b07691..d0453c2 100644 --- a/src/page-worker/error-handler.ts +++ b/src/page-worker/error-handler.ts @@ -1,5 +1,5 @@ export interface ErrorChannelContainer { - emit: (event: 'error', error: { message: string }) => void; + emit: (event: 'error', error: { message: string }) => Promise; } /** @@ -16,7 +16,7 @@ export function withErrorHandling( try { return await originalMethod.call(this, ...args); // 调用原有方法 } catch (error) { - this.emit('error', { message: `发生未知错误:${error}` }); + await this.emit('error', { message: `发生未知错误:${error}` }); throw error; } }; diff --git a/src/page-worker/index.ts b/src/page-worker/index.ts index 670e4dd..9263ca6 100644 --- a/src/page-worker/index.ts +++ b/src/page-worker/index.ts @@ -47,10 +47,9 @@ class AmazonPageWorkerFactory { worker.on('item-top-reviews-collected', (ev) => { updateDetailCache(ev); }), - worker.on('item-aplus-screenshot-collect', (ev) => { - uploadImage(ev.base64data, `${ev.asin}.png`).then((url) => { - url && updateDetailCache({ asin: ev.asin, aplus: url }); - }); + worker.on('item-aplus-screenshot-collect', async (ev) => { + const url = await uploadImage(ev.base64data, `${ev.asin}.png`); + url && updateDetailCache({ asin: ev.asin, aplus: url }); }), worker.on('item-review-collected', (ev) => { updateReviews(ev); @@ -92,14 +91,12 @@ class AmazonPageWorkerFactory { const { searchItems, detailItems, reviewItems } = this.amazonWorkerSettings; if (typeof searchItems !== 'undefined') { searchItems.value = searchItems.value.concat(searchCache); - searchCache.splice(0, searchCache.length); } if (typeof detailItems !== 'undefined') { for (const [k, v] of detailCache.entries()) { detailItems.value.delete(k); // Trigger update detailItems.value.set(k, v); } - detailCache.clear(); } if (typeof reviewItems !== 'undefined') { for (const [asin, reviews] of reviewCache.entries()) { @@ -116,12 +113,11 @@ class AmazonPageWorkerFactory { reviewItems.value.set(asin, reviews); } } - reviewCache.clear(); } }; const taskWrapper = any>(func: T) => { - const { commitChangeIngerval = 1500 } = this.amazonWorkerSettings; + const { commitChangeIngerval = 10000 } = this.amazonWorkerSettings; searchCache.splice(0, searchCache.length); detailCache.clear(); reviewCache.clear(); @@ -215,7 +211,7 @@ class HomedepotWorkerFactory { }; const taskWrapper = any>(func: T) => { - const { commitChangeIngerval = 1500 } = this.homedepotWorkerSettings; + const { commitChangeIngerval = 10000 } = this.homedepotWorkerSettings; return (...params: Parameters) => startTask(async () => { const interval = setInterval(() => commitChange(), commitChangeIngerval); @@ -249,7 +245,6 @@ class HomedepotWorkerFactory { } const amazonfacotry = new AmazonPageWorkerFactory(); - const homedepotfactory = new HomedepotWorkerFactory(); export function usePageWorker(