From d2301db2926ea62dc9ebfcc54768f5b14b31ab1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Mon, 7 Sep 2026 11:06:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend-vue):=20=E4=BA=9A=E9=A9=AC?= =?UTF-8?q?=E9=80=8A=E8=BF=90=E8=90=A5=E5=B7=A5=E5=85=B7=E5=8F=B0=E4=B8=8E?= =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD=E9=A1=B5=E6=8E=A5=E5=85=A5=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=96=B0=E7=89=88=E5=B7=A5=E5=85=B7=E5=8F=B0(amazon-c?= =?UTF-8?q?onsole+tool-catalog)=E3=80=81=E5=93=81=E7=89=8C=E6=A3=80?= =?UTF-8?q?=E6=B5=8B/ASIN=E5=8F=98=E4=BD=93=E9=87=87=E9=9B=86/=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2ASIN=20=E4=B8=89=E4=B8=AA=E5=B7=A5=E5=85=B7=E9=A1=B5?= =?UTF-8?q?=20Vue=20=E5=8C=96=E5=B9=B6=E5=AF=B9=E6=8E=A5=E6=97=A2=E6=9C=89?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=B8=8E=E6=A1=8C=E9=9D=A2=E6=A1=A5=E6=8E=A5?= =?UTF-8?q?=EF=BC=9B=E6=A1=8C=E9=9D=A2=E9=A6=96=E9=A1=B5/=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=A1=B5=20Vue=20=E5=8C=96(=E6=B2=BF=E7=94=A8?= =?UTF-8?q?=E6=97=A7=E8=A7=86=E8=A7=89)=EF=BC=9B=E8=A1=A5=E5=85=85=20vc=5F?= =?UTF-8?q?*/get=5Fdevice=5Fid/save=5Ffile=5Ffrom=5Furl=20=E6=A1=A5?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E3=80=81dev=20=E4=BB=A3=E7=90=86=EF=BC=9B?= =?UTF-8?q?=E5=93=81=E7=89=8C=E9=BB=98=E8=AE=A4=E5=8B=BE=E9=80=89=E6=8E=92?= =?UTF-8?q?=E5=90=8D+FBM=EF=BC=9B=E4=BF=AE=E6=AD=A3=20query-asin=20?= =?UTF-8?q?=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 含未提交新版改造累积:Brand*Tab 套入工具台壳(AmazonToolPageShell/AmazonTopBar)与目录/权限联动。 --- frontend-vue/amazon-console.html | 12 + frontend-vue/brand.html | 12 + frontend-vue/home.html | 12 + frontend-vue/login.html | 12 + frontend-vue/query-asin.html | 2 +- frontend-vue/src/amazon-console-main.ts | 7 + frontend-vue/src/brand-main.ts | 12 + frontend-vue/src/home-main.ts | 8 + frontend-vue/src/login-main.ts | 8 + .../src/pages/amazon/AmazonConsolePage.vue | 643 ++++++++++++++++ .../amazon/components/AmazonFooterBar.vue | 85 +++ .../amazon/components/AmazonToolPageShell.vue | 112 +++ .../pages/amazon/components/AmazonTopBar.vue | 249 +++++++ .../src/pages/amazon/components/ToolIcon.vue | 59 ++ frontend-vue/src/pages/amazon/tool-catalog.ts | 392 ++++++++++ .../components/BrandAppearancePatentTab.vue | 57 +- .../pages/brand/components/BrandBrandTab.vue | 494 +++++++++++++ .../brand/components/BrandCollectDataTab.vue | 77 +- .../brand/components/BrandConvertTab.vue | 97 +-- .../pages/brand/components/BrandDedupeTab.vue | 95 +-- .../brand/components/BrandDeleteBrandTab.vue | 91 +-- .../brand/components/BrandPatrolDeleteTab.vue | 69 +- .../brand/components/BrandPriceTrackTab.vue | 135 ++-- .../brand/components/BrandProductRiskTab.vue | 109 +-- .../brand/components/BrandPublishTab.vue | 57 +- .../brand/components/BrandQueryAsinTab.vue | 63 +- .../components/BrandShopDataCrawlTab.vue | 51 +- .../brand/components/BrandShopMatchTab.vue | 85 +-- .../brand/components/BrandSimilarAsinTab.vue | 79 +- .../pages/brand/components/BrandSplitTab.vue | 105 +-- .../pages/brand/components/BrandTopBar.vue | 5 +- .../brand/components/BrandVariantTab.vue | 688 ++++++++++++++++++ .../brand/components/BrandWithdrawTab.vue | 65 +- .../src/pages/home/DesktopHomePage.vue | 471 ++++++++++++ .../src/pages/login/DesktopLoginPage.vue | 366 ++++++++++ .../src/shared/api/types/modules/brand.ts | 1 + frontend-vue/src/shared/bridges/pywebview.ts | 84 +++ frontend-vue/src/shared/page-prefix.ts | 20 + frontend-vue/src/styles/main.css | 8 +- frontend-vue/src/variant-collection-main.ts | 12 + .../tests/amazon-tool-catalog.test.ts | 90 +++ frontend-vue/variant-collection.html | 12 + frontend-vue/vite.config.ts | 16 + 43 files changed, 4513 insertions(+), 614 deletions(-) create mode 100644 frontend-vue/amazon-console.html create mode 100644 frontend-vue/brand.html create mode 100644 frontend-vue/home.html create mode 100644 frontend-vue/login.html create mode 100644 frontend-vue/src/amazon-console-main.ts create mode 100644 frontend-vue/src/brand-main.ts create mode 100644 frontend-vue/src/home-main.ts create mode 100644 frontend-vue/src/login-main.ts create mode 100644 frontend-vue/src/pages/amazon/AmazonConsolePage.vue create mode 100644 frontend-vue/src/pages/amazon/components/AmazonFooterBar.vue create mode 100644 frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue create mode 100644 frontend-vue/src/pages/amazon/components/AmazonTopBar.vue create mode 100644 frontend-vue/src/pages/amazon/components/ToolIcon.vue create mode 100644 frontend-vue/src/pages/amazon/tool-catalog.ts create mode 100644 frontend-vue/src/pages/brand/components/BrandBrandTab.vue create mode 100644 frontend-vue/src/pages/brand/components/BrandVariantTab.vue create mode 100644 frontend-vue/src/pages/home/DesktopHomePage.vue create mode 100644 frontend-vue/src/pages/login/DesktopLoginPage.vue create mode 100644 frontend-vue/src/shared/page-prefix.ts create mode 100644 frontend-vue/src/variant-collection-main.ts create mode 100644 frontend-vue/tests/amazon-tool-catalog.test.ts create mode 100644 frontend-vue/variant-collection.html diff --git a/frontend-vue/amazon-console.html b/frontend-vue/amazon-console.html new file mode 100644 index 00000000..985e8557 --- /dev/null +++ b/frontend-vue/amazon-console.html @@ -0,0 +1,12 @@ + + + + + + 亚马逊 - 数富AI + + +
+ + + diff --git a/frontend-vue/brand.html b/frontend-vue/brand.html new file mode 100644 index 00000000..6d386217 --- /dev/null +++ b/frontend-vue/brand.html @@ -0,0 +1,12 @@ + + + + + + 品牌检测 - 数富AI + + +
+ + + diff --git a/frontend-vue/home.html b/frontend-vue/home.html new file mode 100644 index 00000000..38a069a6 --- /dev/null +++ b/frontend-vue/home.html @@ -0,0 +1,12 @@ + + + + + + 数富AI + + +
+ + + diff --git a/frontend-vue/login.html b/frontend-vue/login.html new file mode 100644 index 00000000..5eb08c93 --- /dev/null +++ b/frontend-vue/login.html @@ -0,0 +1,12 @@ + + + + + + 登录 - 数富AI + + +
+ + + diff --git a/frontend-vue/query-asin.html b/frontend-vue/query-asin.html index ea149616..9d3c3591 100644 --- a/frontend-vue/query-asin.html +++ b/frontend-vue/query-asin.html @@ -3,7 +3,7 @@ - 巡店删除 - 数富AI + 查询ASIN - 数富AI
diff --git a/frontend-vue/src/amazon-console-main.ts b/frontend-vue/src/amazon-console-main.ts new file mode 100644 index 00000000..5885b4fd --- /dev/null +++ b/frontend-vue/src/amazon-console-main.ts @@ -0,0 +1,7 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import '@/styles/main.css' +import AmazonConsolePage from '@/pages/amazon/AmazonConsolePage.vue' + +createApp(AmazonConsolePage).use(ElementPlus).mount('#app') diff --git a/frontend-vue/src/brand-main.ts b/frontend-vue/src/brand-main.ts new file mode 100644 index 00000000..80ec854a --- /dev/null +++ b/frontend-vue/src/brand-main.ts @@ -0,0 +1,12 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' +import 'element-plus/dist/index.css' +import '@/styles/main.css' +import BrandBrandTab from '@/pages/brand/components/BrandBrandTab.vue' + +dayjs.locale('zh-cn') + +createApp(BrandBrandTab).use(ElementPlus, { locale: zhCn }).mount('#app') diff --git a/frontend-vue/src/home-main.ts b/frontend-vue/src/home-main.ts new file mode 100644 index 00000000..a46f9476 --- /dev/null +++ b/frontend-vue/src/home-main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import 'element-plus/dist/index.css' +import '@/styles/main.css' +import DesktopHomePage from '@/pages/home/DesktopHomePage.vue' + +createApp(DesktopHomePage).use(ElementPlus, { locale: zhCn }).mount('#app') diff --git a/frontend-vue/src/login-main.ts b/frontend-vue/src/login-main.ts new file mode 100644 index 00000000..9ead6b18 --- /dev/null +++ b/frontend-vue/src/login-main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import 'element-plus/dist/index.css' +import '@/styles/main.css' +import DesktopLoginPage from '@/pages/login/DesktopLoginPage.vue' + +createApp(DesktopLoginPage).use(ElementPlus, { locale: zhCn }).mount('#app') diff --git a/frontend-vue/src/pages/amazon/AmazonConsolePage.vue b/frontend-vue/src/pages/amazon/AmazonConsolePage.vue new file mode 100644 index 00000000..267fcc15 --- /dev/null +++ b/frontend-vue/src/pages/amazon/AmazonConsolePage.vue @@ -0,0 +1,643 @@ + + + + + diff --git a/frontend-vue/src/pages/amazon/components/AmazonFooterBar.vue b/frontend-vue/src/pages/amazon/components/AmazonFooterBar.vue new file mode 100644 index 00000000..acce06ca --- /dev/null +++ b/frontend-vue/src/pages/amazon/components/AmazonFooterBar.vue @@ -0,0 +1,85 @@ + + + diff --git a/frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue b/frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue new file mode 100644 index 00000000..826a6ef7 --- /dev/null +++ b/frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/frontend-vue/src/pages/amazon/components/AmazonTopBar.vue b/frontend-vue/src/pages/amazon/components/AmazonTopBar.vue new file mode 100644 index 00000000..66ee3186 --- /dev/null +++ b/frontend-vue/src/pages/amazon/components/AmazonTopBar.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/frontend-vue/src/pages/amazon/components/ToolIcon.vue b/frontend-vue/src/pages/amazon/components/ToolIcon.vue new file mode 100644 index 00000000..e7d3ca05 --- /dev/null +++ b/frontend-vue/src/pages/amazon/components/ToolIcon.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend-vue/src/pages/amazon/tool-catalog.ts b/frontend-vue/src/pages/amazon/tool-catalog.ts new file mode 100644 index 00000000..cf6d76bf --- /dev/null +++ b/frontend-vue/src/pages/amazon/tool-catalog.ts @@ -0,0 +1,392 @@ +/** + * 亚马逊运营工具台 · 工具目录 + * + * 来源:reference/数富AI工具台_含源代码/数富AI工具台_主程序.py 的 CATS 数据, + * 页面链接按现有 BrandTopBar 导航映射到既有功能页(表单 + 右侧任务全部保留现有)。 + */ + +export type ToolInfo = { + /** 工具 id(与参考桌面板一致) */ + id: string + /** 工具名称 */ + name: string + /** 卡片一句话描述 */ + desc: string + /** 角标文字(如 流程·1 / 重点 / 核心),无则不显示 */ + tag?: string + /** 是否核心工具(角标走核心样式) */ + core?: boolean + /** 现有功能页面地址;未提供表示暂未开通 */ + href?: string + /** 菜单权限键(小写),与 BrandTopBar 导航一致 */ + permissionKeys: string[] +} + +export type WorkflowStep = { + name: string + desc: string + /** 指向的工具 id,用于点击跳转 */ + toolId?: string +} + +export type ToolGroup = { + key: 'front' | 'ops' | 'logi' + /** 菜单权限组键(与 BrandTopBar 一致) */ + columnKey: string + name: string + desc: string + /** 分类主色 */ + color: string + /** 分类弱色(角标底、编号圆底) */ + weak: string + tools: ToolInfo[] + /** 标准工作流时间线(后勤无) */ + workflowTitle?: string + workflowBadge?: string + workflowHint?: string + workflow?: WorkflowStep[] +} + +/** 现有功能页地址(全部为 new_web_source 的 Vue 构建页) */ +const VM = '/new_web_source' + +export const TOOL_GROUPS: ReadonlyArray = [ + { + key: 'front', + columnKey: 'brand_front_tools', + name: '前端工具', + desc: '采集 · 清洗 · 查品,产出安全可上架的数据', + color: '#2DD4BF', + weak: '#0F2E28', + workflowTitle: '前端工具 · 标准工作流', + workflowBadge: '产出完整安全数据', + workflowHint: '8步工作流 · 从采集到上架,一步不缺', + workflow: [ + { name: '采集数据', desc: '站点批量采集', toolId: 'collect' }, + { name: 'ASIN变体采集', desc: '按ASIN拉变体', toolId: 'variant' }, + { name: '数据去重', desc: '谁先采集归谁', toolId: 'dedupe' }, + { name: '货源查询·第一遍', desc: '粗略不扣费', toolId: 'source' }, + { name: '品牌检测', desc: '精确+嵌入', toolId: 'brand' }, + { name: '外观专利检测', desc: '规避专利风险', toolId: 'patent' }, + { name: '货源查询·第二遍', desc: '精细扣费', toolId: 'source' }, + { name: '上传后台', desc: '可上架数据', toolId: 'convert' }, + ], + tools: [ + { + id: 'collect', + name: '采集数据', + desc: '批量采集站点商品数据,按照自己要求数据', + tag: '流程·1', + href: `${VM}/collect-data.html`, + permissionKeys: ['collect-data'], + }, + { + id: 'variant', + name: 'ASIN变体采集', + desc: '支持粘贴和文档,按照采集国家选择导出', + tag: '流程·2', + href: `${VM}/variant-collection.html`, + permissionKeys: ['variant-collection'], + }, + { + id: 'dedupe', + name: '数据去重', + desc: '全站唯一 ASIN 归属:谁先采集归谁,自动过滤撞款', + tag: '重点', + core: true, + href: `${VM}/dedupe.html`, + permissionKeys: ['dedupe'], + }, + { + id: 'source', + name: '货源查询', + desc: '关闭图片检查和类目检测不扣费,打开需要算力费用', + tag: '流程·4', + href: `${VM}/similar-asin.html`, + permissionKeys: ['similar-asin'], + }, + { + id: 'brand', + name: '品牌检测', + desc: '核查上架数据品牌风险,精确 / 嵌入两种策略', + tag: '流程·5', + href: `${VM}/brand.html`, + permissionKeys: ['brand'], + }, + { + id: 'patent', + name: '外观专利检测', + desc: '检测商品外观专利,版权,标题,图片。详细页风险,规避下架', + tag: '流程·6', + href: `${VM}/appearance-patent.html`, + permissionKeys: ['appearance-patent'], + }, + { + id: 'split', + name: '数据拆分', + desc: '大文件按规则拆分,便于分批处理与上传', + href: `${VM}/split.html`, + permissionKeys: ['split'], + }, + { + id: 'convert', + name: '格式转换', + desc: '数据格式一键转换,适配各工具导入', + href: `${VM}/convert.html`, + permissionKeys: ['convert'], + }, + ], + }, + { + key: 'ops', + columnKey: 'brand_operation_tools', + name: '运营工具', + desc: '上架 · 跟价 · 查品 · 巡店,日常店铺运营与维护', + color: '#60A5FA', + weak: '#12243D', + workflowTitle: '运营工具 · 标准工作流', + workflowBadge: '日常运营与维护', + workflowHint: '10步工作流 · 从上架到复盘,环环相扣', + workflow: [ + { name: '上架', desc: '安全数据批量上架', toolId: 'list' }, + { name: '指定删除ASIN', desc: '批量清理无效ASIN', toolId: 'delasin' }, + { name: '商品风险解决', desc: '4类风险一键处理', toolId: 'risk' }, + { name: '定时匹配', desc: '定时价格匹配', toolId: 'time' }, + { name: '跟价', desc: '跟价规则配置', toolId: 'follow' }, + { name: '巡店删除', desc: '巡检店铺状态', toolId: 'patrol' }, + { name: '查询ASIN', desc: '排查ASIN在售状态', toolId: 'qasin' }, + { name: '店铺数据抓取', desc: '抓取运营数据', toolId: 'scrape' }, + { name: '取款', desc: '留足平台扣款费用', toolId: 'cash' }, + { name: '店铺状态查询', desc: '查询店铺账号状态', toolId: 'shop' }, + ], + tools: [ + { + id: 'list', + name: '上架', + desc: '把安全数据批量上架到指定店铺', + href: `${VM}/publish.html`, + permissionKeys: ['publish'], + }, + { + id: 'delasin', + name: '指定删除ASIN', + desc: '批量删除需清理的 ASIN(风险 / 优化 / 邮箱汇总)', + href: `${VM}/delete-brand.html`, + permissionKeys: ['delete-brand'], + }, + { + id: 'risk', + name: '商品风险解决', + desc: '4 类风险一键处理:禁止显示 / 需要批准 / 详情已删 / 账户状态', + tag: '高频', + core: true, + href: `${VM}/product-risk.html`, + permissionKeys: ['product-risk'], + }, + { + id: 'time', + name: '定时匹配', + desc: '定时价格匹配,更快更及时抢回购物车', + href: `${VM}/shop-match.html`, + permissionKeys: ['shop-match'], + }, + { + id: 'follow', + name: '跟价', + desc: '核心工具 · 时时跟价抢购物车,支持指定 ASIN 快速跟价', + tag: '核心', + core: true, + href: `${VM}/price-track.html`, + permissionKeys: ['pricing', 'price-track'], + }, + { + id: 'patrol', + name: '巡店删除', + desc: '按商品状态巡店清理,摸清店铺商品全貌', + href: `${VM}/patrol-delete.html`, + permissionKeys: ['patrol-delete'], + }, + { + id: 'qasin', + name: '查询ASIN', + desc: '排查出单 ASIN 是否仍在售,保住优质数据', + href: `${VM}/query-asin.html`, + permissionKeys: ['query-asin'], + }, + { + id: 'scrape', + name: '店铺数据抓取', + desc: '核心 · 抓取店铺排名 / 品牌 / 销售,支撑复查与优化', + tag: '核心', + core: true, + href: `${VM}/shop-data-crawl.html`, + permissionKeys: ['shop-data-crawl', 'shop_data_crawl'], + }, + { + id: 'cash', + name: '取款', + desc: '留足平台扣款费用,其余全部提现', + href: `${VM}/withdraw.html`, + permissionKeys: ['withdraw'], + }, + { + id: 'shop', + name: '店铺状态查询', + desc: '查询店铺账号当前状态,及时发现异常', + permissionKeys: ['shop-status', 'shop'], + }, + ], + }, + { + key: 'logi', + columnKey: 'brand_logistics_tools', + name: '后勤工具', + desc: '采购 · ERP,供应链与系统对接', + color: '#A78BFA', + weak: '#221E38', + tools: [ + { + id: 'purchase', + name: '采购', + desc: '供应链采购与订单管理', + permissionKeys: ['purchase'], + }, + { + id: 'erp', + name: 'ERP', + desc: '对接 ERP 系统,同步库存与订单', + permissionKeys: ['erp'], + }, + ], + }, +] + +/** 图标渐变背景(照 icons.py ICON_COLORS 转 CSS) */ +export const TOOL_ICON_GRADIENTS: Record = { + collect: 'linear-gradient(180deg, #5EEAD4, #14B8A6)', + variant: 'linear-gradient(180deg, #93C5FD, #3B82F6)', + dedupe: 'linear-gradient(180deg, #C4B5FD, #8B5CF6)', + source: 'linear-gradient(180deg, #F9A8D4, #EC4899)', + brand: 'linear-gradient(180deg, #FDBA74, #F97316)', + patent: 'linear-gradient(180deg, #FDE047, #EAB308)', + split: 'linear-gradient(180deg, #86EFAC, #22C55E)', + convert: 'linear-gradient(180deg, #FCA5A5, #EF4444)', + list: 'linear-gradient(180deg, #5EEAD4, #14B8A6)', + delasin: 'linear-gradient(180deg, #93C5FD, #3B82F6)', + risk: 'linear-gradient(180deg, #C4B5FD, #8B5CF6)', + time: 'linear-gradient(180deg, #F9A8D4, #EC4899)', + follow: 'linear-gradient(180deg, #FDBA74, #F97316)', + patrol: 'linear-gradient(180deg, #FDE047, #EAB308)', + qasin: 'linear-gradient(180deg, #86EFAC, #22C55E)', + scrape: 'linear-gradient(180deg, #FCA5A5, #EF4444)', + cash: 'linear-gradient(180deg, #5EEAD4, #14B8A6)', + shop: 'linear-gradient(180deg, #93C5FD, #3B82F6)', + purchase: 'linear-gradient(180deg, #C4B5FD, #8B5CF6)', + erp: 'linear-gradient(180deg, #F9A8D4, #EC4899)', +} + +/** + * 图标白色图形(48x48,照 icons.py 各 icon_* 坐标转 SVG 路径) + * 默认描边 stroke:#fff width:3(方头 butt),实心填充 shape 用 .fill 标记 + */ +export const TOOL_ICON_SHAPES: Record = { + // 采集数据:圆角方框+向下箭头 + collect: '', + // ASIN变体采集:两个重叠方框 + variant: '', + // 数据去重:左右双向箭头 + dedupe: '', + // 货源查询:地球(圆+经纬线) + source: '', + // 品牌检测:盾牌+对勾 + brand: '', + // 外观专利检测:灯泡+对勾 + patent: '', + // 数据拆分:Y形分叉箭头 + split: '', + // 格式转换:循环箭头 + convert: '', + // 上架:方框+向上箭头 + list: '', + // 删除ASIN:垃圾桶 + delasin: '', + // 商品风险解决:三角+感叹号 + risk: '', + // 定时匹配:时钟 + time: '', + // 跟价:标签 + follow: '', + // 巡店删除:眼睛 + patrol: '', + // 查询ASIN:放大镜 + qasin: '', + // 店铺数据抓取:立方体 + scrape: '', + // 取款:钱包 + cash: '', + // 店铺状态查询:店铺 + shop: '', + // 采购:购物车 + purchase: '', + // ERP:网格 + erp: '', +} + +export const TOOL_MAP: ReadonlyMap = new Map( + TOOL_GROUPS.flatMap((group) => group.tools.map((tool) => [tool.id, tool] as const)), +) + +/** + * 工具页页头长描述(参考桌面板 detail_desc,优先于卡片 desc 显示) + */ +export const TOOL_DETAILS: Readonly> = { + collect: '默认勾选有排名和FBM这两个选择', + dedupe: '去重之后,切记一定上传到数富后台去重数据库', + brand: '核查上架数据品牌风险,先跑精确匹配,后跑嵌入式', + delasin: '收集统一要删除的ASIN,批量删除清理', + risk: + 'A 搜索结果中禁止显示;B 需要批准(最通用、使用频率最高);C 详情页面已删除;D 账户状态处理(需审批时用后台处理)。', + follow: + '一个虚拟桌面跟一个国家,错开跑即可时时跟价抢购物车。指定 ASIN 文档跟价:用后台有利润数据快速跟价,下班前跑一下,确保出单数据及时是我们的购物车。', + patrol: '巡店删除是按商品状态删除,如审核多次不通过的商品。不放任何状态跑一遍,可清楚掌握店铺数据情况。', + qasin: '通过店小蜜导出出单 ASIN,上传后台,时不时排查是否还在店铺售卖,确保优质数据时刻在店。', + scrape: + '没排名、久未出单数据可优化删除;获取店铺所有数据的排名、品牌和销售情况。半个月到一个月复查一次品牌,确保数据没有被注册。', +} + +export type VisibleGroup = ToolGroup & { tools: ToolInfo[] } + +/** + * 按菜单权限过滤分组(逻辑与 BrandTopBar.visibleNavGroups 一致): + * 组键(brand_front_tools 等)命中则整组展示;否则只展示有权限的工具; + * 无任何可见工具的分组整体隐藏。 + */ +export function filterGroupsByPermission( + groups: ReadonlyArray, + allowedKeys: ReadonlyArray, +): VisibleGroup[] { + const allowedSet = new Set(allowedKeys.map((key) => String(key || '').trim().toLowerCase()).filter(Boolean)) + return groups.flatMap((group) => { + if (allowedSet.has(group.columnKey)) { + return [{ ...group, tools: [...group.tools] }] + } + const tools = group.tools.filter((tool) => + tool.permissionKeys.some((key) => allowedSet.has(key.toLowerCase())), + ) + return tools.length ? [{ ...group, tools }] : [] + }) +} + +/** 从工作流时间线中截取有权限的步骤(与分组逻辑共用权限集合) */ +export function filterWorkflowByPermission( + steps: ReadonlyArray, + allowedKeys: ReadonlyArray, +): WorkflowStep[] { + const allowedSet = new Set(allowedKeys.map((key) => String(key || '').trim().toLowerCase()).filter(Boolean)) + return steps.filter((step) => { + if (!step.toolId) return true + const tool = TOOL_MAP.get(step.toolId) + if (!tool) return true + return tool.permissionKeys.some((key) => allowedSet.has(key.toLowerCase())) + }) +} diff --git a/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue b/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue index 35b9e335..89796823 100644 --- a/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue @@ -1,6 +1,6 @@ diff --git a/frontend-vue/src/pages/brand/components/BrandBrandTab.vue b/frontend-vue/src/pages/brand/components/BrandBrandTab.vue new file mode 100644 index 00000000..4be4205a --- /dev/null +++ b/frontend-vue/src/pages/brand/components/BrandBrandTab.vue @@ -0,0 +1,494 @@ + + + + + diff --git a/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue b/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue index 3e24c2cc..0423373f 100644 --- a/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue @@ -1,6 +1,6 @@ diff --git a/frontend-vue/src/pages/brand/components/BrandConvertTab.vue b/frontend-vue/src/pages/brand/components/BrandConvertTab.vue index 2a7a879b..deeb2c66 100644 --- a/frontend-vue/src/pages/brand/components/BrandConvertTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandConvertTab.vue @@ -1,6 +1,6 @@ diff --git a/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue b/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue index 43d23aae..15255dca 100644 --- a/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue @@ -1,6 +1,6 @@  diff --git a/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue b/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue index 67e4ad0a..4ed8ed17 100644 --- a/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue @@ -1,6 +1,6 @@ diff --git a/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue b/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue index 967edb15..e483ced6 100644 --- a/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue @@ -1,6 +1,6 @@  diff --git a/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue b/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue index b3d1d675..af09322c 100644 --- a/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue @@ -1,6 +1,6 @@  + + diff --git a/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue b/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue index be03664a..5a325d7a 100644 --- a/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue @@ -1,6 +1,6 @@  diff --git a/frontend-vue/src/pages/home/DesktopHomePage.vue b/frontend-vue/src/pages/home/DesktopHomePage.vue new file mode 100644 index 00000000..e2146e94 --- /dev/null +++ b/frontend-vue/src/pages/home/DesktopHomePage.vue @@ -0,0 +1,471 @@ + + + + + diff --git a/frontend-vue/src/pages/login/DesktopLoginPage.vue b/frontend-vue/src/pages/login/DesktopLoginPage.vue new file mode 100644 index 00000000..8a167b59 --- /dev/null +++ b/frontend-vue/src/pages/login/DesktopLoginPage.vue @@ -0,0 +1,366 @@ + + + + + diff --git a/frontend-vue/src/shared/api/types/modules/brand.ts b/frontend-vue/src/shared/api/types/modules/brand.ts index 8f907e04..cbc0ca5a 100644 --- a/frontend-vue/src/shared/api/types/modules/brand.ts +++ b/frontend-vue/src/shared/api/types/modules/brand.ts @@ -32,6 +32,7 @@ export interface BrandTaskItem { created_at?: string progress_total?: number progress_current?: number + error_message?: string result_paths?: BrandTaskResultPaths } diff --git a/frontend-vue/src/shared/bridges/pywebview.ts b/frontend-vue/src/shared/bridges/pywebview.ts index 4a34875b..84810a84 100644 --- a/frontend-vue/src/shared/bridges/pywebview.ts +++ b/frontend-vue/src/shared/bridges/pywebview.ts @@ -8,6 +8,35 @@ export interface UploadedJavaFile { export type ProxyMode = 1 | 2; +/** ASIN 变体采集任务项(/batch/list 返回元素,后端数值状态 0排队/1执行/2成功/3失败) */ +export interface VariantTaskListItem { + task_id: string; + status?: number | string; + task_type?: string; + update_time?: string; + res_type?: number | string; + res_mes?: string; + file_url?: string[] | string; + task_data?: { + file_url?: string; + execute_country?: string[]; + proxy_url?: string; + proxy_mode?: number | string; + [key: string]: unknown; + }; +} + +/** ASIN 变体采集单任务查询 data 段 */ +export interface VariantTaskQueryData { + message?: string; + msg?: string; + res_mes?: string; + res_type?: number | string; + file_url?: string[] | string; + task_data?: VariantTaskListItem['task_data']; + [key: string]: unknown; +} + export interface DesktopConfig { proxy_url?: string; proxy_mode?: number | string; @@ -58,6 +87,10 @@ export interface PywebviewApi { data?: UploadedJavaFile; error?: string; }>; + save_file_from_url?: ( + url: string, + filename: string, + ) => Promise<{ success: boolean; path?: string; error?: string }>; save_file_from_url_new?: ( url: string, filename: string, @@ -86,8 +119,59 @@ export interface PywebviewApi { enqueue_json?: ( data: unknown, ) => Promise<{ success: boolean; queue_size?: number; error?: string }>; + // —— ASIN 变体采集(VariantCollectionAPI,vc_* 前缀)—— + vc_select_input_files?: () => Promise; + vc_start_collect?: (params: { + mode: 'asin' | 'file'; + files?: string[]; + asins?: string[]; + countries: string[]; + uid: string; + }) => Promise<{ + success: boolean; + tasks?: Array<{ task_id: string; source?: string; execute_country?: string[] }>; + error?: string; + }>; + vc_query_task?: ( + taskId: string, + ) => Promise<{ + success: boolean; + status?: number | string; + data?: VariantTaskQueryData; + error?: string; + }>; + vc_list_tasks?: (query: { + uid: string; + page: number; + page_size: number; + }) => Promise<{ + success: boolean; + page?: number; + total_page?: number; + total?: number; + tasks?: VariantTaskListItem[]; + error?: string; + }>; + vc_export_task?: ( + taskId: string, + ) => Promise<{ success: boolean; file_id?: string; error?: string }>; + vc_query_export?: ( + fileId: string, + ) => Promise<{ + success: boolean; + status?: number | string; + file_url?: string; + file_name?: string; + error?: string; + }>; + vc_download_files_as_zip?: (params: { + urls: string[]; + zip_name: string; + download_id: string; + }) => Promise<{ success: boolean; path?: string; error?: string }>; read_config?: () => Promise; save_config?: (data: DesktopConfigUpdate) => Promise; + get_device_id?: () => Promise<{ success: boolean; device_id?: string; error?: string }>; } declare global { diff --git a/frontend-vue/src/shared/page-prefix.ts b/frontend-vue/src/shared/page-prefix.ts new file mode 100644 index 00000000..895425c5 --- /dev/null +++ b/frontend-vue/src/shared/page-prefix.ts @@ -0,0 +1,20 @@ +/** + * 页面路径环境适配 + * + * 生产环境(桌面客户端 Flask)功能页由 /new_web_source/ 路由提供; + * vite dev 下页面直接挂在根路径。旧静态页(/brand、/web_source/*)仅生产存在。 + */ + +/** 当前是否运行在桌面客户端生产环境(页面路径以 /new_web_source/ 开头) */ +export function isDesktopClientPage(): boolean { + return typeof window !== 'undefined' && window.location.pathname.startsWith('/new_web_source') +} + +/** 链接本地化:dev 预览去掉 /new_web_source 前缀;/brand、/web_source/* 等旧静态页仅桌面端可用 */ +export function resolvePageHref(rawHref?: string): string { + if (!rawHref) return '' + if (rawHref.startsWith('/new_web_source/')) { + return `${isDesktopClientPage() ? '/new_web_source' : ''}${rawHref.slice('/new_web_source'.length)}` + } + return isDesktopClientPage() ? rawHref : '' +} diff --git a/frontend-vue/src/styles/main.css b/frontend-vue/src/styles/main.css index 9a107033..e4326260 100644 --- a/frontend-vue/src/styles/main.css +++ b/frontend-vue/src/styles/main.css @@ -2,7 +2,7 @@ @import './reset.css'; body { - background: #1a1a1a; + background: #151a25; font-family: var(--font-family-base); } @@ -97,6 +97,12 @@ html { overscroll-behavior: contain; scrollbar-gutter: stable; } + + /* 亚马逊工具台外壳:旧功能页中间区(.main-content)由外壳撑满剩余高度 */ + .amazon-tool-shell > .tool-shell-body > .main-content { + height: 100%; + min-height: 0; + } } .theme-light { diff --git a/frontend-vue/src/variant-collection-main.ts b/frontend-vue/src/variant-collection-main.ts new file mode 100644 index 00000000..b9c3699c --- /dev/null +++ b/frontend-vue/src/variant-collection-main.ts @@ -0,0 +1,12 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' +import 'element-plus/dist/index.css' +import '@/styles/main.css' +import BrandVariantTab from '@/pages/brand/components/BrandVariantTab.vue' + +dayjs.locale('zh-cn') + +createApp(BrandVariantTab).use(ElementPlus, { locale: zhCn }).mount('#app') diff --git a/frontend-vue/tests/amazon-tool-catalog.test.ts b/frontend-vue/tests/amazon-tool-catalog.test.ts new file mode 100644 index 00000000..bdb98d2d --- /dev/null +++ b/frontend-vue/tests/amazon-tool-catalog.test.ts @@ -0,0 +1,90 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { + filterGroupsByPermission, + filterWorkflowByPermission, + TOOL_DETAILS, + TOOL_GROUPS, + TOOL_ICON_GRADIENTS, + TOOL_ICON_SHAPES, + TOOL_MAP, +} from '../src/pages/amazon/tool-catalog.ts' + +test('工具目录:20 个工具 id 唯一且每个都有图标与渐变配色', () => { + const ids = TOOL_GROUPS.flatMap((g) => g.tools.map((t) => t.id)) + assert.equal(ids.length, 20, '工具总数应为 20(前端8 + 运营10 + 后勤2)') + assert.equal(new Set(ids).size, ids.length, '工具 id 不应重复') + for (const id of ids) { + assert.ok(TOOL_ICON_SHAPES[id], `图标形状缺失: ${id}`) + assert.ok(TOOL_ICON_GRADIENTS[id], `图标渐变缺失: ${id}`) + assert.equal(TOOL_MAP.get(id)?.id, id, `TOOL_MAP 应覆盖: ${id}`) + } +}) + +test('工具目录:无页面的工具应在 catalog 里标记为未开通(无 href)', () => { + const pending = TOOL_GROUPS.flatMap((g) => g.tools).filter((t) => !t.href) + const pendingIds = pending.map((t) => t.id).sort() + assert.deepEqual(pendingIds, ['erp', 'purchase', 'shop'], '只有 店铺状态查询/采购/ERP 未开通') +}) + +test('权限过滤:组键命中整组保留,未命中按工具权限过滤并隐藏空组', () => { + const allowedAll = ['brand_front_tools', 'brand_operation_tools', 'brand_logistics_tools'] + const all = filterGroupsByPermission(TOOL_GROUPS, allowedAll) + assert.equal(all.length, 3) + assert.ok(all[0].tools.every((t) => t.permissionKeys.length > 0)) + + // 只给前端组键:仅前端工具整组可见 + const frontOnly = filterGroupsByPermission(TOOL_GROUPS, ['brand_front_tools']) + assert.equal(frontOnly.length, 1) + assert.equal(frontOnly[0].key, 'front') + assert.equal(frontOnly[0].tools.length, 8) + + // 无组键时按单个工具权限过滤 + const partial = filterGroupsByPermission(TOOL_GROUPS, ['collect-data', 'publish']) + const groups = new Map(partial.map((g) => [g.key, g])) + assert.deepEqual(groups.get('front')?.tools.map((t) => t.id), ['collect']) + assert.deepEqual(groups.get('ops')?.tools.map((t) => t.id), ['list']) + assert.equal(groups.has('logi'), false, '后勤无可见工具应整体隐藏') + + // 无任何权限:全部隐藏 + assert.equal(filterGroupsByPermission(TOOL_GROUPS, []).length, 0) +}) + +test('权限过滤:工作流时间线按工具权限截断', () => { + const front = TOOL_GROUPS.find((g) => g.key === 'front') + assert.ok(front?.workflow?.length === 8) + + // 只允许 采集数据,时间线只剩它一步 + const steps = filterWorkflowByPermission(front?.workflow ?? [], ['collect-data']) + assert.equal(steps.length, 1) + assert.equal(steps[0].name, '采集数据') + + // 无权限 → 空 + assert.equal(filterWorkflowByPermission(front?.workflow ?? [], []).length, 0) +}) + +test('工具目录:页面链接与现有 BrandTopBar 导航映射一致', () => { + const expectHref = new Map([ + ['collect', '/new_web_source/collect-data.html'], + ['dedupe', '/new_web_source/dedupe.html'], + ['source', '/new_web_source/similar-asin.html'], + ['brand', '/brand'], + ['list', '/new_web_source/publish.html'], + ['follow', '/new_web_source/price-track.html'], + ['scrape', '/new_web_source/shop-data-crawl.html'], + ['cash', '/new_web_source/withdraw.html'], + ]) + for (const [id, href] of expectHref) { + assert.equal(TOOL_MAP.get(id)?.href, href, `工具 ${id} 应指向 ${href}`) + } +}) + +test('工具目录:页头长描述(detail)只覆盖已知工具,且不为空', () => { + for (const [id, detail] of Object.entries(TOOL_DETAILS)) { + assert.ok(TOOL_MAP.has(id), `TOOL_DETAILS 引用了未知工具: ${id}`) + assert.ok(detail.trim().length > 0, `工具 ${id} 的 detail 不应为空`) + const toolsWithHref = TOOL_MAP.get(id) + assert.ok(toolsWithHref?.href, `detail 只应用于有页面的工具: ${id}`) + } +}) diff --git a/frontend-vue/variant-collection.html b/frontend-vue/variant-collection.html new file mode 100644 index 00000000..8470f437 --- /dev/null +++ b/frontend-vue/variant-collection.html @@ -0,0 +1,12 @@ + + + + + + ASIN变体采集 - 数富AI + + +
+ + + diff --git a/frontend-vue/vite.config.ts b/frontend-vue/vite.config.ts index b2b87df2..cb25a116 100644 --- a/frontend-vue/vite.config.ts +++ b/frontend-vue/vite.config.ts @@ -34,6 +34,17 @@ export default defineConfig({ changeOrigin: true, rewrite: (path) => path.replace(/^\/newApi/, ''), }, + // /login、/check_login、/logout 等无 /api 前缀的 Java 接口(登录/用户信息) + '/newApi/login': { + target: process.env.VITE_API_TARGET || 'http://127.0.0.1:18080/', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/newApi/, ''), + }, + '/newApi/check_login': { + target: process.env.VITE_API_TARGET || 'http://127.0.0.1:18080/', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/newApi/, ''), + }, }, }, build: { @@ -57,7 +68,12 @@ export default defineConfig({ 'shop-data-crawl': resolve(__dirname, 'shop-data-crawl.html'), withdraw: resolve(__dirname, 'withdraw.html'), 'collect-data': resolve(__dirname, 'collect-data.html'), + 'variant-collection': resolve(__dirname, 'variant-collection.html'), + brand: resolve(__dirname, 'brand.html'), 'image-video': resolve(__dirname, 'image-video.html'), + 'amazon-console': resolve(__dirname, 'amazon-console.html'), + home: resolve(__dirname, 'home.html'), + login: resolve(__dirname, 'login.html'), }, output: { entryFileNames: (chunkInfo) =>