feat(electron):优化商标筛查面板与资源加载逻辑
- 将多个 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 下载与数据解析功能
@@ -1,6 +1,6 @@
|
||||
import {app, BrowserWindow, ipcMain, Menu, screen, dialog} from 'electron';
|
||||
import {existsSync, createWriteStream, promises as fs, mkdirSync, copyFileSync, readdirSync, writeFileSync} from 'fs';
|
||||
import {join, dirname, basename} from 'path';
|
||||
import {app, BrowserWindow, ipcMain, Menu, screen, dialog, protocol} from 'electron';
|
||||
import {existsSync, createWriteStream, promises as fs, mkdirSync, copyFileSync, readdirSync, writeFileSync, readFileSync} from 'fs';
|
||||
import {join, dirname, basename, extname} from 'path';
|
||||
import {spawn, ChildProcess} from 'child_process';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
@@ -74,7 +74,7 @@ function getJarFilePath(): string {
|
||||
}
|
||||
|
||||
const getSplashPath = () => getResourcePath('../../public/splash.html', 'public/splash.html');
|
||||
const getIconPath = () => getResourcePath('../../public/icon/icon.png', 'public/icon/icon.png', '../renderer/icon/icon.png');
|
||||
const getIconPath = () => getResourcePath('../../public/icon/icon1.png', 'public/icon/icon1.png', '../renderer/icon/icon1.png');
|
||||
const getLogbackConfigPath = () => getResourcePath('../../public/config/logback.xml', 'public/config/logback.xml');
|
||||
|
||||
function getDataDirectoryPath(): string {
|
||||
@@ -211,7 +211,7 @@ function startSpringBoot() {
|
||||
}
|
||||
}
|
||||
|
||||
startSpringBoot();
|
||||
// startSpringBoot();
|
||||
function stopSpringBoot() {
|
||||
if (!springProcess) return;
|
||||
try {
|
||||
@@ -305,6 +305,28 @@ if (!gotTheLock) {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// 注册文件协议拦截器,将 /icon/ 和 /image/ 请求重定向到 public 目录
|
||||
if (!isDev) {
|
||||
protocol.interceptFileProtocol('file', (request, callback) => {
|
||||
let url = request.url.substring(8); // 移除 'file:///'
|
||||
|
||||
// 检查是否是 icon 或 image 资源请求
|
||||
if (url.includes('/icon/') || url.includes('/image/')) {
|
||||
const match = url.match(/\/(icon|image)\/([^?#]+)/);
|
||||
if (match) {
|
||||
const [, type, filename] = match;
|
||||
const publicPath = join(process.resourcesPath, 'app.asar.unpacked', 'public', type, filename);
|
||||
if (existsSync(publicPath)) {
|
||||
callback({ path: publicPath });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback({ path: url });
|
||||
});
|
||||
}
|
||||
|
||||
// 应用开机自启动配置
|
||||
const config = loadConfig();
|
||||
const shouldMinimize = config.launchMinimized || false;
|
||||
@@ -348,9 +370,9 @@ app.whenReady().then(() => {
|
||||
}
|
||||
}
|
||||
//666
|
||||
// setTimeout(() => {
|
||||
// openAppIfNotOpened();
|
||||
// }, 100);
|
||||
setTimeout(() => {
|
||||
openAppIfNotOpened();
|
||||
}, 100);
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
|
||||
@@ -7,11 +7,11 @@ let tray: Tray | null = null
|
||||
function getIconPath(): string {
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
if (isDev) {
|
||||
return join(__dirname, '../../public/icon/icon.png')
|
||||
return join(__dirname, '../../public/icon/icon1.png')
|
||||
}
|
||||
const bundledPath = join(process.resourcesPath, 'app.asar.unpacked', 'public/icon/icon.png')
|
||||
const bundledPath = join(process.resourcesPath, 'app.asar.unpacked', 'public/icon/icon1.png')
|
||||
if (existsSync(bundledPath)) return bundledPath
|
||||
return join(__dirname, '../renderer/icon/icon.png')
|
||||
return join(__dirname, '../renderer/icon/icon1.png')
|
||||
}
|
||||
|
||||
export function createTray(mainWindow: BrowserWindow | null) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
|
||||
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://8.138.23.49:8085/monitor/account/events'
|
||||
//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'
|
||||
} as const;
|
||||
|
||||
function resolveBase(path: string): string {
|
||||
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma') || path.startsWith('/tool/genmai') || path.startsWith('/tool/mark')) {
|
||||
// 路由到 ruoyi-admin (8085):仅系统管理和监控相关
|
||||
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma') || path.startsWith('/tool/genmai')) {
|
||||
return CONFIG.RUOYI_BASE;
|
||||
}
|
||||
return CONFIG.CLIENT_BASE;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { http } from './http'
|
||||
|
||||
export const markApi = {
|
||||
// 新建任务
|
||||
// 新建任务(调用 erp_client_sb)
|
||||
newTask(file: File) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return http.upload<{ code: number, data: any, msg: string }>('/tool/mark/newTask', formData)
|
||||
return http.upload<{ code: number, data: any, msg: string }>('/api/trademark/newTask', formData)
|
||||
},
|
||||
|
||||
// 获取任务列表及筛选数据(返回完整行数据和表头)
|
||||
// 获取任务列表及筛选数据(调用 erp_client_sb)
|
||||
getTask() {
|
||||
return http.get<{
|
||||
return http.post<{
|
||||
code: number,
|
||||
data: {
|
||||
original: any,
|
||||
@@ -18,7 +18,7 @@ export const markApi = {
|
||||
headers: string[] // 表头
|
||||
},
|
||||
msg: string
|
||||
}>('/tool/mark/task')
|
||||
}>('/api/trademark/task')
|
||||
},
|
||||
|
||||
// 品牌商标筛查
|
||||
|
||||
@@ -150,7 +150,7 @@ function handleExportData() {
|
||||
|
||||
<!-- ASIN查询面板 -->
|
||||
<AsinQueryPanel
|
||||
v-if="currentTab === 'asin'"
|
||||
v-show="currentTab === 'asin'"
|
||||
ref="asinPanelRef"
|
||||
:is-vip="isVip"
|
||||
@update-data="handleAsinDataUpdate"
|
||||
@@ -158,13 +158,13 @@ function handleExportData() {
|
||||
|
||||
<!-- 跟卖精灵面板 -->
|
||||
<GenmaiSpiritPanel
|
||||
v-if="currentTab === 'genmai'"
|
||||
v-show="currentTab === 'genmai'"
|
||||
:is-vip="isVip"
|
||||
/>
|
||||
|
||||
<!-- 商标筛查面板 -->
|
||||
<TrademarkCheckPanel
|
||||
v-if="currentTab === 'trademark'"
|
||||
v-show="currentTab === 'trademark'"
|
||||
ref="trademarkPanelRef"
|
||||
:is-vip="isVip"
|
||||
@update-data="handleTrademarkDataUpdate"
|
||||
@@ -296,7 +296,7 @@ function handleExportData() {
|
||||
<div class="status-column">
|
||||
<!-- 任务1:产品商标筛查 -->
|
||||
<div class="status-item">
|
||||
<img v-if="(trademarkPanelRef?.taskProgress?.product?.total || 0) > 100" src="/icon/done.png" alt="采集完成" class="status-indicator-icon" />
|
||||
<img v-if="(trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total" src="/icon/done.png" alt="采集完成" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" src="/icon/error.png" alt="筛查失败" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" src="/icon/cancel.png" alt="已取消" class="status-indicator-icon" />
|
||||
<img v-else-if="(trademarkPanelRef?.taskProgress?.product?.current || 0) > 0" src="/icon/inProgress.png" alt="采集中" class="status-indicator-icon" />
|
||||
@@ -340,15 +340,15 @@ function handleExportData() {
|
||||
<div class="task-stats">
|
||||
<div class="task-stat">
|
||||
<span class="stat-label">查询数量</span>
|
||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? trademarkPanelRef.taskProgress.product.total : '-' }}</span>
|
||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 ? trademarkPanelRef.taskProgress.product.total : '-' }}</span>
|
||||
</div>
|
||||
<div class="task-stat highlight">
|
||||
<span class="stat-label">未注册/TM标</span>
|
||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? (trademarkPanelRef?.taskProgress?.product?.completed || 0) : '-' }}</span>
|
||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total ? (trademarkPanelRef?.taskProgress?.product?.completed || 0) : '-' }}</span>
|
||||
</div>
|
||||
<div class="task-stat">
|
||||
<span class="stat-label">已过滤</span>
|
||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? ((trademarkPanelRef?.taskProgress?.product?.total || 0) - (trademarkPanelRef?.taskProgress?.product?.completed || 0)) : '-' }}</span>
|
||||
<span class="stat-value">{{ (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)) : '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -332,7 +332,6 @@ async function startTrademarkQuery() {
|
||||
|
||||
try {
|
||||
productResult = await pollTask()
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
if (!productResult || (productResult.code !== 200 && productResult.code !== 0)) {
|
||||
throw new Error('获取任务超时或失败,请重试')
|
||||
@@ -360,6 +359,7 @@ async function startTrademarkQuery() {
|
||||
|
||||
if (filteredAsins.length === 0) {
|
||||
showMessage('第三方筛查结果为空', 'warning')
|
||||
queryStatus.value = 'done'
|
||||
return
|
||||
}
|
||||
|
||||
@@ -396,8 +396,6 @@ async function startTrademarkQuery() {
|
||||
|
||||
// 品牌商标筛查
|
||||
if (needBrandCheck) {
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
// 如果没有执行产品筛查,需要先从Excel提取品牌列表和完整数据
|
||||
if (!needProductCheck) {
|
||||
const extractResult = await markApi.extractBrands(trademarkFile.value)
|
||||
@@ -415,9 +413,7 @@ async function startTrademarkQuery() {
|
||||
trademarkHeaders.value = extractResult.data.headers || []
|
||||
}
|
||||
|
||||
if (brandList.length === 0) {
|
||||
showMessage('没有品牌需要筛查', 'warning')
|
||||
} else {
|
||||
if (brandList.length > 0) {
|
||||
const brandData = taskProgress.value.brand
|
||||
brandData.total = brandList.length
|
||||
brandData.current = 1 // 立即显示初始进度
|
||||
@@ -439,8 +435,6 @@ async function startTrademarkQuery() {
|
||||
const brandResult = await markApi.brandCheck(brandList, brandTaskId.value)
|
||||
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
||||
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
if (brandResult.code === 200 || brandResult.code === 0) {
|
||||
// 完成,显示100%
|
||||
brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total
|
||||
@@ -489,18 +483,17 @@ async function startTrademarkQuery() {
|
||||
}
|
||||
}
|
||||
|
||||
if (trademarkLoading.value) {
|
||||
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()
|
||||
}
|
||||
// 只要流程正常完成,就设置为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()
|
||||
} catch (error: any) {
|
||||
const hasProductData = taskProgress.value.product.total > 0
|
||||
|
||||
@@ -536,6 +529,13 @@ async function startTrademarkQuery() {
|
||||
clearInterval(brandProgressTimer)
|
||||
brandProgressTimer = null
|
||||
}
|
||||
|
||||
// 兜底:如果有数据且状态还是进行中,强制设置为完成(最优先处理)
|
||||
if (trademarkData.value.length > 0 && queryStatus.value === 'inProgress') {
|
||||
queryStatus.value = 'done'
|
||||
emit('updateData', trademarkData.value)
|
||||
}
|
||||
|
||||
trademarkLoading.value = false
|
||||
currentStep.value = 0
|
||||
totalSteps.value = 0
|
||||
|
||||
|
Before Width: | Height: | Size: 533 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 638 B |
|
Before Width: | Height: | Size: 378 B |
|
Before Width: | Height: | Size: 913 B |
|
Before Width: | Height: | Size: 731 B |
|
Before Width: | Height: | Size: 894 B |
|
Before Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 968 B |
|
Before Width: | Height: | Size: 628 B |
|
Before Width: | Height: | Size: 894 B |
|
Before Width: | Height: | Size: 804 B |
|
Before Width: | Height: | Size: 533 B |
|
Before Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,33 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>正在启动...</title>
|
||||
<style>
|
||||
html, body { height: 100%; margin: 0; }
|
||||
body {
|
||||
background: #fff; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
|
||||
background-image: url('./image/splash_screen.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
.box { position: fixed; left: 0; right: 0; bottom: 28px; padding: 0 0; }
|
||||
.progress { position: relative; width: 100vw; height: 6px; background: rgba(0,0,0,0.08); }
|
||||
.bar { position: absolute; left: 0; top: 0; height: 100%; width: 20vw; min-width: 120px; background: linear-gradient(90deg, #67C23A, #409EFF); animation: slide 1s ease-in-out infinite alternate; }
|
||||
@keyframes slide { 0% { left: 0; } 100% { left: calc(100vw - 20vw); } }
|
||||
</style>
|
||||
<link rel="icon" href="icon/icon.png">
|
||||
<link rel="apple-touch-icon" href="icon/icon.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';">
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div class="progress"><div class="bar"></div></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||