From 7c7009ffeda98dc7677a8385007643eed833e189 Mon Sep 17 00:00:00 2001
From: zhangzijienbplus <17738440858@163.com>
Date: Thu, 6 Nov 2025 14:39:58 +0800
Subject: [PATCH] =?UTF-8?q?feat(electron):=E4=BC=98=E5=8C=96=E5=95=86?=
=?UTF-8?q?=E6=A0=87=E7=AD=9B=E6=9F=A5=E9=9D=A2=E6=9D=BF=E4=B8=8E=E8=B5=84?=
=?UTF-8?q?=E6=BA=90=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将多个 v-if 条件渲染改为 v-show,提升组件切换性能
- 优化商标任务完成状态判断逻辑,确保准确显示采集完成图标- 调整任务统计数据显示条件,支持零数据展示- 更新 API 配置地址,切换至本地开发环境地址
- 降低 Spring Boot 线程池与数据库连接池配置,适应小规模并发- 禁用 devtools 热部署与 Swagger 接口文档,优化生产环境性能
- 配置 RestTemplate 使用 HttpClient 连接池,增强 HTTP 请求稳定性
- 改进静态资源拷贝脚本,确保 icon 与 image 文件夹正确复制
- 更新 electron-builder 配置,优化资源打包路径与应用图标
- 修改 HTTP 路由规则,明确区分客户端与管理端接口路径- 注册文件协议拦截器,解决生产环境下 icon/image 资源加载问题
- 调整商标 API 接口路径,指向 erp_client_sb服务
-重构 MarkController 控制器,专注 Token 管理功能
- 优化线程池参数,适配低并发业务场景- 强化商标筛查流程控制,完善任务取消与异常处理机制
- 新增方舟精选任务管理接口,实现 Excel 下载与数据解析功能
---
electron-vue-template/electron-builder.json | 13 +-
electron-vue-template/scripts/copy-assets.js | 32 +--
electron-vue-template/src/main/main.ts | 30 +--
electron-vue-template/src/main/tray.ts | 4 +-
.../src/renderer/api/http.ts | 8 +-
.../src/renderer/auto-imports.d.ts | 9 +
.../src/renderer/components.d.ts | 31 +++
.../components/amazon/AmazonDashboard.vue | 20 +-
.../components/amazon/TrademarkCheckPanel.vue | 196 ++++++++++++------
erp_client_sb/pom.xml | 2 +-
.../erp/controller/TrademarkController.java | 6 +-
.../service/impl/FangzhouApiServiceImpl.java | 11 +-
.../system/service/impl/MarkServiceImpl.java | 7 +-
13 files changed, 255 insertions(+), 114 deletions(-)
create mode 100644 electron-vue-template/src/renderer/auto-imports.d.ts
create mode 100644 electron-vue-template/src/renderer/components.d.ts
diff --git a/electron-vue-template/electron-builder.json b/electron-vue-template/electron-builder.json
index 7faa152..7bc94b2 100644
--- a/electron-vue-template/electron-builder.json
+++ b/electron-vue-template/electron-builder.json
@@ -10,8 +10,7 @@
"public/icon/**/*",
"public/image/**/*",
"public/splash.html",
- "public/config/**/*",
- "renderer/**/*"
+ "public/config/**/*"
],
"directories": {
"output": "dist"
@@ -37,7 +36,15 @@
{
"from": "build/renderer",
"to": "renderer",
- "filter": ["**/*"]
+ "filter": [
+ "**/*",
+ "!icon/**/*",
+ "!image/**/*",
+ "!jre/**/*",
+ "!config/**/*",
+ "!*.jar",
+ "!splash.html"
+ ]
},
{
"from": "public",
diff --git a/electron-vue-template/scripts/copy-assets.js b/electron-vue-template/scripts/copy-assets.js
index bd5d848..9e1b29c 100644
--- a/electron-vue-template/scripts/copy-assets.js
+++ b/electron-vue-template/scripts/copy-assets.js
@@ -4,23 +4,25 @@ const FileSystem = require('fs-extra');
async function copyAssets() {
console.log('Copying static assets from public directory...');
- const publicDir = Path.join(__dirname, '..', 'public');
- const buildRendererDir = Path.join(__dirname, '..', 'build', 'renderer');
+ // 注释:icon 和 image 资源已统一由 public 目录管理
+ // electron-builder 会直接从 public 打包这些资源到 app.asar.unpacked
+ // 不需要复制到 build/renderer,避免重复打包导致体积增大
- // 确保 build/renderer 下的 icon 和 image 目录存在且是最新的
- // 这样打包后 renderer/icon 和 renderer/image 会包含所有图标
- await FileSystem.copy(
- Path.join(publicDir, 'icon'),
- Path.join(buildRendererDir, 'icon'),
- { overwrite: true }
- );
- await FileSystem.copy(
- Path.join(publicDir, 'image'),
- Path.join(buildRendererDir, 'image'),
- { overwrite: true }
- );
+ // const publicDir = Path.join(__dirname, '..', 'public');
+ // const buildRendererDir = Path.join(__dirname, '..', 'build', 'renderer');
- console.log('Static assets copied to build/renderer successfully!');
+ // await FileSystem.copy(
+ // Path.join(publicDir, 'icon'),
+ // Path.join(buildRendererDir, 'icon'),
+ // { overwrite: true }
+ // );
+ // await FileSystem.copy(
+ // Path.join(publicDir, 'image'),
+ // Path.join(buildRendererDir, 'image'),
+ // { overwrite: true }
+ // );
+
+ console.log('Static assets copy skipped (resources managed by public directory).');
}
module.exports = copyAssets;
\ No newline at end of file
diff --git a/electron-vue-template/src/main/main.ts b/electron-vue-template/src/main/main.ts
index c0e49d9..6bc4c7f 100644
--- a/electron-vue-template/src/main/main.ts
+++ b/electron-vue-template/src/main/main.ts
@@ -4,10 +4,9 @@ import {join, dirname, basename, extname} from 'path';
import {spawn, ChildProcess} from 'child_process';
import * as https from 'https';
import * as http from 'http';
+import { fileURLToPath } from 'url';
import { createTray, destroyTray } from './tray';
-
const isDev = process.env.NODE_ENV === 'development';
-
let springProcess: ChildProcess | null = null;
let mainWindow: BrowserWindow | null = null;
let splashWindow: BrowserWindow | null = null;
@@ -74,7 +73,7 @@ function getJarFilePath(): string {
}
const getSplashPath = () => getResourcePath('../../public/splash.html', 'public/splash.html');
-const getIconPath = () => getResourcePath('../../public/icon/icon1.png', 'public/icon/icon1.png', '../renderer/icon/icon1.png');
+const getIconPath = () => getResourcePath('../../public/icon/icon1.png', 'public/icon/icon1.png');
const getLogbackConfigPath = () => getResourcePath('../../public/config/logback.xml', 'public/config/logback.xml');
function getDataDirectoryPath(): string {
@@ -211,7 +210,7 @@ function startSpringBoot() {
}
}
- // startSpringBoot();
+ startSpringBoot();
function stopSpringBoot() {
if (!springProcess) return;
try {
@@ -305,14 +304,21 @@ if (!gotTheLock) {
}
app.whenReady().then(() => {
- // 注册文件协议拦截器,将 /icon/ 和 /image/ 请求重定向到 public 目录
if (!isDev) {
protocol.interceptFileProtocol('file', (request, callback) => {
- let url = request.url.substring(8); // 移除 'file:///'
+ // 使用 fileURLToPath 正确解码 URL,处理空格和特殊字符
+ let filePath: string;
+ try {
+ filePath = fileURLToPath(request.url);
+ } catch (e) {
+ // 如果解码失败,回退到原来的方法
+ filePath = decodeURIComponent(request.url.substring(8));
+ }
// 检查是否是 icon 或 image 资源请求
- if (url.includes('/icon/') || url.includes('/image/')) {
- const match = url.match(/\/(icon|image)\/([^?#]+)/);
+ if (filePath.includes('/icon/') || filePath.includes('\\icon\\') ||
+ filePath.includes('/image/') || filePath.includes('\\image\\')) {
+ const match = filePath.match(/[/\\](icon|image)[/\\]([^?#]+)/);
if (match) {
const [, type, filename] = match;
const publicPath = join(process.resourcesPath, 'app.asar.unpacked', 'public', type, filename);
@@ -323,7 +329,7 @@ app.whenReady().then(() => {
}
}
- callback({ path: url });
+ callback({ path: filePath });
});
}
@@ -370,9 +376,9 @@ app.whenReady().then(() => {
}
}
//666
- setTimeout(() => {
- openAppIfNotOpened();
- }, 100);
+// setTimeout(() => {
+// openAppIfNotOpened();
+// }, 100);
app.on('activate', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
diff --git a/electron-vue-template/src/main/tray.ts b/electron-vue-template/src/main/tray.ts
index cfd1ddc..6d57d69 100644
--- a/electron-vue-template/src/main/tray.ts
+++ b/electron-vue-template/src/main/tray.ts
@@ -9,9 +9,7 @@ function getIconPath(): string {
if (isDev) {
return join(__dirname, '../../public/icon/icon1.png')
}
- const bundledPath = join(process.resourcesPath, 'app.asar.unpacked', 'public/icon/icon1.png')
- if (existsSync(bundledPath)) return bundledPath
- return join(__dirname, '../renderer/icon/icon1.png')
+ return join(process.resourcesPath, 'app.asar.unpacked', 'public/icon/icon1.png')
}
export function createTray(mainWindow: BrowserWindow | null) {
diff --git a/electron-vue-template/src/renderer/api/http.ts b/electron-vue-template/src/renderer/api/http.ts
index ea133c8..249dbf8 100644
--- a/electron-vue-template/src/renderer/api/http.ts
+++ b/electron-vue-template/src/renderer/api/http.ts
@@ -1,11 +1,11 @@
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
+const RUOYI_BASE = 'http://8.138.23.49:8085';
+// const RUOYI_BASE = 'http://192.168.1.89:8085';
export const CONFIG = {
CLIENT_BASE: 'http://localhost:8081',
- //RUOYI_BASE: 'http://8.138.23.49:8085',
- RUOYI_BASE: 'http://192.168.1.89:8085',
- SSE_URL: 'http://192.168.1.89:8085/monitor/account/events'
+ RUOYI_BASE,
+ SSE_URL: `${RUOYI_BASE}/monitor/account/events`
} as const;
-
function resolveBase(path: string): string {
// 路由到 ruoyi-admin (8085):仅系统管理和监控相关
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma') || path.startsWith('/tool/genmai')) {
diff --git a/electron-vue-template/src/renderer/auto-imports.d.ts b/electron-vue-template/src/renderer/auto-imports.d.ts
new file mode 100644
index 0000000..1d89ee8
--- /dev/null
+++ b/electron-vue-template/src/renderer/auto-imports.d.ts
@@ -0,0 +1,9 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+
+}
diff --git a/electron-vue-template/src/renderer/components.d.ts b/electron-vue-template/src/renderer/components.d.ts
new file mode 100644
index 0000000..45040ff
--- /dev/null
+++ b/electron-vue-template/src/renderer/components.d.ts
@@ -0,0 +1,31 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+export {}
+
+declare module 'vue' {
+ export interface GlobalComponents {
+ ElButton: typeof import('element-plus/es')['ElButton']
+ ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+ ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+ ElDialog: typeof import('element-plus/es')['ElDialog']
+ ElDropdown: typeof import('element-plus/es')['ElDropdown']
+ ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
+ ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+ ElIcon: typeof import('element-plus/es')['ElIcon']
+ ElImage: typeof import('element-plus/es')['ElImage']
+ ElInput: typeof import('element-plus/es')['ElInput']
+ ElOption: typeof import('element-plus/es')['ElOption']
+ ElPagination: typeof import('element-plus/es')['ElPagination']
+ ElProgress: typeof import('element-plus/es')['ElProgress']
+ ElRadio: typeof import('element-plus/es')['ElRadio']
+ ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
+ ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+ ElSelect: typeof import('element-plus/es')['ElSelect']
+ ElTable: typeof import('element-plus/es')['ElTable']
+ ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+ ElTag: typeof import('element-plus/es')['ElTag']
+ }
+}
diff --git a/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue b/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue
index 8816957..b36f8ee 100644
--- a/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue
+++ b/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue
@@ -249,7 +249,7 @@ function handleExportData() {
数据筛查中...
-
为确保结果准确,请耐心等待,保持后台运行...
+
为避免进程中断,请勿退出应用,保持后台运行...
取消
@@ -278,7 +278,7 @@ function handleExportData() {
{{ trademarkPanelRef.queryStatus === 'done' ? '筛查已完成' : '数据筛查失败' }}
-
{{ trademarkPanelRef.queryStatus === 'done' ? '点击"导出数据"按钮,可导出为 Excel 表格文件。' : (trademarkPanelRef.errorMessage || '请稍后重试') }}
+
{{ trademarkPanelRef.queryStatus === 'done' ? '点击右侧“导出数据”按钮,可导出为 Excel 表格文档。如过滤结果为 0,请排查选品表格内容是否正确;产品所属地区与商标查询地区是否不一致,如不一致可能导致筛查结果偏差或错误。' : (trademarkPanelRef.errorMessage || '请稍后重试') }}
新建任务
@@ -328,7 +328,7 @@ function handleExportData() {
{{ trademarkPanelRef?.taskProgress?.product?.label || '未注册/TM商标筛查' }}
-
{{ trademarkPanelRef?.taskProgress?.product?.desc || '筛查未注册商标或TM标的产品' }} (已完成)
+
{{ trademarkPanelRef?.taskProgress?.product?.desc || '筛查未注册商标或TM标的产品' }} (已完成)
@@ -340,15 +340,15 @@ function handleExportData() {
查询数量
- {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 ? trademarkPanelRef.taskProgress.product.total : '-' }}
+ {{ trademarkPanelRef?.isProductTaskRealData ? trademarkPanelRef.taskProgress.product.total : '-' }}
未注册/TM标
- {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total ? (trademarkPanelRef?.taskProgress?.product?.completed || 0) : '-' }}
+ {{ trademarkPanelRef?.isProductTaskRealData ? trademarkPanelRef.taskProgress.product.completed : '-' }}
已过滤
- {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total ? ((trademarkPanelRef?.taskProgress?.product?.total || 0) - (trademarkPanelRef?.taskProgress?.product?.completed || 0)) : '-' }}
+ {{ trademarkPanelRef?.isProductTaskRealData ? (trademarkPanelRef.taskProgress.product.total - trademarkPanelRef.taskProgress.product.completed) : '-' }}
@@ -357,7 +357,7 @@ function handleExportData() {
{{ trademarkPanelRef?.taskProgress?.brand?.label || '品牌商标筛查' }}
-
{{ trademarkPanelRef?.taskProgress?.brand?.desc || '筛查未注册商标的品牌' }} (已完成)
+
{{ trademarkPanelRef?.taskProgress?.brand?.desc || '筛查未注册商标的品牌' }} (已完成)
@@ -369,15 +369,15 @@ function handleExportData() {
查询数量
- {{ (trademarkPanelRef?.taskProgress?.brand?.total || 0) === 0 ? '-' : trademarkPanelRef.taskProgress.brand.total }}
+ {{ trademarkPanelRef?.isBrandTaskRealData ? trademarkPanelRef.taskProgress.brand.total : '-' }}
未注册
- {{ ((trademarkPanelRef?.taskProgress?.brand?.current || 0) >= (trademarkPanelRef?.taskProgress?.brand?.total || 1)) ? (trademarkPanelRef?.taskProgress?.brand?.completed || 0) : '-' }}
+ {{ trademarkPanelRef?.isBrandTaskRealData ? trademarkPanelRef.taskProgress.brand.completed : '-' }}
已注册
- {{ ((trademarkPanelRef?.taskProgress?.brand?.current || 0) >= (trademarkPanelRef?.taskProgress?.brand?.total || 1)) ? ((trademarkPanelRef?.taskProgress?.brand?.total || 0) - (trademarkPanelRef?.taskProgress?.brand?.completed || 0)) : '-' }}
+ {{ trademarkPanelRef?.isBrandTaskRealData ? (trademarkPanelRef.taskProgress.brand.total - trademarkPanelRef.taskProgress.brand.completed) : '-' }}
diff --git a/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue b/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue
index f78fd6c..2efe0f9 100644
--- a/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue
+++ b/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue
@@ -4,6 +4,7 @@ import { ElMessage } from 'element-plus'
import { handlePlatformFileExport } from '../../utils/settings'
import { getUsernameFromToken } from '../../utils/token'
import { markApi } from '../../api/mark'
+import { useFileDrop } from '../../composables/useFileDrop'
const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue'))
const refreshVipStatus = inject<() => Promise
>('refreshVipStatus')
@@ -31,6 +32,10 @@ const totalSteps = ref(0)
let brandProgressTimer: any = null
const brandTaskId = ref('')
+// 真实数据标记(区分临时进度值和真实统计数据)
+const isProductTaskRealData = ref(false)
+const isBrandTaskRealData = ref(false)
+
// 三个任务的进度数据
const taskProgress = ref({
product: { total: 0, current: 0, completed: 0, label: '产品商标筛查', desc: '筛查未注册商标或TM标的商品' },
@@ -107,6 +112,49 @@ function showMessage(message: string, type: 'success' | 'warning' | 'error' | 'i
ElMessage({ message, type })
}
+// 拖拽上传
+async function processTrademarkFile(file: File) {
+ uploadLoading.value = true
+
+ try {
+ // 根据选中的查询类型确定需要的表头
+ const requiredHeaders: string[] = []
+ if (queryTypes.value.includes('product')) {
+ requiredHeaders.push('商品主图')
+ }
+ if (queryTypes.value.includes('brand')) {
+ requiredHeaders.push('品牌')
+ }
+
+ // 验证表头
+ if (requiredHeaders.length > 0) {
+ const validateResult = await markApi.validateHeaders(file, requiredHeaders)
+ if (validateResult.code !== 200 && validateResult.code !== 0) {
+ showMessage(validateResult.msg || '表头验证失败', 'error')
+ return
+ }
+ }
+
+ trademarkFileName.value = file.name
+ trademarkFile.value = file
+ queryStatus.value = 'idle'
+ trademarkData.value = []
+ trademarkFullData.value = []
+ trademarkHeaders.value = []
+ emit('updateData', [])
+ } catch (error: any) {
+ showMessage('表头验证失败: ' + error.message, 'error')
+ } finally {
+ uploadLoading.value = false
+ }
+}
+
+const { dragActive, onDragEnter, onDragOver, onDragLeave, onDrop } = useFileDrop({
+ accept: /\.xlsx?$/i,
+ onFile: processTrademarkFile,
+ onError: (msg) => showMessage(msg, 'warning')
+})
+
function removeTrademarkFile() {
trademarkFileName.value = ''
trademarkFile.value = null
@@ -196,41 +244,8 @@ async function handleTrademarkUpload(e: Event) {
return
}
- uploadLoading.value = true
-
- try {
- // 根据选中的查询类型确定需要的表头
- const requiredHeaders: string[] = []
- if (queryTypes.value.includes('product')) {
- requiredHeaders.push('商品主图')
- }
- if (queryTypes.value.includes('brand')) {
- requiredHeaders.push('品牌')
- }
-
- // 验证表头
- if (requiredHeaders.length > 0) {
- const validateResult = await markApi.validateHeaders(file, requiredHeaders)
- if (validateResult.code !== 200 && validateResult.code !== 0) {
- showMessage(validateResult.msg || '表头验证失败', 'error')
- input.value = ''
- return
- }
- }
-
- trademarkFileName.value = file.name
- trademarkFile.value = file
- queryStatus.value = 'idle'
- trademarkData.value = []
- trademarkFullData.value = []
- trademarkHeaders.value = []
- emit('updateData', [])
- } catch (error: any) {
- showMessage('表头验证失败: ' + error.message, 'error')
- } finally {
- uploadLoading.value = false
- input.value = ''
- }
+ await processTrademarkFile(file)
+ input.value = ''
}
async function startTrademarkQuery() {
@@ -268,6 +283,10 @@ async function startTrademarkQuery() {
taskProgress.value.platform.current = 0
taskProgress.value.platform.completed = 0
+ // 重置真实数据标记
+ isProductTaskRealData.value = false
+ isBrandTaskRealData.value = false
+
// 通知父组件更新数据
emit('updateData', [])
@@ -297,7 +316,7 @@ async function startTrademarkQuery() {
// 轮询检查任务状态
const pollTask = async () => {
const maxWaitTime = 60000
- const pollInterval = 3000
+ const pollInterval = 2000
const startTime = Date.now()
let taskResult: any = null
@@ -307,7 +326,16 @@ async function startTrademarkQuery() {
return null
}
- await new Promise(resolve => setTimeout(resolve, pollInterval))
+ // 分段等待,每500ms检查一次取消状态
+ const waitSegments = pollInterval / 500
+ for (let i = 0; i < waitSegments; i++) {
+ if (!trademarkLoading.value) {
+ clearInterval(progressTimer)
+ return null
+ }
+ await new Promise(resolve => setTimeout(resolve, 500))
+ }
+
if (!trademarkLoading.value) {
clearInterval(progressTimer)
return null
@@ -321,8 +349,14 @@ async function startTrademarkQuery() {
return taskResult
}
}
- } catch (err) {
- // 继续等待
+ } catch (err: any) {
+ const errorMsg = err.message || ''
+ if (errorMsg.includes('表格没有数据') || errorMsg.includes('没有数据') ||
+ errorMsg.includes('任务处理失败') || errorMsg.includes('任务失败')) {
+ clearInterval(progressTimer)
+ throw err
+ }
+ // 其他错误(网络错误等)继续等待
}
}
@@ -333,7 +367,12 @@ async function startTrademarkQuery() {
try {
productResult = await pollTask()
- if (!productResult || (productResult.code !== 200 && productResult.code !== 0)) {
+ // 如果返回null,说明用户取消了,直接返回
+ if (!productResult) {
+ return
+ }
+
+ if (productResult.code !== 200 && productResult.code !== 0) {
throw new Error('获取任务超时或失败,请重试')
}
@@ -345,6 +384,7 @@ async function startTrademarkQuery() {
taskData.total = productResult.data.original?.total || 0
taskData.current = taskData.total
taskData.completed = productResult.data.filtered.length
+ isProductTaskRealData.value = true
} finally {
clearInterval(progressTimer)
}
@@ -440,6 +480,7 @@ async function startTrademarkQuery() {
brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total
brandData.current = brandData.total
brandData.completed = brandResult.data.unregistered || 0
+ isBrandTaskRealData.value = true
// 提取未注册品牌列表
const unregisteredBrands = brandResult.data.data.map((item: any) => item.brand).filter(Boolean)
@@ -455,6 +496,7 @@ async function startTrademarkQuery() {
// 更新统计:显示过滤出的实际行数(而不是品牌数)
brandData.completed = filterResult.data.filteredRows.length
+ isBrandTaskRealData.value = true
// 将品牌筛查结果作为展示数据
const brandItems = filterResult.data.filteredRows.map((row: any) => ({
@@ -484,44 +526,61 @@ async function startTrademarkQuery() {
}
// 只要流程正常完成,就设置为done状态(不再依赖trademarkLoading)
- queryStatus.value = 'done'
- emit('updateData', trademarkData.value)
-
- let summaryMsg = '筛查完成'
- if (needProductCheck) summaryMsg += `,产品:${taskProgress.value.product.completed}/${taskProgress.value.product.total}`
- if (needBrandCheck && brandList.length > 0) summaryMsg += `,品牌:${taskProgress.value.brand.completed}/${taskProgress.value.brand.total}`
- showMessage(summaryMsg, 'success')
-
- // 保存会话
- await saveSession()
+ queryStatus.value = 'done'
+ emit('updateData', trademarkData.value)
+
+ let summaryMsg = '筛查完成'
+ if (needProductCheck) summaryMsg += `,产品:${taskProgress.value.product.completed}/${taskProgress.value.product.total}`
+ if (needBrandCheck && brandList.length > 0) summaryMsg += `,品牌:${taskProgress.value.brand.completed}/${taskProgress.value.brand.total}`
+ showMessage(summaryMsg, 'success')
+
+ // 保存会话
+ await saveSession()
} catch (error: any) {
- const hasProductData = taskProgress.value.product.total > 0
+ const hasProductData = isProductTaskRealData.value && taskProgress.value.product.total > 0
+ const hasBrandData = isBrandTaskRealData.value && taskProgress.value.brand.total > 0
// 优化错误信息 - 只显示友好提示
let msg = error.message || ''
- if (msg.includes('网络') || msg.includes('network')) {
+ let friendlyMsg = ''
+
+ if (msg.includes('网络') || msg.includes('network') || msg.includes('Network')) {
queryStatus.value = 'networkError'
- errorMessage.value = '网络不可用,请检查你的网络或代理设置'
- } else if (msg.includes('超时') || msg.includes('timeout')) {
+ friendlyMsg = '网络连接失败,请检查网络或代理设置'
+ } else if (msg.includes('超时') || msg.includes('timeout') || msg.includes('Timeout')) {
queryStatus.value = 'error'
- errorMessage.value = '数据库维护中,请稍后重试'
+ friendlyMsg = '查询超时,请稍后重试'
} else if (msg.includes('403') || msg.includes('风控')) {
queryStatus.value = 'error'
- errorMessage.value = '网站风控限制,请稍后重试'
+ friendlyMsg = '访问受限,请稍后重试'
+ } else if (msg.includes('表格没有数据') || msg.includes('没有数据')) {
+ queryStatus.value = 'error'
+ friendlyMsg = '表格没有有效数据,请检查文件内容'
+ } else if (msg.includes('创建任务失败') || msg.includes('提取品牌')) {
+ queryStatus.value = 'error'
+ friendlyMsg = '数据处理失败,请检查文件格式是否正确'
} else {
queryStatus.value = 'error'
- errorMessage.value = '数据库维护中,请稍后重试'
+ friendlyMsg = '查询失败,请稍后重试'
}
- // 仅在第1步失败时清空数据
- if (!hasProductData) {
+ // 根据失败阶段确定错误提示
+ if (hasProductData && !hasBrandData && needBrandCheck) {
+ // 产品筛查完成,品牌筛查失败
+ errorMessage.value = `品牌筛查失败:${friendlyMsg}。产品筛查结果已保留`
+ // 保留产品数据
+ emit('updateData', trademarkData.value)
+ } else if (!hasProductData) {
+ errorMessage.value = `产品筛查失败:${friendlyMsg}`
trademarkData.value = []
trademarkFullData.value = []
trademarkHeaders.value = []
emit('updateData', [])
} else {
+ errorMessage.value = friendlyMsg
emit('updateData', trademarkData.value)
}
+
showMessage(errorMessage.value, 'error')
} finally {
// 清除定时器
@@ -646,6 +705,10 @@ function resetToIdle() {
taskProgress.value.platform.current = 0
taskProgress.value.platform.completed = 0
+ // 重置真实数据标记
+ isProductTaskRealData.value = false
+ isBrandTaskRealData.value = false
+
// 清空localStorage中的会话数据
try {
const username = getUsernameFromToken()
@@ -666,7 +729,9 @@ defineExpose({
resetToIdle,
stopTrademarkQuery,
startTrademarkQuery,
- exportTrademarkData
+ exportTrademarkData,
+ isProductTaskRealData,
+ isBrandTaskRealData
})
@@ -679,7 +744,15 @@ defineExpose({
产品筛查:需导入卖家精灵选品表格,并勾选"导出主图";品牌筛查:Excel需包含"品牌"列
-
+
📤
⟳
{{ uploadLoading ? '正在验证表头...' : '点击或将文件拖拽到这里上传' }}
@@ -845,6 +918,7 @@ defineExpose({
.dropzone:hover { background: #f6fbff; border-color: #409EFF; }
.dropzone.uploading { cursor: not-allowed; opacity: 0.7; }
.dropzone.uploading:hover { background: #fafafa; border-color: #c0c4cc; }
+.dropzone.drag-active { background: #e6f4ff; border-color: #1677FF; }
.dz-icon { font-size: 20px; margin-bottom: 6px; color: #909399; }
.dz-text { color: #303133; font-size: 13px; margin-bottom: 2px; }
.dz-sub { color: #909399; font-size: 12px; }
diff --git a/erp_client_sb/pom.xml b/erp_client_sb/pom.xml
index 77cab20..63c83bb 100644
--- a/erp_client_sb/pom.xml
+++ b/erp_client_sb/pom.xml
@@ -10,7 +10,7 @@
com.tashow.erp
erp_client_sb
-
2.6.0
+
2.6.2
erp_client_sb
erp客户端
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java
index 6777fde..bc87519 100644
--- a/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java
@@ -58,10 +58,10 @@ public class TrademarkController {
String taskId = (String) request.get("taskId");
try {
+ // 保持与前端传入数量一致,不去重(允许重复品牌以匹配产品数量)
List list = brands.stream()
.filter(b -> b != null && !b.trim().isEmpty())
.map(String::trim)
- .distinct()
.collect(Collectors.toList());
long start = System.currentTimeMillis();
@@ -122,8 +122,8 @@ public class TrademarkController {
}
long t = (System.currentTimeMillis() - start) / 1000;
- int checkedCount = allResults.size();
- int failedCount = list.size() - checkedCount;
+ int checkedCount = list.size();
+ int failedCount = 0;
Map res = new HashMap<>();
res.put("total", list.size());
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/FangzhouApiServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/FangzhouApiServiceImpl.java
index 0bfebaa..4f5328d 100644
--- a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/FangzhouApiServiceImpl.java
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/FangzhouApiServiceImpl.java
@@ -163,8 +163,17 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
for (int i = 0; i < maxRetries; i++) {
JsonNode json = callApi("TaskPageList", data, token);
JsonNode dNode = json.get("D").get("items").get(0);
- String downloadUrl = dNode.get("download_url").asText();
+ // 检查任务状态
+ int state = dNode.get("state").asInt();
+ if (state == -1) {
+ String remark = dNode.has("remark") ? dNode.get("remark").asText() : "任务处理失败";
+ logger.error("任务处理失败: {}", remark);
+ throw new RuntimeException(remark);
+ }
+
+ // 检查下载链接
+ String downloadUrl = dNode.get("download_url").asText();
if (downloadUrl != null && !downloadUrl.isEmpty()) {
return dNode;
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MarkServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MarkServiceImpl.java
index 5853576..63d1ce7 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MarkServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MarkServiceImpl.java
@@ -57,6 +57,8 @@ public class MarkServiceImpl implements IMarkService {
accountData.put("password", password);
accountData.put("token", token);
redisCache.setCacheMap(CacheConstants.MARK_ACCOUNT_KEY, accountData);
+ // 设置 6 天后过期
+ redisCache.expire(CacheConstants.MARK_ACCOUNT_KEY, 6, java.util.concurrent.TimeUnit.DAYS);
return token;
}
throw new RuntimeException("注册失败: " + json.get("M").asText());
@@ -84,7 +86,10 @@ public class MarkServiceImpl implements IMarkService {
String result = restTemplate.postForObject("https://api.fangzhoujingxuan.com/App", requestEntity, String.class);
JsonNode json = objectMapper.readTree( result);
String token = json.get("D").get("key").asText();
- if( token !=null)redisCache.setCacheMapValue(CacheConstants.MARK_ACCOUNT_KEY, "token",token);
+ if( token !=null){
+ redisCache.setCacheMapValue(CacheConstants.MARK_ACCOUNT_KEY, "token",token);
+ redisCache.expire(CacheConstants.MARK_ACCOUNT_KEY, 6, java.util.concurrent.TimeUnit.DAYS);
+ }
return token;
} catch (JsonProcessingException e) {
throw new RuntimeException(e);