feat(trademark): 实现商标筛查功能并优化相关配置
- 新增商标筛查进度展示界面与交互逻辑 - 实现产品、品牌及平台跟卖许可的分项任务进度追踪 - 添加商标数据导出与任务重试、取消功能 - 调整Redis连接池配置以提升并发性能 - 禁用ChromeDriver预加载,改为按需启动以节省资源- 支持品牌商标远程筛查接口调用与结果解析 - 增加Hutool工具库依赖用于简化IO与Excel处理- 更新USPTO商标查询脚本实现自动化检测 - 修改Ruoyi后台Redis依赖版本并添加集群心跳配置- 切换本地开发环境API地址指向内网测试服务器
This commit is contained in:
@@ -3,6 +3,7 @@ import { ref, inject, defineAsyncComponent } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
import { getUsernameFromToken } from '../../utils/token'
|
||||
import { markApi } from '../../api/mark'
|
||||
const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue'))
|
||||
|
||||
const refreshVipStatus = inject<() => Promise<boolean>>('refreshVipStatus')
|
||||
@@ -17,12 +18,21 @@ const emit = defineEmits<{
|
||||
|
||||
const trademarkData = ref<any[]>([])
|
||||
const trademarkFileName = ref('')
|
||||
const trademarkFile = ref<File | null>(null)
|
||||
const trademarkUpload = ref<HTMLInputElement | null>(null)
|
||||
const trademarkLoading = ref(false)
|
||||
const trademarkProgress = ref(0)
|
||||
const exportLoading = ref(false)
|
||||
const currentStep = ref(0)
|
||||
const totalSteps = ref(0)
|
||||
let brandProgressTimer: any = null
|
||||
|
||||
// 三个任务的进度数据
|
||||
const taskProgress = ref({
|
||||
product: { total: 0, current: 0, completed: 0, label: '产品商标筛查', desc: '筛查未注册商标或TM标的商品' },
|
||||
brand: { total: 0, current: 0, completed: 0, label: '品牌商标筛查', desc: '筛查未注册商标的品牌' },
|
||||
platform: { total: 0, current: 0, completed: 0, label: '跟卖许可筛查', desc: '筛查亚马逊许可跟卖的商品' }
|
||||
})
|
||||
|
||||
// 查询状态: idle-待开始, inProgress-进行中, done-已完成, error-错误, networkError-网络错误, cancel-已取消
|
||||
const queryStatus = ref<'idle' | 'inProgress' | 'done' | 'error' | 'networkError' | 'cancel'>('idle')
|
||||
@@ -39,14 +49,14 @@ const statusConfig = {
|
||||
desc: '请耐心等待,正在为您筛查商标信息'
|
||||
},
|
||||
done: {
|
||||
icon: '/icon/done.png',
|
||||
icon: '/icon/done1.png',
|
||||
title: '筛查完成',
|
||||
desc: '已成功完成所有商标筛查任务'
|
||||
},
|
||||
error: {
|
||||
icon: '/icon/error.png',
|
||||
title: '查询失败',
|
||||
desc: '查询过程中出现错误,请重试'
|
||||
title: '筛查失败',
|
||||
desc: '筛查过程中出现错误,请重新尝试'
|
||||
},
|
||||
networkError: {
|
||||
icon: '/icon/networkErrors.png',
|
||||
@@ -61,19 +71,15 @@ const statusConfig = {
|
||||
}
|
||||
|
||||
// 选择的账号(模拟数据)
|
||||
const selectedAccount = ref('chensri3.com')
|
||||
const selectedAccount = ref(1)
|
||||
const accountOptions = [
|
||||
{ label: 'chensri3.com', value: 'chensri3.com', status: '美国' },
|
||||
{ label: 'zhangikcpi.com', value: 'zhangikcpi.com', status: '加拿大' },
|
||||
{ label: 'hhhhsoutlook...', value: 'hhhhsoutlook', status: '日本' }
|
||||
{ id: 1, name: 'chensri3.com', username: 'chensri3.com', status: 1 }
|
||||
]
|
||||
|
||||
// 地区选择
|
||||
const region = ref('美国')
|
||||
const regionOptions = [
|
||||
{ label: '美国', value: '美国', flag: '🇺🇸' },
|
||||
{ label: '日本', value: '日本', flag: '🇯🇵' },
|
||||
{ label: '加拿大', value: '加拿大', flag: '🇨🇦' }
|
||||
{ label: '美国', value: '美国', flag: '🇺🇸' }
|
||||
]
|
||||
|
||||
// 查询类型多选
|
||||
@@ -103,6 +109,7 @@ async function handleTrademarkUpload(e: Event) {
|
||||
}
|
||||
|
||||
trademarkFileName.value = file.name
|
||||
trademarkFile.value = file
|
||||
queryStatus.value = 'idle' // 重置状态
|
||||
trademarkData.value = []
|
||||
emit('updateData', [])
|
||||
@@ -111,7 +118,7 @@ async function handleTrademarkUpload(e: Event) {
|
||||
}
|
||||
|
||||
async function startTrademarkQuery() {
|
||||
if (!trademarkFileName.value) {
|
||||
if (!trademarkFileName.value || !trademarkFile.value) {
|
||||
showMessage('请先导入商标列表', 'warning')
|
||||
return
|
||||
}
|
||||
@@ -129,30 +136,231 @@ async function startTrademarkQuery() {
|
||||
trademarkData.value = []
|
||||
queryStatus.value = 'inProgress'
|
||||
|
||||
const mockData = [
|
||||
{ name: 'SONY', status: '已注册', class: '9类-电子产品', owner: 'Sony Corporation', expireDate: '2028-12-31', similarity: 0 },
|
||||
{ name: 'SUMSUNG', status: '近似风险', class: '9类-电子产品', owner: '待查询', expireDate: '-', similarity: 85 },
|
||||
{ name: 'NIKE', status: '已注册', class: '25类-服装鞋帽', owner: 'Nike, Inc.', expireDate: '2029-06-15', similarity: 0 },
|
||||
{ name: 'ADIBAS', status: '近似风险', class: '25类-服装鞋帽', owner: '待查询', expireDate: '-', similarity: 92 },
|
||||
{ name: 'MyBrand', status: '可注册', class: '未查询', owner: '-', expireDate: '-', similarity: 0 }
|
||||
]
|
||||
// 完全重置任务进度(包括total)
|
||||
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
|
||||
|
||||
totalSteps.value = mockData.length
|
||||
// 通知父组件更新数据
|
||||
emit('updateData', [])
|
||||
|
||||
try {
|
||||
for (let i = 0; i < mockData.length; i++) {
|
||||
if (!trademarkLoading.value) break // 被取消
|
||||
let productResult: any = null
|
||||
let brandList: string[] = []
|
||||
|
||||
// 判断是否需要执行产品商标筛查
|
||||
const needProductCheck = queryTypes.value.includes('product')
|
||||
const needBrandCheck = queryTypes.value.includes('brand')
|
||||
|
||||
if (needProductCheck) {
|
||||
// 步骤1: 产品商标筛查 - 调用新建任务接口
|
||||
showMessage('正在上传文件...', 'info')
|
||||
const createResult = await markApi.newTask(trademarkFile.value)
|
||||
|
||||
currentStep.value = i + 1
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
trademarkData.value.push(mockData[i])
|
||||
trademarkProgress.value = Math.round(((i + 1) / mockData.length) * 100)
|
||||
if (createResult.code !== 200 && createResult.code !== 0) {
|
||||
throw new Error(createResult.msg || '创建任务失败')
|
||||
}
|
||||
|
||||
showMessage('文件上传成功,正在处理...', 'success')
|
||||
|
||||
// 步骤2: 轮询检查任务状态,最多等待60秒
|
||||
const taskData = taskProgress.value.product
|
||||
const maxWaitTime = 60000 // 最多等60秒
|
||||
const pollInterval = 3000 // 每3秒检查一次
|
||||
const startTime = Date.now()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 maxWaitTime = 60000
|
||||
const pollInterval = 3000
|
||||
const startTime = Date.now()
|
||||
|
||||
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) {
|
||||
if (taskResult.data.original?.download_url) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('等待任务处理中...', err)
|
||||
}
|
||||
}
|
||||
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
if (!taskResult || (taskResult.code !== 200 && taskResult.code !== 0)) {
|
||||
throw new Error('获取任务超时或失败,请重试')
|
||||
}
|
||||
|
||||
if (!taskResult.data.original?.download_url) {
|
||||
throw new Error('任务处理超时,请稍后重试')
|
||||
}
|
||||
|
||||
// 从结果中提取品牌列表(包括TM和未注册的品牌)
|
||||
brandList = taskResult.data.filtered
|
||||
.map((item: any) => item['品牌'])
|
||||
.filter((brand: string) => brand && brand.trim())
|
||||
|
||||
showMessage(`品牌列表提取成功,共 ${brandList.length} 个品牌`, 'success')
|
||||
}
|
||||
|
||||
if (brandList.length === 0) {
|
||||
showMessage('没有品牌需要筛查', 'warning')
|
||||
} else {
|
||||
const brandData = taskProgress.value.brand
|
||||
brandData.total = brandList.length
|
||||
brandData.current = 0
|
||||
brandData.completed = 0
|
||||
|
||||
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)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
// 调用品牌筛查接口(浏览器内并发,速度快)
|
||||
const brandResult = await markApi.brandCheck(brandList)
|
||||
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
||||
|
||||
if (!trademarkLoading.value) return
|
||||
|
||||
if (brandResult.code === 200 || brandResult.code === 0) {
|
||||
// 完成,显示100%
|
||||
brandData.current = brandData.total
|
||||
brandData.completed = brandResult.data.filtered
|
||||
|
||||
// 将品牌筛查结果追加到展示数据中
|
||||
const brandItems = brandResult.data.data.map((item: any) => ({
|
||||
name: item.brand || '',
|
||||
status: item.status || '未注册',
|
||||
class: '',
|
||||
owner: '',
|
||||
expireDate: '',
|
||||
similarity: 0,
|
||||
isBrand: true // 标记为品牌数据
|
||||
}))
|
||||
|
||||
trademarkData.value = [...trademarkData.value, ...brandItems]
|
||||
|
||||
showMessage(`品牌筛查完成,共查询 ${brandData.total} 个品牌,筛查出 ${brandData.completed} 个未注册品牌`, 'success')
|
||||
} else {
|
||||
throw new Error(brandResult.msg || '品牌筛查失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trademarkLoading.value) {
|
||||
queryStatus.value = 'done'
|
||||
emit('updateData', trademarkData.value)
|
||||
showMessage('商标筛查完成', 'success')
|
||||
|
||||
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}`
|
||||
}
|
||||
showMessage(summaryMsg, 'success')
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.message && error.message.includes('网络')) {
|
||||
@@ -163,7 +371,16 @@ async function startTrademarkQuery() {
|
||||
errorMessage.value = error.message || '查询失败'
|
||||
}
|
||||
showMessage(errorMessage.value, 'error')
|
||||
|
||||
// 失败时清空数据,不显示侧边栏
|
||||
trademarkData.value = []
|
||||
emit('updateData', [])
|
||||
} finally {
|
||||
// 清除定时器
|
||||
if (brandProgressTimer) {
|
||||
clearInterval(brandProgressTimer)
|
||||
brandProgressTimer = null
|
||||
}
|
||||
trademarkLoading.value = false
|
||||
currentStep.value = 0
|
||||
totalSteps.value = 0
|
||||
@@ -171,6 +388,11 @@ async function startTrademarkQuery() {
|
||||
}
|
||||
|
||||
function stopTrademarkQuery() {
|
||||
// 清除进度动画定时器
|
||||
if (brandProgressTimer) {
|
||||
clearInterval(brandProgressTimer)
|
||||
brandProgressTimer = null
|
||||
}
|
||||
trademarkLoading.value = false
|
||||
queryStatus.value = 'cancel'
|
||||
currentStep.value = 0
|
||||
@@ -239,12 +461,28 @@ function downloadTrademarkTemplate() {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
function resetToIdle() {
|
||||
queryStatus.value = 'idle'
|
||||
trademarkData.value = []
|
||||
trademarkFileName.value = ''
|
||||
trademarkFile.value = null
|
||||
taskProgress.value.product.current = 0
|
||||
taskProgress.value.product.completed = 0
|
||||
taskProgress.value.brand.current = 0
|
||||
taskProgress.value.brand.completed = 0
|
||||
taskProgress.value.platform.current = 0
|
||||
taskProgress.value.platform.completed = 0
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
trademarkLoading,
|
||||
trademarkProgress,
|
||||
trademarkData,
|
||||
queryStatus,
|
||||
statusConfig
|
||||
statusConfig,
|
||||
taskProgress,
|
||||
resetToIdle,
|
||||
stopTrademarkQuery
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -278,19 +516,26 @@ defineExpose({
|
||||
<div class="step-header"><div class="title">选择亚马逊商家账号</div></div>
|
||||
<div class="desc">请确保账号地区与专利地区一致,不一致可能导致结果不准确或失败。</div>
|
||||
|
||||
<el-select v-model="selectedAccount" placeholder="选择账号" size="small" style="width: 100%">
|
||||
<el-option v-for="opt in accountOptions" :key="opt.value" :label="opt.label" :value="opt.value">
|
||||
<div class="account-option">
|
||||
<span>{{ opt.label }}</span>
|
||||
<span class="account-status">{{ opt.status }}</span>
|
||||
<span class="account-check">✓</span>
|
||||
<el-scrollbar :class="['account-list', { 'scroll-limit': accountOptions.length > 3 }]">
|
||||
<div>
|
||||
<div
|
||||
v-for="acc in accountOptions"
|
||||
:key="acc.id"
|
||||
:class="['acct-item', 'disabled', { selected: selectedAccount === acc.id }]"
|
||||
>
|
||||
<span class="acct-row">
|
||||
<span :class="['status-dot', acc.status === 1 ? 'on' : 'off']"></span>
|
||||
<img class="avatar" src="/image/user.png" alt="avatar" />
|
||||
<span class="acct-text">{{ acc.name || acc.username }}</span>
|
||||
<span v-if="selectedAccount === acc.id" class="acct-check">✔️</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<div class="step-actions btn-row">
|
||||
<el-button size="small" class="w50">添加账号</el-button>
|
||||
<el-button size="small" class="w50 btn-blue">账号管理</el-button>
|
||||
<el-button size="small" class="w50" disabled>添加账号</el-button>
|
||||
<el-button size="small" class="w50 btn-blue" disabled>账号管理</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -301,7 +546,7 @@ defineExpose({
|
||||
<div class="step-card">
|
||||
<div class="step-header"><div class="title">选择产品品牌地区</div></div>
|
||||
<div class="desc">暂时仅支持美国品牌商标筛查,后续将开放更多地区,敬请期待。</div>
|
||||
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%">
|
||||
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%" disabled>
|
||||
<el-option v-for="opt in regionOptions" :key="opt.value" :label="opt.label" :value="opt.value">
|
||||
<span style="margin-right:6px">{{ opt.flag }}</span>{{ opt.label }}
|
||||
</el-option>
|
||||
@@ -337,7 +582,7 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="['query-card', { active: queryTypes.includes('platform') }]" @click="toggleQueryType('platform')">
|
||||
<div :class="['query-card', 'query-disabled', { active: queryTypes.includes('platform') }]">
|
||||
<div class="query-check">
|
||||
<div class="check-icon" v-if="queryTypes.includes('platform')">✓</div>
|
||||
</div>
|
||||
@@ -355,9 +600,12 @@ defineExpose({
|
||||
<div class="bottom-action">
|
||||
<div class="action-header">
|
||||
<span class="step-indicator">{{ trademarkLoading ? currentStep : 1 }}/4</span>
|
||||
<el-button class="start-btn" type="primary" :disabled="!trademarkFileName || trademarkLoading" :loading="trademarkLoading" @click="startTrademarkQuery">
|
||||
<el-button v-if="!trademarkLoading" class="start-btn" type="primary" :disabled="!trademarkFileName || queryTypes.length === 0" @click="startTrademarkQuery">
|
||||
开始筛查
|
||||
</el-button>
|
||||
<el-button v-else class="start-btn stop-btn" @click="stopTrademarkQuery">
|
||||
停止筛查
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -391,7 +639,7 @@ defineExpose({
|
||||
.title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
||||
.desc { font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5; }
|
||||
.links { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
||||
.link { color: #409EFF; cursor: pointer; font-size: 12px; }
|
||||
.link { color: #909399; cursor: pointer; font-size: 12px; }
|
||||
|
||||
.file-chip {
|
||||
display: flex;
|
||||
@@ -402,19 +650,22 @@ defineExpose({
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-top: 6px;
|
||||
margin-top: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
.file-chip .dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #409EFF;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.file-chip .name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
@@ -459,6 +710,11 @@ defineExpose({
|
||||
border-color: #1677FF;
|
||||
background: #f0f9ff;
|
||||
}
|
||||
.query-card.query-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.query-check {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -468,15 +724,15 @@ defineExpose({
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
background: #ffffff;
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.query-card.active .query-check {
|
||||
border-color: #1677FF;
|
||||
background: #1677FF;
|
||||
background: transparent;
|
||||
}
|
||||
.check-icon {
|
||||
color: #ffffff;
|
||||
color: #1677FF;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
@@ -534,32 +790,90 @@ defineExpose({
|
||||
}
|
||||
.start-btn {
|
||||
flex: 1;
|
||||
height: 38px;
|
||||
height: 32px;
|
||||
padding: 0px 16px;
|
||||
background: #1677FF;
|
||||
border-color: #1677FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.stop-btn {
|
||||
background: #f56c6c;
|
||||
border-color: #f56c6c;
|
||||
}
|
||||
.stop-btn:hover {
|
||||
background: #f78989;
|
||||
border-color: #f78989;
|
||||
}
|
||||
|
||||
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
|
||||
.btn-blue:disabled { background: #a6c8ff; border-color: #a6c8ff; color: #fff; }
|
||||
|
||||
.account-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
.account-list {
|
||||
height: auto;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
.account-status {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
.scroll-limit { max-height: 140px; }
|
||||
.avatar { width: 18px; height: 18px; border-radius: 50%; }
|
||||
.acct-row {
|
||||
display: grid;
|
||||
grid-template-columns: 6px 18px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
.account-check {
|
||||
color: #1677FF;
|
||||
font-size: 14px;
|
||||
.acct-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
}
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
.status-dot.on { background: #22c55e; }
|
||||
.status-dot.off { background: #f87171; }
|
||||
.acct-item {
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.acct-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.acct-item.selected {
|
||||
background: #eef5ff;
|
||||
box-shadow: inset 0 0 0 1px #d6e4ff;
|
||||
}
|
||||
.acct-item.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.acct-check {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: #111;
|
||||
font-size: 12px;
|
||||
}
|
||||
.account-list :deep(.el-scrollbar__view) {
|
||||
padding: 0;
|
||||
}
|
||||
.account-list::-webkit-scrollbar { width: 0; height: 0; }
|
||||
|
||||
.trademark-panel :deep(.el-select),
|
||||
.trademark-panel :deep(.el-input__wrapper) {
|
||||
|
||||
Reference in New Issue
Block a user