Add admin-frontend and user-frontend standalone projects, certification/invoice/discovery features, fix auth header and theme consistency

This commit is contained in:
TradeMate Dev
2026-05-22 18:35:30 +08:00
parent 18c6cf5406
commit 52dba37f22
79 changed files with 10333 additions and 248 deletions
+117
View File
@@ -0,0 +1,117 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="6" v-for="item in stats" :key="item.label">
<el-card shadow="hover" class="stat-card">
<div class="stat-value">{{ item.value }}</div>
<div class="stat-label">{{ item.label }}</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top:20px">
<el-col :span="12">
<el-card shadow="never">
<template #header><span>快速翻译</span></template>
<el-input v-model="quickText" type="textarea" :rows="3" placeholder="输入要翻译的文本..." />
<div style="margin-top:12px;display:flex;gap:8px">
<el-select v-model="quickLang" style="width:140px">
<el-option label="英语" value="en" />
<el-option label="中文" value="zh" />
<el-option label="西班牙语" value="es" />
<el-option label="日语" value="ja" />
</el-select>
<el-button type="primary" :loading="translating" @click="doQuickTranslate">翻译</el-button>
</div>
<p v-if="quickResult" style="margin-top:12px;padding:12px;background:#f5f5f5;border-radius:6px">{{ quickResult }}</p>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never">
<template #header><span>跟进提醒</span></template>
<div v-if="followups.length">
<div v-for="f in followups" :key="f.id" class="followup-item">
<span class="followup-name">{{ f.customer_name }}</span>
<span class="followup-days">{{ f.silent_days }}天未联系</span>
</div>
</div>
<el-empty v-else description="暂无跟进提醒" :image-size="60" />
</el-card>
</el-col>
</el-row>
<el-card shadow="never" style="margin-top:20px">
<template #header><span>功能入口</span></template>
<div class="feature-grid">
<div v-for="f in features" :key="f.title" class="feature-item" @click="$router.push(f.route)">
<el-icon :size="24" :color="f.color"><component :is="f.icon" /></el-icon>
<span>{{ f.title }}</span>
</div>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getAnalyticsOverview, translate, getFollowupPending } from '@/api'
const stats = ref([])
const quickText = ref('')
const quickLang = ref('en')
const quickResult = ref('')
const translating = ref(false)
const followups = ref([])
const features = [
{ title: '智能翻译', icon: 'ChatLineSquare', color: '#409eff', route: '/translate' },
{ title: '客户管理', icon: 'User', color: '#67c23a', route: '/customers' },
{ title: '产品库', icon: 'Goods', color: '#e6a23c', route: '/products' },
{ title: '报价单', icon: 'DocumentCopy', color: '#f56c6c', route: '/quotations' },
{ title: '营销素材', icon: 'Promotion', color: '#909399', route: '/marketing' },
{ title: '挖掘新客', icon: 'Search', color: '#409eff', route: '/discovery' },
{ title: '智能跟进', icon: 'Message', color: '#67c23a', route: '/followup' },
{ title: '数据分析', icon: 'DataAnalysis', color: '#e6a23c', route: '/analytics' },
{ title: '团队协作', icon: 'UserFilled', color: '#f56c6c', route: '/team' },
]
onMounted(async () => {
try {
const [overview, fup] = await Promise.all([
getAnalyticsOverview().catch(() => null),
getFollowupPending().catch(() => [])
])
const d = overview?.data || overview || {}
stats.value = [
{ value: d.customers?.total || d.total_customers || 0, label: '客户总数' },
{ value: d.translations?.today || d.today_translations || 0, label: '今日翻译' },
{ value: d.quotations?.total || d.total_quotations || 0, label: '报价单数' },
{ value: fup?.length || 0, label: '待跟进' },
]
followups.value = Array.isArray(fup) ? fup.slice(0, 5) : []
} catch { /* ignore */ }
})
async function doQuickTranslate() {
if (!quickText.value.trim()) return
translating.value = true
try {
const res = await translate({ text: quickText.value, target_lang: quickLang.value })
quickResult.value = res.data?.translated_text || res.translated_text || ''
} catch { quickResult.value = '翻译失败' }
finally { translating.value = false }
}
</script>
<style scoped>
.stat-card { cursor: default; text-align: center; }
.stat-value { font-size: 32px; font-weight: 700; color: #409eff; }
.stat-label { font-size: 14px; color: #999; margin-top: 4px; }
.followup-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
.followup-name { font-weight: 500; }
.followup-days { color: #f56c6c; font-size: 12px; }
.feature-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 16px; }
.feature-item { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 20px 12px; cursor: pointer; border-radius: 8px; transition: background 0.2s; }
.feature-item:hover { background: #f0f5ff; }
.feature-item span { font-size: 13px; color: #333; }
</style>