feat(trademark):优化商标查询功能和Excel解析逻辑
- 重构品牌商标缓存服务,移除冗余的日志记录和存在检查- 简化Excel解析工具类,提取公共方法并优化列索引查找逻辑 - 增强Electron客户端开发模式下的后端启动控制能力 - 改进商标筛查面板的用户体验和数据处理流程-优化商标查询工具类,提高查询准确性和稳定性 - 调整商标控制器接口参数校验逻辑和资源清理机制 - 更新USPTO API测试用例以支持Spring容器环境运行
This commit is contained in:
@@ -434,6 +434,19 @@ app.whenReady().then(() => {
|
||||
|
||||
createWindow();
|
||||
createTray(mainWindow);
|
||||
|
||||
// 开发模式快捷键
|
||||
if (isDev && mainWindow) {
|
||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||
if (input.control && input.shift && input.key.toLowerCase() === 'd') {
|
||||
console.log('[开发模式] 手动跳过后端启动');
|
||||
openAppIfNotOpened();
|
||||
} else if (input.control && input.shift && input.key.toLowerCase() === 's') {
|
||||
console.log('[开发模式] 手动启动后端服务');
|
||||
startSpringBoot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 只有在不需要最小化启动时才显示 splash 窗口
|
||||
if (!shouldMinimize) {
|
||||
@@ -476,9 +489,22 @@ app.whenReady().then(() => {
|
||||
}
|
||||
|
||||
console.log('[启动流程] 准备启动 Spring Boot...');
|
||||
setTimeout(() => {
|
||||
startSpringBoot();
|
||||
}, 200);
|
||||
|
||||
// 开发模式:添加快捷键跳过后端启动
|
||||
if (isDev) {
|
||||
console.log('[开发模式] 按 Ctrl+Shift+D 跳过后端启动,直接进入应用');
|
||||
console.log('[开发模式] 按 Ctrl+Shift+S 手动启动后端服务');
|
||||
|
||||
// 5秒后自动跳过,避免卡死
|
||||
setTimeout(() => {
|
||||
console.log('[开发模式] 自动跳过后端启动,直接进入应用');
|
||||
openAppIfNotOpened();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// setTimeout(() => {
|
||||
// startSpringBoot();
|
||||
// }, 200);
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
@@ -847,6 +873,26 @@ ipcMain.handle('set-launch-config', (event, launchConfig: { autoLaunch: boolean;
|
||||
// 刷新页面
|
||||
ipcMain.handle('reload', () => mainWindow?.webContents.reload());
|
||||
|
||||
// 开发模式:跳过后端启动
|
||||
ipcMain.handle('dev-skip-backend', () => {
|
||||
if (isDev) {
|
||||
console.log('[开发模式] 前端请求跳过后端启动');
|
||||
openAppIfNotOpened();
|
||||
return { success: true };
|
||||
}
|
||||
return { success: false, error: '仅开发模式可用' };
|
||||
});
|
||||
|
||||
// 开发模式:手动启动后端
|
||||
ipcMain.handle('dev-start-backend', () => {
|
||||
if (isDev) {
|
||||
console.log('[开发模式] 前端请求启动后端');
|
||||
startSpringBoot();
|
||||
return { success: true };
|
||||
}
|
||||
return { success: false, error: '仅开发模式可用' };
|
||||
});
|
||||
|
||||
// 窗口控制 API
|
||||
ipcMain.handle('window-minimize', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
|
||||
@@ -116,18 +116,11 @@ function showMessage(message: string, type: 'success' | 'warning' | 'error' | 'i
|
||||
// 拖拽上传
|
||||
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('品牌')
|
||||
}
|
||||
const requiredHeaders = []
|
||||
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) {
|
||||
@@ -160,15 +153,11 @@ function removeTrademarkFile() {
|
||||
trademarkFileName.value = ''
|
||||
trademarkFile.value = null
|
||||
uploadLoading.value = false
|
||||
if (trademarkUpload.value) {
|
||||
trademarkUpload.value.value = ''
|
||||
}
|
||||
if (trademarkUpload.value) trademarkUpload.value.value = ''
|
||||
}
|
||||
|
||||
// 保存会话到后端
|
||||
async function saveSession() {
|
||||
if (!trademarkData.value.length) return
|
||||
|
||||
try {
|
||||
const sessionData = {
|
||||
fileName: trademarkFileName.value,
|
||||
@@ -178,7 +167,6 @@ async function saveSession() {
|
||||
taskProgress: taskProgress.value,
|
||||
queryStatus: queryStatus.value
|
||||
}
|
||||
|
||||
const result = await markApi.saveSession(sessionData)
|
||||
if (result.code === 200 || result.code === 0) {
|
||||
const username = getUsernameFromToken()
|
||||
@@ -258,13 +246,10 @@ async function handleTrademarkUpload(e: Event) {
|
||||
const input = e.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
const ok = /\.xlsx?$/.test(file.name)
|
||||
if (!ok) {
|
||||
if (!/\.xlsx?$/.test(file.name)) {
|
||||
showMessage('仅支持 .xlsx/.xls 文件', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
await processTrademarkFile(file)
|
||||
input.value = ''
|
||||
}
|
||||
@@ -274,9 +259,7 @@ async function startTrademarkQuery() {
|
||||
showMessage('请先导入商标列表', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
if (refreshVipStatus) await refreshVipStatus()
|
||||
|
||||
if (!props.isVip) {
|
||||
if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType
|
||||
showTrialExpiredDialog.value = true
|
||||
@@ -286,6 +269,7 @@ async function startTrademarkQuery() {
|
||||
const needProductCheck = queryTypes.value.includes('product')
|
||||
const needBrandCheck = queryTypes.value.includes('brand')
|
||||
|
||||
// 立即显示加载状态,防止重复点击
|
||||
trademarkLoading.value = true
|
||||
trademarkProgress.value = 0
|
||||
trademarkData.value = []
|
||||
@@ -293,6 +277,9 @@ async function startTrademarkQuery() {
|
||||
trademarkHeaders.value = []
|
||||
queryStatus.value = 'inProgress'
|
||||
|
||||
// 立即显示"正在准备..."状态
|
||||
showMessage('正在准备筛查任务...', 'info')
|
||||
|
||||
// 重置任务进度
|
||||
taskProgress.value.product.total = 0
|
||||
taskProgress.value.product.current = 0
|
||||
@@ -476,11 +463,16 @@ async function startTrademarkQuery() {
|
||||
if (brandList.length > 0) {
|
||||
const brandData = taskProgress.value.brand
|
||||
brandData.total = brandList.length
|
||||
brandData.current = 1 // 立即显示初始进度
|
||||
brandData.current = 0
|
||||
brandData.completed = 0
|
||||
|
||||
// 生成任务ID并轮询真实进度
|
||||
// 生成任务ID并立即开始轮询
|
||||
brandTaskId.value = `task_${Date.now()}`
|
||||
|
||||
// 立即显示开始状态
|
||||
showMessage(`开始品牌商标筛查,共${brandList.length}个品牌`, 'success')
|
||||
|
||||
// 立即开始进度轮询
|
||||
brandProgressTimer = setInterval(async () => {
|
||||
try {
|
||||
const res = await markApi.getBrandCheckProgress(brandTaskId.value)
|
||||
@@ -490,72 +482,69 @@ async function startTrademarkQuery() {
|
||||
} catch (e) {
|
||||
// 忽略进度查询错误
|
||||
}
|
||||
}, 1000)
|
||||
}, 500)
|
||||
|
||||
const brandResult = await markApi.brandCheck(brandList, brandTaskId.value)
|
||||
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
||||
|
||||
if (brandResult.code === 200 || brandResult.code === 0) {
|
||||
// 完成,显示100%
|
||||
if ( brandResult.code === 0) {
|
||||
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)
|
||||
|
||||
if (unregisteredBrands.length > 0) {
|
||||
// 从原始Excel中过滤出包含这些品牌的完整行
|
||||
const filterResult = await markApi.filterByBrands(trademarkFile.value, unregisteredBrands)
|
||||
|
||||
if (filterResult.code === 200 || filterResult.code === 0) {
|
||||
// 保存完整数据(用于导出,使用原始表头)
|
||||
trademarkFullData.value = filterResult.data.filteredRows
|
||||
trademarkHeaders.value = filterResult.data.headers || []
|
||||
|
||||
// 更新统计:显示过滤出的实际行数(而不是品牌数)
|
||||
brandData.completed = filterResult.data.filteredRows.length
|
||||
isBrandTaskRealData.value = true
|
||||
|
||||
// 将品牌筛查结果作为展示数据
|
||||
const brandItems = filterResult.data.filteredRows.map((row: any) => ({
|
||||
name: row['品牌'] || '',
|
||||
status: '未注册',
|
||||
class: '',
|
||||
owner: '',
|
||||
expireDate: row['注册时间'] || '',
|
||||
similarity: 0,
|
||||
asin: row['ASIN'] || '',
|
||||
productImage: row['商品主图'] || '',
|
||||
isBrand: true // 标记为品牌数据
|
||||
}))
|
||||
|
||||
// 如果有产品筛查,也替换展示数据(只显示品牌筛查结果)
|
||||
if (needProductCheck) {
|
||||
trademarkData.value = brandItems
|
||||
} else {
|
||||
trademarkData.value = [...trademarkData.value, ...brandItems]
|
||||
}
|
||||
}
|
||||
}
|
||||
await processBrandResult(brandResult)
|
||||
} else {
|
||||
throw new Error(brandResult.msg || '品牌筛查失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 只要流程正常完成,就设置为done状态(不再依赖trademarkLoading)
|
||||
queryStatus.value = 'done'
|
||||
emit('updateData', trademarkData.value)
|
||||
// 处理品牌查询结果的函数
|
||||
async function processBrandResult(brandResult: any) {
|
||||
// 提取未注册品牌列表
|
||||
const unregisteredBrands = brandResult.data.data.map((item: any) => item.brand).filter(Boolean)
|
||||
|
||||
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()
|
||||
if (unregisteredBrands.length > 0) {
|
||||
// 从原始Excel中过滤出包含这些品牌的完整行
|
||||
const filterResult = await markApi.filterByBrands(trademarkFile.value, unregisteredBrands)
|
||||
|
||||
// 保存完整数据(用于导出,使用原始表头)
|
||||
trademarkFullData.value = filterResult.data.filteredRows
|
||||
trademarkHeaders.value = filterResult.data.headers || []
|
||||
|
||||
// 更新统计:显示过滤出的实际行数(而不是品牌数)
|
||||
const brandData = taskProgress.value.brand
|
||||
brandData.completed = filterResult.data.filteredRows.length
|
||||
isBrandTaskRealData.value = true
|
||||
|
||||
// 将品牌筛查结果作为展示数据
|
||||
const brandItems = filterResult.data.filteredRows.map((row: any) => ({
|
||||
name: row['品牌'] || '',
|
||||
status: '未注册',
|
||||
class: '',
|
||||
owner: '',
|
||||
expireDate: row['注册时间'] || '',
|
||||
similarity: 0,
|
||||
asin: row['ASIN'] || '',
|
||||
productImage: row['商品主图'] || '',
|
||||
isBrand: true // 标记为品牌数据
|
||||
}))
|
||||
|
||||
// 更新展示数据
|
||||
trademarkData.value = [...trademarkData.value, ...brandItems]
|
||||
}
|
||||
}
|
||||
|
||||
// 只要流程正常完成,就设置为done状态
|
||||
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 = isProductTaskRealData.value && taskProgress.value.product.total > 0
|
||||
const hasBrandData = isBrandTaskRealData.value && taskProgress.value.brand.total > 0
|
||||
@@ -715,27 +704,15 @@ function resetToIdle() {
|
||||
trademarkHeaders.value = []
|
||||
trademarkFileName.value = ''
|
||||
trademarkFile.value = null
|
||||
taskProgress.value.product.total = 0
|
||||
taskProgress.value.product.current = 0
|
||||
taskProgress.value.product.completed = 0
|
||||
taskProgress.value.brand.total = 0
|
||||
taskProgress.value.brand.current = 0
|
||||
taskProgress.value.brand.completed = 0
|
||||
taskProgress.value.platform.total = 0
|
||||
taskProgress.value.platform.current = 0
|
||||
taskProgress.value.platform.completed = 0
|
||||
|
||||
// 重置真实数据标记
|
||||
Object.assign(taskProgress.value.product, { total: 0, current: 0, completed: 0 })
|
||||
Object.assign(taskProgress.value.brand, { total: 0, current: 0, completed: 0 })
|
||||
Object.assign(taskProgress.value.platform, { total: 0, current: 0, completed: 0 })
|
||||
isProductTaskRealData.value = false
|
||||
isBrandTaskRealData.value = false
|
||||
|
||||
// 清空localStorage中的会话数据
|
||||
try {
|
||||
const username = getUsernameFromToken()
|
||||
localStorage.removeItem(`trademark_session_${username}`)
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
||||
Reference in New Issue
Block a user