Files
erp_sb/electron-vue-template/src/renderer/components/common/AccountManager.vue
zhangzijienbplus 0be60bc103 feat(genmai): 集成跟卖精灵账号管理系统跟
- 新增卖精灵账号管理功能,支持多账号切换
- 实现账号增删改查接口与前端交互逻辑
-优化打开跟卖精灵流程,增加账号选择界面
- 添加账号权限限制与订阅升级提醒
- 完善后端账号实体类及数据库映射
- 更新系统控制器以支持指定账号启动功能- 调整HTTP请求路径适配新工具模块路由- 升级客户端版本至2.5.3并优化代码结构
2025-10-27 09:13:00 +08:00

220 lines
9.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, onMounted, computed, defineAsyncComponent, watch } from 'vue'
import { zebraApi, type BanmaAccount } from '../../api/zebra'
import { genmaiApi, type GenmaiAccount } from '../../api/genmai'
import { ElMessageBox, ElMessage } from 'element-plus'
import { getUsernameFromToken } from '../../utils/token'
const TrialExpiredDialog = defineAsyncComponent(() => import('./TrialExpiredDialog.vue'))
type PlatformKey = 'zebra' | 'shopee' | 'rakuten' | 'amazon' | 'genmai'
const props = defineProps<{ modelValue: boolean; platform?: PlatformKey }>()
const emit = defineEmits(['update:modelValue', 'refresh'])
const visible = computed({ get: () => props.modelValue, set: v => emit('update:modelValue', v) })
const curPlatform = ref<PlatformKey>(props.platform || 'zebra')
// 监听弹框打开,同步平台并加载数据
watch(() => props.modelValue, (newVal) => {
if (newVal && props.platform) {
curPlatform.value = props.platform
load()
}
})
// 升级订阅弹框
const showUpgradeDialog = ref(false)
const PLATFORM_LABEL: Record<PlatformKey, string> = {
zebra: '斑马 ERP',
shopee: 'Shopee 虾皮购物',
rakuten: 'Rakuten 乐天购物',
amazon: 'Amazon 亚马逊',
genmai: '跟卖精灵'
}
const accounts = ref<(BanmaAccount | GenmaiAccount)[]>([])
const accountLimit = ref({ limit: 1, count: 0 })
// 添加账号对话框
const accountDialogVisible = ref(false)
const formUsername = ref('')
const formPassword = ref('')
async function load() {
const api = curPlatform.value === 'genmai' ? genmaiApi : zebraApi
const username = getUsernameFromToken()
const [res, limitRes] = await Promise.all([api.getAccounts(username), api.getAccountLimit(username)])
accounts.value = (res as any)?.data ?? res
accountLimit.value = (limitRes as any)?.data ?? limitRes
}
// 暴露方法供父组件调用
defineExpose({ load })
onMounted(load)
function switchPlatform(p: PlatformKey) {
curPlatform.value = p
load()
}
function formatDate(a: any) {
const v = a?.updateTime || a?.createTime
return v ? String(v) : '-'
}
async function onDelete(a: any) {
const id = a?.id
try {
await ElMessageBox.confirm(`确定删除账号 "${a?.name || a?.username || id}" 吗?`, '提示', { type: 'warning' })
} catch { return }
const api = curPlatform.value === 'genmai' ? genmaiApi : zebraApi
await api.removeAccount(id)
ElMessage({ message: '删除成功', type: 'success' })
await load()
emit('refresh') // 通知外层组件刷新账号列表
}
async function handleAddAccount() {
if (accountLimit.value.count >= accountLimit.value.limit) {
ElMessage({ message: `账号数量已达上限`, type: 'warning' })
return
}
formUsername.value = ''
formPassword.value = ''
accountDialogVisible.value = true
}
async function submitAccount() {
const api = curPlatform.value === 'genmai' ? genmaiApi : zebraApi
try {
await api.saveAccount({
username: formUsername.value,
password: formPassword.value,
status: 1
}, getUsernameFromToken())
ElMessage({ message: '添加成功', type: 'success' })
accountDialogVisible.value = false
await load()
emit('refresh')
} catch (e: any) {
ElMessage({ message: e.message || '添加失败', type: 'error' })
}
}
</script>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({ name: 'AccountManager' })
</script>
<template>
<el-dialog v-model="visible" width="550px" class="acc-manager" :show-close="true">
<template #header>
<div class="dlg-header"></div>
</template>
<div class="layout">
<aside class="sider">
<div class="sider-title">全账号管理</div>
<div class="nav">
<div :class="['nav-item', {active: curPlatform==='zebra'}]" @click="switchPlatform('zebra')">斑马 ERP</div>
<div :class="['nav-item', {active: curPlatform==='genmai'}]" @click="switchPlatform('genmai')">跟卖精灵</div>
</div>
</aside>
<section class="content">
<div class="platform-bar">{{ PLATFORM_LABEL[curPlatform] }}</div>
<div class="top">
<img src="/icon/image.png" class="hero" alt="logo" />
<div class="head-main">
<div class="main-title">在线账号管理{{ accountLimit.count }}/{{ accountLimit.limit }}</div>
<div class="main-sub">
您当前订阅可同时托管{{ accountLimit.limit }}{{ curPlatform === 'genmai' ? '跟卖精灵' : '斑马' }}账号<br>
<span v-if="accountLimit.limit < 3">如需扩增账号数量,请 <span class="upgrade" @click="showUpgradeDialog = true">升级订阅</span></span>
</div>
</div>
</div>
<div class="list" :class="{ compact: accounts.length <= 1 }">
<div v-for="a in accounts" :key="a.id" class="row">
<span :class="['dot', a.status === 1 ? 'on' : 'off']"></span>
<div class="user-info">
<img class="avatar" src="/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg" />
<span class="name">{{ a.name || a.username }}</span>
</div>
<span class="date">{{ formatDate(a) }}</span>
<el-button link type="danger" @click="onDelete(a)">删除</el-button>
</div>
</div>
<div class="footer">
<el-button type="primary" class="btn" @click="handleAddAccount">添加账号</el-button>
</div>
</section>
</div>
<!-- 添加账号对话框 -->
<el-dialog v-model="accountDialogVisible" width="420px" class="add-account-dialog">
<template #header>
<div class="aad-header">
<img class="aad-icon" src="/icon/image.png" alt="logo" />
<div class="aad-title">添加{{ curPlatform === 'genmai' ? '跟卖精灵' : '斑马' }}账号</div>
</div>
</template>
<div class="aad-row">
<el-input v-model="formUsername" :placeholder="curPlatform === 'genmai' ? '请输入账号nickname' : '请输入账号'" />
</div>
<div class="aad-row">
<el-input v-model="formPassword" placeholder="请输入密码" type="password" show-password />
</div>
<template #footer>
<el-button type="primary" class="btn-blue" style="width: 100%" @click="submitAccount">添加</el-button>
</template>
</el-dialog>
<!-- 升级订阅弹框 -->
<TrialExpiredDialog v-model="showUpgradeDialog" expired-type="subscribe" />
</el-dialog>
</template>
<style scoped>
.acc-manager :deep(.el-dialog__header) { text-align:center; }
.layout { display:grid; grid-template-columns: 160px 1fr; gap: 12px; min-height: 340px; }
.sider { border-right: 1px solid #ebeef5; padding-right: 10px; }
.sider-title { color:#303133; font-size:13px; font-weight: 600; margin-bottom: 10px; text-align: left; }
.nav { display:flex; flex-direction: column; gap: 4px; }
.nav-item { padding: 6px 8px; border-radius: 4px; cursor: pointer; color:#606266; font-size: 12px; transition: all 0.2s; text-align: left; }
.nav-item:hover { background:#f0f2f5; }
.nav-item.active { background:#e6f4ff; color:#409EFF; font-weight: 600; }
.platform-bar { font-weight: 600; color:#303133; margin: 0 0 12px 0; text-align: left; font-size: 14px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; }
.content { display:flex; flex-direction: column; min-width: 0; }
.top { display:flex; flex-direction: column; align-items:center; gap: 6px; margin-bottom: 12px; }
.hero { width: 160px; height: auto; }
.head-main { text-align:center; }
.main-title { font-size: 16px; font-weight: 600; color:#303133; margin-bottom: 4px; }
.main-sub { color:#909399; font-size: 11px; line-height: 1.4; }
.upgrade { color:#409EFF; cursor: pointer; font-weight: 600; transition: all 0.2s ease; }
.upgrade:hover { color:#0d5ed6; text-decoration: underline; }
.list { border:1px solid #ebeef5; border-radius: 6px; background: #fff; flex: 0 0 auto; width: 100%; max-height: 160px; overflow-y: auto; }
.list.compact { max-height: 48px; }
/* 添加账号对话框样式 */
.add-account-dialog .aad-header { display:flex; flex-direction: column; align-items:center; gap:8px; padding-top: 8px; width: 100%; }
.add-account-dialog .aad-icon { width: 120px; height: auto; }
.add-account-dialog .aad-title { font-weight: 600; font-size: 18px; text-align: center; }
.add-account-dialog .aad-row { margin-top: 12px; }
:deep(.add-account-dialog .el-dialog__header) { text-align: center; padding-right: 0; display: block; }
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
.btn-blue:hover { background: #0d5ed6; border-color: #0d5ed6; }
.row { display:grid; grid-template-columns: 8px 1fr 120px 60px; gap: 8px; align-items:center; padding: 4px 8px; border-bottom: 1px solid #f5f5f5; height: 28px; }
.row:last-child { border-bottom:none; }
.row:hover { background:#fafafa; }
.dot { width:6px; height:6px; border-radius:50%; justify-self: center; }
.dot.on { background:#52c41a; }
.dot.off { background:#ff4d4f; }
.user-info { display: flex; align-items: center; gap: 8px; min-width: 0; }
.avatar { width:22px; height:22px; border-radius:50%; object-fit: cover; }
.name { font-weight:500; font-size: 13px; color:#303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.date { color:#999; font-size:11px; text-align: center; }
.footer { display:flex; justify-content:center; padding-top: 10px; }
.btn { width: 180px; height: 32px; font-size: 13px; }
</style>