feat(trademark): 支持商标筛查任务取消状态及优化错误处理- 新增商标筛查取消状态的UI展示和处理逻辑
-优化错误提示信息,区分网络错误、超时和风控场景 - 改进任务进度计算逻辑,支持更准确的完成状态判断 - 调整品牌提取逻辑,从Excel中直接读取品牌列数据 - 增强后端403错误检测和代理自动切换机制 - 更新前端组件样式和交互逻辑以匹配新状态 -修复部分条件判断逻辑以提升稳定性- 调整文件上传大小限制至50MB以支持更大文件- 优化Excel解析工具类,支持自动识别表头行位置
This commit is contained in:
@@ -131,12 +131,15 @@ async function startTrademarkQuery() {
|
||||
return
|
||||
}
|
||||
|
||||
const needProductCheck = queryTypes.value.includes('product')
|
||||
const needBrandCheck = queryTypes.value.includes('brand')
|
||||
|
||||
trademarkLoading.value = true
|
||||
trademarkProgress.value = 0
|
||||
trademarkData.value = []
|
||||
queryStatus.value = 'inProgress'
|
||||
|
||||
// 完全重置任务进度(包括total)
|
||||
// 重置任务进度
|
||||
taskProgress.value.product.total = 0
|
||||
taskProgress.value.product.current = 0
|
||||
taskProgress.value.product.completed = 0
|
||||
@@ -154,10 +157,6 @@ async function startTrademarkQuery() {
|
||||
let productResult: any = null
|
||||
let brandList: string[] = []
|
||||
|
||||
// 判断是否需要执行产品商标筛查
|
||||
const needProductCheck = queryTypes.value.includes('product')
|
||||
const needBrandCheck = queryTypes.value.includes('brand')
|
||||
|
||||
if (needProductCheck) {
|
||||
// 步骤1: 产品商标筛查 - 调用新建任务接口
|
||||
showMessage('正在上传文件...', 'info')
|
||||
@@ -169,132 +168,113 @@ async function startTrademarkQuery() {
|
||||
|
||||
showMessage('文件上传成功,正在处理...', 'success')
|
||||
|
||||
// 步骤2: 轮询检查任务状态,最多等待60秒
|
||||
const taskData = taskProgress.value.product
|
||||
const maxWaitTime = 60000 // 最多等60秒
|
||||
const pollInterval = 3000 // 每3秒检查一次
|
||||
const startTime = Date.now()
|
||||
taskData.total = 100 // 设置临时总数以显示进度动画
|
||||
taskData.current = 5 // 立即显示初始进度
|
||||
|
||||
let taskResult: any = null
|
||||
while (Date.now() - startTime < maxWaitTime) {
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
// 等待一段时间
|
||||
await new Promise(resolve => setTimeout(resolve, pollInterval))
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
// 尝试获取任务结果
|
||||
try {
|
||||
taskResult = await markApi.getTask()
|
||||
|
||||
if (taskResult.code === 200 || taskResult.code === 0) {
|
||||
// 检查是否有 download_url,有则说明任务完成
|
||||
if (taskResult.data.original?.download_url) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// 继续等待
|
||||
console.log('等待任务处理中...', err)
|
||||
// 启动进度动画(5-95%)
|
||||
const progressTimer = setInterval(() => {
|
||||
if (taskData.current < 95) {
|
||||
taskData.current = Math.min(taskData.current + 3, 95)
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
// 步骤3: 检查是否成功获取到结果
|
||||
if (!taskResult || (taskResult.code !== 200 && taskResult.code !== 0)) {
|
||||
throw new Error('获取任务超时或失败,请重试')
|
||||
}
|
||||
|
||||
if (!taskResult.data.original?.download_url) {
|
||||
throw new Error('任务处理超时,请稍后重试')
|
||||
}
|
||||
|
||||
productResult = taskResult
|
||||
|
||||
// 从后端获取真实的统计数据
|
||||
taskData.total = taskResult.data.original?.total || 0
|
||||
taskData.completed = taskResult.data.filtered.length
|
||||
taskData.current = taskData.total
|
||||
|
||||
// 映射后端数据到前端格式
|
||||
trademarkData.value = taskResult.data.filtered.map((item: any) => ({
|
||||
name: item['品牌'] || '',
|
||||
status: item['商标类型'] || '',
|
||||
class: '',
|
||||
owner: '',
|
||||
expireDate: item['注册时间'] || '',
|
||||
similarity: 0,
|
||||
// 保留原始数据供后续使用
|
||||
asin: item['ASIN'],
|
||||
productImage: item['商品主图']
|
||||
}))
|
||||
|
||||
// 如果需要品牌筛查,从产品结果中提取品牌列表
|
||||
if (needBrandCheck) {
|
||||
brandList = taskResult.data.filtered
|
||||
.map((item: any) => item['品牌'])
|
||||
.filter((brand: string) => brand && brand.trim())
|
||||
}
|
||||
|
||||
showMessage(`产品筛查完成,共查询 ${taskData.total} 条,筛查出 ${taskData.completed} 条未注册/TM标`, 'success')
|
||||
}
|
||||
|
||||
// 品牌商标筛查
|
||||
if (needBrandCheck) {
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
// 如果没有执行产品筛查,需要先上传文件获取品牌列表
|
||||
if (!needProductCheck) {
|
||||
showMessage('正在上传文件提取品牌列表...', 'info')
|
||||
|
||||
// 调用新建任务接口获取处理后的数据
|
||||
const createResult = await markApi.newTask(trademarkFile.value)
|
||||
|
||||
if (createResult.code !== 200 && createResult.code !== 0) {
|
||||
throw new Error(createResult.msg || '创建任务失败')
|
||||
}
|
||||
|
||||
// 轮询获取任务结果
|
||||
// 轮询检查任务状态
|
||||
const pollTask = async () => {
|
||||
const maxWaitTime = 60000
|
||||
const pollInterval = 3000
|
||||
const startTime = Date.now()
|
||||
|
||||
let taskResult: any = null
|
||||
while (Date.now() - startTime < maxWaitTime) {
|
||||
if (!trademarkLoading.value) return
|
||||
if (!trademarkLoading.value) {
|
||||
clearInterval(progressTimer)
|
||||
return null
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, pollInterval))
|
||||
if (!trademarkLoading.value) return
|
||||
if (!trademarkLoading.value) {
|
||||
clearInterval(progressTimer)
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
taskResult = await markApi.getTask()
|
||||
|
||||
if (taskResult.code === 200 || taskResult.code === 0) {
|
||||
if (taskResult.data.original?.download_url) {
|
||||
break
|
||||
clearInterval(progressTimer)
|
||||
return taskResult
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('等待任务处理中...', err)
|
||||
// 继续等待
|
||||
}
|
||||
}
|
||||
|
||||
clearInterval(progressTimer)
|
||||
return taskResult
|
||||
}
|
||||
|
||||
try {
|
||||
productResult = await pollTask()
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
if (!taskResult || (taskResult.code !== 200 && taskResult.code !== 0)) {
|
||||
if (!productResult || (productResult.code !== 200 && productResult.code !== 0)) {
|
||||
throw new Error('获取任务超时或失败,请重试')
|
||||
}
|
||||
|
||||
if (!taskResult.data.original?.download_url) {
|
||||
if (!productResult.data.original?.download_url) {
|
||||
throw new Error('任务处理超时,请稍后重试')
|
||||
}
|
||||
|
||||
// 从结果中提取品牌列表(包括TM和未注册的品牌)
|
||||
brandList = taskResult.data.filtered
|
||||
// 设置真实统计数据
|
||||
taskData.total = productResult.data.original?.total || 0
|
||||
taskData.current = taskData.total
|
||||
taskData.completed = productResult.data.filtered.length
|
||||
} finally {
|
||||
clearInterval(progressTimer)
|
||||
}
|
||||
|
||||
// 映射后端数据到前端格式
|
||||
trademarkData.value = productResult.data.filtered.map((item: any) => ({
|
||||
name: item['品牌'] || '',
|
||||
status: item['商标类型'] || '',
|
||||
class: '',
|
||||
owner: '',
|
||||
expireDate: item['注册时间'] || '',
|
||||
similarity: 0,
|
||||
asin: item['ASIN'],
|
||||
productImage: item['商品主图']
|
||||
}))
|
||||
|
||||
// 如果需要品牌筛查,从产品结果中提取品牌列表
|
||||
if (needBrandCheck) {
|
||||
brandList = productResult.data.filtered
|
||||
.map((item: any) => item['品牌'])
|
||||
.filter((brand: string) => brand && brand.trim())
|
||||
}
|
||||
|
||||
showMessage(`产品筛查完成,共 ${taskData.total} 条,筛查出 ${taskData.completed} 条`, 'success')
|
||||
}
|
||||
|
||||
// 品牌商标筛查
|
||||
if (needBrandCheck) {
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
// 如果没有执行产品筛查,需要先从Excel提取品牌列表
|
||||
if (!needProductCheck) {
|
||||
showMessage('正在从Excel提取品牌列表...', 'info')
|
||||
const extractResult = await markApi.extractBrands(trademarkFile.value)
|
||||
|
||||
if (extractResult.code !== 200 && extractResult.code !== 0) {
|
||||
throw new Error(extractResult.msg || '提取品牌列表失败')
|
||||
}
|
||||
|
||||
if (!extractResult.data.brands || extractResult.data.brands.length === 0) {
|
||||
throw new Error('未能从文件中提取到品牌数据,请确保Excel包含"品牌"列')
|
||||
}
|
||||
|
||||
brandList = extractResult.data.brands
|
||||
showMessage(`品牌列表提取成功,共 ${brandList.length} 个品牌`, 'success')
|
||||
}
|
||||
|
||||
@@ -308,15 +288,13 @@ async function startTrademarkQuery() {
|
||||
|
||||
showMessage(`开始品牌商标筛查,共 ${brandList.length} 个品牌...`, 'info')
|
||||
|
||||
// 模拟进度动画(每秒增加20个)
|
||||
const batchSize = 20
|
||||
// 模拟进度动画
|
||||
brandProgressTimer = setInterval(() => {
|
||||
if (brandData.current < brandList.length * 0.95) {
|
||||
brandData.current = Math.min(brandData.current + batchSize, brandList.length * 0.95)
|
||||
brandData.current = Math.min(brandData.current + 20, brandList.length * 0.95)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
// 调用品牌筛查接口(浏览器内并发,速度快)
|
||||
const brandResult = await markApi.brandCheck(brandList)
|
||||
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
||||
|
||||
@@ -352,29 +330,37 @@ async function startTrademarkQuery() {
|
||||
emit('updateData', trademarkData.value)
|
||||
|
||||
let summaryMsg = '筛查完成'
|
||||
if (needProductCheck) {
|
||||
const taskData = taskProgress.value.product
|
||||
summaryMsg += `,产品:${taskData.completed}/${taskData.total}`
|
||||
}
|
||||
if (needBrandCheck && brandList.length > 0) {
|
||||
const brandData = taskProgress.value.brand
|
||||
summaryMsg += `,品牌:${brandData.completed}/${brandData.total}`
|
||||
}
|
||||
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')
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.message && error.message.includes('网络')) {
|
||||
const hasProductData = taskProgress.value.product.total > 0
|
||||
|
||||
// 优化错误信息 - 只显示友好提示
|
||||
let msg = error.message || ''
|
||||
if (msg.includes('网络') || msg.includes('network')) {
|
||||
queryStatus.value = 'networkError'
|
||||
errorMessage.value = '网络连接失败'
|
||||
errorMessage.value = '网络不可用,请检查你的网络或代理设置'
|
||||
} else if (msg.includes('超时') || msg.includes('timeout')) {
|
||||
queryStatus.value = 'error'
|
||||
errorMessage.value = '数据库维护中,请稍后重试'
|
||||
} else if (msg.includes('403') || msg.includes('风控')) {
|
||||
queryStatus.value = 'error'
|
||||
errorMessage.value = '网站风控限制,请稍后重试'
|
||||
} else {
|
||||
queryStatus.value = 'error'
|
||||
errorMessage.value = error.message || '查询失败'
|
||||
errorMessage.value = '数据库维护中,请稍后重试'
|
||||
}
|
||||
|
||||
// 仅在第1步失败时清空数据
|
||||
if (!hasProductData) {
|
||||
trademarkData.value = []
|
||||
emit('updateData', [])
|
||||
} else {
|
||||
emit('updateData', trademarkData.value)
|
||||
}
|
||||
showMessage(errorMessage.value, 'error')
|
||||
|
||||
// 失败时清空数据,不显示侧边栏
|
||||
trademarkData.value = []
|
||||
emit('updateData', [])
|
||||
} finally {
|
||||
// 清除定时器
|
||||
if (brandProgressTimer) {
|
||||
@@ -466,10 +452,13 @@ function resetToIdle() {
|
||||
trademarkData.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
|
||||
}
|
||||
@@ -481,8 +470,10 @@ defineExpose({
|
||||
queryStatus,
|
||||
statusConfig,
|
||||
taskProgress,
|
||||
errorMessage,
|
||||
resetToIdle,
|
||||
stopTrademarkQuery
|
||||
stopTrademarkQuery,
|
||||
startTrademarkQuery
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -493,8 +484,8 @@ defineExpose({
|
||||
<div class="flow-item">
|
||||
<div class="step-index">1</div>
|
||||
<div class="step-card">
|
||||
<div class="step-header"><div class="title">导入“卖家精灵选品表格”</div></div>
|
||||
<div class="desc">在卖家精灵导出文档时,必须要勾选“导出主图”,具体操作请<span class="link" @click.prevent="viewTrademarkExample">点击查看</span></div>
|
||||
<div class="step-header"><div class="title">导入Excel表格</div></div>
|
||||
<div class="desc">产品筛查:需导入卖家精灵选品表格,并勾选"导出主图";品牌筛查:Excel需包含"品牌"列</div>
|
||||
<div class="dropzone" @click="openTrademarkUpload">
|
||||
<div class="dz-icon">📤</div>
|
||||
<div class="dz-text">点击或将文件拖拽到这里上传</div>
|
||||
@@ -798,6 +789,12 @@ defineExpose({
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.start-btn:disabled {
|
||||
background: #d9d9d9;
|
||||
border-color: #d9d9d9;
|
||||
color: #ffffff;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.stop-btn {
|
||||
background: #f56c6c;
|
||||
border-color: #f56c6c;
|
||||
|
||||
Reference in New Issue
Block a user