mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-01-27 17:33:17 +08:00
Add column filter
This commit is contained in:
parent
2a52150c4a
commit
d66c40b9e7
@ -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": {
|
||||
|
||||
@ -76,6 +76,17 @@ const emit = defineEmits<{
|
||||
</template>
|
||||
<slot name="filter" />
|
||||
</n-popover>
|
||||
<n-popover v-if="$slots.settings" trigger="hover" placement="bottom" :duration="500">
|
||||
<template #trigger>
|
||||
<n-button type="default" ghost :round="round" :size="size">
|
||||
<template #icon>
|
||||
<solar-settings-linear />
|
||||
</template>
|
||||
设置
|
||||
</n-button>
|
||||
</template>
|
||||
<slot name="settings" />
|
||||
</n-popover>
|
||||
</n-button-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -47,13 +47,11 @@ export async function exec<T, P extends Record<string, unknown>>(
|
||||
): Promise<T> {
|
||||
const { timeout = 30000 } = options;
|
||||
return new Promise<T>(async (resolve, reject) => {
|
||||
if (isFirefox) {
|
||||
while (true) {
|
||||
await new Promise<void>((r) => setTimeout(r, 200));
|
||||
const tab = await browser.tabs.get(tabId);
|
||||
if (tab.status === 'complete') {
|
||||
break;
|
||||
}
|
||||
while (true) {
|
||||
await new Promise<void>((r) => setTimeout(r, 200));
|
||||
const tab = await browser.tabs.get(tabId);
|
||||
if (tab.status === 'complete') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
setTimeout(() => reject('脚本运行超时'), timeout);
|
||||
|
||||
@ -6,6 +6,10 @@ export const detailAsinInput = useWebExtensionStorage<string>('detailAsinInputTe
|
||||
|
||||
export const reviewAsinInput = useWebExtensionStorage<string>('reviewAsinInputText', '');
|
||||
|
||||
export const itemColumnSettings = useWebExtensionStorage<
|
||||
Set<keyof Pick<AmazonItem, 'keywords' | 'page' | 'rank' | 'createTime' | 'timestamp'>>
|
||||
>('itemColumnSettings', new Set(['keywords', 'page', 'rank', 'createTime']));
|
||||
|
||||
export const searchItems = useWebExtensionStorage<AmazonSearchItem[]>('searchItems', []);
|
||||
|
||||
export const detailWorkerSettings = useWebExtensionStorage<{ aplus: boolean }>(
|
||||
|
||||
@ -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}`;
|
||||
}
|
||||
|
||||
@ -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<ProtocolMap['html-to-image']['return']> {
|
||||
const sender = await this.getMessageSender();
|
||||
return Promise.resolve<ProtocolMap['html-to-image']['return']>(
|
||||
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<T, P extends Record<string, unknown>>(
|
||||
|
||||
@ -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 <amazon-detail-description model={row} />;
|
||||
const columns = computed<TableColumn[]>(() => {
|
||||
return [
|
||||
{
|
||||
type: 'expand',
|
||||
expandable: (row) => row.hasDetail,
|
||||
renderExpand(row) {
|
||||
return <amazon-detail-description model={row} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<n-space>
|
||||
{[
|
||||
{
|
||||
text: '评论',
|
||||
disabled: !reviewItems.value.has(row.asin),
|
||||
onClick: () => {
|
||||
const asin = row.asin;
|
||||
modal.create({
|
||||
title: `${asin}评论`,
|
||||
preset: 'card',
|
||||
style: {
|
||||
width: '80vw',
|
||||
height: '85vh',
|
||||
},
|
||||
content: () => <amazon-review-preview asin={asin} />,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '链接',
|
||||
onClick: () => {
|
||||
browser.tabs.create({
|
||||
active: true,
|
||||
url: row.link,
|
||||
});
|
||||
},
|
||||
},
|
||||
].map(({ text, onClick, disabled }) => (
|
||||
<n-button type="primary" text size="small" disabled={disabled} onClick={onClick}>
|
||||
{text}
|
||||
</n-button>
|
||||
))}
|
||||
</n-space>
|
||||
);
|
||||
{
|
||||
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 (
|
||||
<n-space>
|
||||
{[
|
||||
{
|
||||
text: '评论',
|
||||
disabled: !reviewItems.value.has(row.asin),
|
||||
onClick: () => {
|
||||
const asin = row.asin;
|
||||
modal.create({
|
||||
title: `${asin}评论`,
|
||||
preset: 'card',
|
||||
style: {
|
||||
width: '80vw',
|
||||
height: '85vh',
|
||||
},
|
||||
content: () => <amazon-review-preview asin={asin} />,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '链接',
|
||||
onClick: () => {
|
||||
browser.tabs.create({
|
||||
active: true,
|
||||
url: row.link,
|
||||
});
|
||||
},
|
||||
},
|
||||
].map(({ text, onClick, disabled }) => (
|
||||
<n-button type="primary" text size="small" disabled={disabled} onClick={onClick}>
|
||||
{text}
|
||||
</n-button>
|
||||
))}
|
||||
</n-space>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const extraHeaders: Header<AmazonItem>[] = [
|
||||
{ prop: 'link', label: '商品链接' },
|
||||
@ -155,7 +167,7 @@ const reviewHeaders: Header<AmazonReview>[] = [
|
||||
];
|
||||
|
||||
const getItemHeaders = () => {
|
||||
return columns
|
||||
return columns.value
|
||||
.filter((col: Record<string, any>) => col.key !== 'actions')
|
||||
.reduce(
|
||||
(p, v: Record<string, any>) => {
|
||||
@ -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 () => {
|
||||
</n-form-item>
|
||||
<n-form-item label="日期(搜索页)">
|
||||
<n-date-picker
|
||||
type="daterange"
|
||||
type="datetimerange"
|
||||
clearable
|
||||
v-model:value="filter.searchDateRange"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="日期(详情页)">
|
||||
<n-date-picker
|
||||
type="daterange"
|
||||
type="datetimerange"
|
||||
clearable
|
||||
v-model:value="filter.detailDateRange"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="列筛选">
|
||||
<n-checkbox-group
|
||||
:value="Array.from(itemColumnSettings)"
|
||||
@update:value="(val) => (itemColumnSettings = new Set(val) as any)"
|
||||
>
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox value="keywords" label="关键词" />
|
||||
<n-checkbox value="page" label="页码" />
|
||||
<n-checkbox value="rank" label="排位" />
|
||||
<n-checkbox value="createTime" label="获取日期" />
|
||||
<n-checkbox value="timestamp" label="获取日期(详情)" />
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="filter-footer" @click="onFilterReset"><n-button>重置</n-button></div>
|
||||
</div>
|
||||
@ -445,7 +471,7 @@ const handleClearData = async () => {
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
max-width: 360px;
|
||||
max-width: 500px;
|
||||
|
||||
.filter-title {
|
||||
font-size: 18px;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export interface ErrorChannelContainer {
|
||||
emit: (event: 'error', error: { message: string }) => void;
|
||||
emit: (event: 'error', error: { message: string }) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 = <T extends (...params: any) => 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 = <T extends (...params: any) => any>(func: T) => {
|
||||
const { commitChangeIngerval = 1500 } = this.homedepotWorkerSettings;
|
||||
const { commitChangeIngerval = 10000 } = this.homedepotWorkerSettings;
|
||||
return (...params: Parameters<T>) =>
|
||||
startTask(async () => {
|
||||
const interval = setInterval(() => commitChange(), commitChangeIngerval);
|
||||
@ -249,7 +245,6 @@ class HomedepotWorkerFactory {
|
||||
}
|
||||
|
||||
const amazonfacotry = new AmazonPageWorkerFactory();
|
||||
|
||||
const homedepotfactory = new HomedepotWorkerFactory();
|
||||
|
||||
export function usePageWorker(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user