Add admin-frontend and user-frontend standalone projects, certification/invoice/discovery features, fix auth header and theme consistency
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user