feat(genmai): 集成跟卖精灵账号管理系统跟
- 新增卖精灵账号管理功能,支持多账号切换 - 实现账号增删改查接口与前端交互逻辑 -优化打开跟卖精灵流程,增加账号选择界面 - 添加账号权限限制与订阅升级提醒 - 完善后端账号实体类及数据库映射 - 更新系统控制器以支持指定账号启动功能- 调整HTTP请求路径适配新工具模块路由- 升级客户端版本至2.5.3并优化代码结构
This commit is contained in:
@@ -3,11 +3,13 @@ import { ref, computed, onMounted, defineAsyncComponent, inject } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { amazonApi } from '../../api/amazon'
|
||||
import { systemApi } from '../../api/system'
|
||||
import { genmaiApi, type GenmaiAccount } from '../../api/genmai'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
import { getUsernameFromToken } from '../../utils/token'
|
||||
import { useFileDrop } from '../../composables/useFileDrop'
|
||||
|
||||
const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue'))
|
||||
const AccountManager = defineAsyncComponent(() => import('../common/AccountManager.vue'))
|
||||
|
||||
const refreshVipStatus = inject<() => Promise<boolean>>('refreshVipStatus')
|
||||
|
||||
@@ -24,6 +26,9 @@ const progressVisible = ref(false) // 进度条是否显示(完成后仍保留
|
||||
const localProductData = ref<any[]>([]) // 本地产品数据
|
||||
const currentAsin = ref('') // 当前处理的ASIN
|
||||
const genmaiLoading = ref(false) // Genmai Spirit加载状态
|
||||
const genmaiAccounts = ref<GenmaiAccount[]>([]) // 跟卖精灵账号列表
|
||||
const selectedGenmaiAccountId = ref<number | null>(null) // 选中的跟卖精灵账号ID
|
||||
const currentTab = ref<'asin' | 'genmai'>('asin') // 当前选中的tab
|
||||
let abortController: AbortController | null = null // 请求取消控制器
|
||||
|
||||
// 分页配置
|
||||
@@ -35,6 +40,10 @@ const amazonUpload = ref<HTMLInputElement | null>(null)
|
||||
const showTrialExpiredDialog = ref(false)
|
||||
const trialExpiredType = ref<'device' | 'account' | 'both' | 'subscribe'>('account')
|
||||
|
||||
// 账号管理弹框
|
||||
const showAccountManager = ref(false)
|
||||
const accountManagerRef = ref<any>(null)
|
||||
|
||||
const vipStatus = inject<any>('vipStatus')
|
||||
|
||||
// 计算属性 - 当前页数据
|
||||
@@ -257,25 +266,25 @@ function stopFetch() {
|
||||
}
|
||||
|
||||
async function openGenmaiSpirit() {
|
||||
try {
|
||||
await ElMessageBox.confirm('打开跟卖精灵会关闭所有谷歌浏览器进程,是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
genmaiLoading.value = true
|
||||
try {
|
||||
await systemApi.openGenmaiSpirit()
|
||||
showMessage('跟卖精灵已打开', 'success')
|
||||
} catch (error: any) {
|
||||
const errorMsg = error?.msg || error?.message || '打开跟卖精灵失败'
|
||||
showMessage(errorMsg, 'error')
|
||||
} finally {
|
||||
genmaiLoading.value = false
|
||||
}
|
||||
} catch {
|
||||
// 用户取消
|
||||
if (!genmaiAccounts.value.length) {
|
||||
showAccountManager.value = true
|
||||
return
|
||||
}
|
||||
genmaiLoading.value = true
|
||||
try {
|
||||
await systemApi.openGenmaiSpirit(selectedGenmaiAccountId.value)
|
||||
showMessage('跟卖精灵已打开', 'success')
|
||||
} finally {
|
||||
genmaiLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGenmaiAccounts() {
|
||||
try {
|
||||
const res = await genmaiApi.getAccounts(getUsernameFromToken())
|
||||
genmaiAccounts.value = (res as any)?.data ?? []
|
||||
if (genmaiAccounts.value[0]) selectedGenmaiAccountId.value = genmaiAccounts.value[0].id
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
@@ -310,14 +319,12 @@ function downloadAmazonTemplate() {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
// 组件挂载时获取最新数据
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const resp = await amazonApi.getLatestProducts()
|
||||
localProductData.value = resp.data?.products || []
|
||||
} catch {
|
||||
// 静默处理初始化失败
|
||||
}
|
||||
} catch {}
|
||||
await loadGenmaiAccounts()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -329,18 +336,79 @@ onMounted(async () => {
|
||||
<aside class="steps-sidebar">
|
||||
<!-- 顶部标签栏 -->
|
||||
<div class="top-tabs">
|
||||
<div class="tab-item active">
|
||||
<div :class="['tab-item', { active: currentTab === 'asin' }]" @click="currentTab = 'asin'">
|
||||
<span class="tab-icon">📦</span>
|
||||
<span class="tab-text">ASIN查询</span>
|
||||
</div>
|
||||
<div class="tab-item" :class="{ loading: genmaiLoading }" @click="!genmaiLoading && openGenmaiSpirit()">
|
||||
<span class="tab-icon" v-if="!genmaiLoading">🔍</span>
|
||||
<span class="tab-icon spinner-icon" v-else>⟳</span>
|
||||
<span class="tab-text">{{ genmaiLoading ? '启动中...' : '跟卖精灵' }}</span>
|
||||
<div :class="['tab-item', { active: currentTab === 'genmai' }]" @click="currentTab = 'genmai'">
|
||||
<span class="tab-icon">🔍</span>
|
||||
<span class="tab-text">跟卖精灵</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="steps-title">操作流程:</div>
|
||||
<div class="steps-flow">
|
||||
|
||||
<!-- 跟卖精灵流程区 -->
|
||||
<div v-if="currentTab === 'genmai'" class="steps-title">操作流程:</div>
|
||||
<div v-if="currentTab === 'genmai'" class="steps-flow">
|
||||
<!-- 1. 选择账号 -->
|
||||
<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">请选择需启动的跟卖精灵账号</div>
|
||||
<template v-if="genmaiAccounts.length">
|
||||
<el-scrollbar :class="['account-list', { 'scroll-limit': genmaiAccounts.length > 3 }]">
|
||||
<div>
|
||||
<div
|
||||
v-for="acc in genmaiAccounts"
|
||||
:key="acc.id"
|
||||
:class="['acct-item', { selected: selectedGenmaiAccountId === acc.id }]"
|
||||
@click="selectedGenmaiAccountId = acc.id"
|
||||
>
|
||||
<span class="acct-row">
|
||||
<span :class="['status-dot', acc.status === 1 ? 'on' : 'off']"></span>
|
||||
<img class="avatar" src="/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg" alt="avatar" />
|
||||
<span class="acct-text">{{ acc.name || acc.username }}</span>
|
||||
<span v-if="selectedGenmaiAccountId === acc.id" class="acct-check">✔️</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="placeholder-box">
|
||||
<img class="placeholder-img" src="/icon/image.png" alt="add-account" />
|
||||
<div class="placeholder-tip">请添加跟卖精灵账号</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="step-actions btn-row">
|
||||
<el-button size="small" class="w50" @click="showAccountManager = true">添加账号</el-button>
|
||||
<el-button size="small" class="w50 btn-blue" @click="showAccountManager = true">账号管理</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2. 启动服务 -->
|
||||
<div class="flow-item">
|
||||
<div class="step-index">2</div>
|
||||
<div class="step-card">
|
||||
<div class="step-header"><div class="title">启动服务</div></div>
|
||||
<div class="desc">请确保设备已安装Chrome浏览器,否则服务将无法启动。打开跟卖精灵将关闭Chrome浏览器进程。</div>
|
||||
<div class="action-buttons column">
|
||||
<el-button
|
||||
size="small"
|
||||
class="w100 btn-blue"
|
||||
:disabled="genmaiLoading || !genmaiAccounts.length"
|
||||
@click="openGenmaiSpirit"
|
||||
>
|
||||
<span v-if="!genmaiLoading">启动服务</span>
|
||||
<span v-else>⟳ 启动中...</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentTab === 'asin'" class="steps-title">操作流程:</div>
|
||||
<div v-if="currentTab === 'asin'" class="steps-flow">
|
||||
<!-- 1 -->
|
||||
<div class="flow-item">
|
||||
<div class="step-index">1</div>
|
||||
@@ -423,6 +491,14 @@ onMounted(async () => {
|
||||
<!-- 试用期过期弹框 -->
|
||||
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType" />
|
||||
|
||||
<!-- 账号管理弹框 -->
|
||||
<AccountManager
|
||||
ref="accountManagerRef"
|
||||
v-model="showAccountManager"
|
||||
platform="genmai"
|
||||
@refresh="loadGenmaiAccounts"
|
||||
/>
|
||||
|
||||
<!-- 表格上方进度条 -->
|
||||
<div v-if="progressVisible" class="progress-head">
|
||||
<div class="progress-section">
|
||||
@@ -502,11 +578,26 @@ onMounted(async () => {
|
||||
.tab-item:last-child { border-radius: 0 3px 3px 0; border-left: none; }
|
||||
.tab-item:hover { background: #e8f4ff; color: #409EFF; }
|
||||
.tab-item.active { background: #1677FF; color: #fff; border-color: #1677FF; cursor: default; }
|
||||
.tab-item.loading { background: #e8f4ff; color: #409EFF; cursor: not-allowed; opacity: 0.8; }
|
||||
.tab-icon { font-size: 12px; }
|
||||
.spinner-icon { animation: spin 1s linear infinite; display: inline-block; }
|
||||
.tab-text { line-height: 1; }
|
||||
|
||||
/* 账号列表样式(和斑马一致) */
|
||||
.account-list { height: auto; }
|
||||
.scroll-limit { max-height: 140px; }
|
||||
.placeholder-box { display: flex; align-items: center; justify-content: center; flex-direction: column; height: 100px; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 8px; }
|
||||
.placeholder-img { width: 80px; opacity: 0.9; }
|
||||
.placeholder-tip { margin-top: 6px; font-size: 12px; color: #a8abb2; }
|
||||
.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%; }
|
||||
.acct-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px; }
|
||||
.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; cursor: pointer; margin-bottom: 4px; }
|
||||
.acct-item.selected { background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff; }
|
||||
.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::-webkit-scrollbar { width: 0; height: 0; }
|
||||
|
||||
.body-layout { display: flex; gap: 12px; flex: 1; overflow: hidden; }
|
||||
.steps-sidebar { width: 220px; background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 10px; height: 100%; flex-shrink: 0; }
|
||||
.steps-title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
||||
@@ -536,6 +627,9 @@ onMounted(async () => {
|
||||
.file-chip .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.single-input.left { display: flex; gap: 8px; }
|
||||
.action-buttons.column { display: flex; flex-direction: column; gap: 8px; }
|
||||
.step-actions { margin-top: 8px; display: flex; gap: 8px; }
|
||||
.btn-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||
.w50 { width: 100%; }
|
||||
.form-row { margin-bottom: 10px; }
|
||||
.label { display: block; font-size: 12px; color: #606266; margin-bottom: 6px; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user