| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- <template>
- <div class="sales-page">
- <section class="header-bar">
- <h1 class="header-title">数据监控</h1>
- <div class="header-actions">
- <el-upload action="" :auto-upload="false" :show-file-list="false" accept=".xlsx,.xls,.csv" :on-change="handleFileChange">
- <el-button size="small" icon="el-icon-upload2">上传销售数据</el-button>
- </el-upload>
- <el-button size="small" icon="el-icon-refresh" :loading="loading" @click="refreshData">刷新</el-button>
- <el-button size="small" plain icon="el-icon-setting" @click="settingsVisible = true">设置</el-button>
- <el-button size="small" type="primary" icon="el-icon-download" @click="exportData">导出</el-button>
- </div>
- </section>
- <section class="panel">
- <h3>数据同步状态</h3>
- <div class="platform-grid">
- <div v-for="platform in platforms" :key="platform.name" class="platform-card" :class="platform.status">
- <strong>{{ platform.name }}</strong>
- <i :class="platform.icon"></i>
- <span>{{ platform.label }}</span>
- <b>{{ platform.rate }}</b>
- <em>最后同步:{{ platform.lastSync }}</em>
- </div>
- </div>
- <div class="log-section">
- <div class="log-header"><span>同步日志</span><small>最近 {{ logs.length }} 条记录</small></div>
- <div v-for="log in logs" :key="log.id" class="log-item" :class="{ expanded: expandedLog === log.id }" @click="expandedLog = expandedLog === log.id ? '' : log.id">
- <div class="log-line">
- <i :class="log.icon"></i>
- <span>{{ log.title }}</span>
- <time>{{ log.time }}</time>
- </div>
- <p v-if="expandedLog === log.id">{{ log.detail }}</p>
- </div>
- </div>
- </section>
- <section class="overview-grid">
- <div class="overview-card">
- <div>
- <span>今日销售额</span>
- <strong>{{ formatMoney(todayRevenue) }}</strong>
- <em class="up">+{{ revenueGrowth }}% 较昨日</em>
- </div>
- <i class="el-icon-money sales"></i>
- </div>
- <div class="overview-card">
- <div>
- <span>今日订单量</span>
- <strong>{{ formatNumber(todayOrders) }}</strong>
- <em class="up">+{{ orderGrowth }}% 较昨日</em>
- </div>
- <i class="el-icon-s-order orders"></i>
- </div>
- </section>
- <section class="panel">
- <div class="panel-head">
- <h3>数据源配置</h3>
- <el-button size="small" type="primary" icon="el-icon-plus" @click="addSource">添加数据源</el-button>
- </div>
- <el-table :data="platforms" size="small">
- <el-table-column prop="name" label="平台名称" min-width="120" />
- <el-table-column label="API 状态" min-width="120">
- <template slot-scope="{ row }"><span class="status-dot" :class="row.status"></span> {{ row.label }}</template>
- </el-table-column>
- <el-table-column prop="frequency" label="同步频率" min-width="120" />
- <el-table-column prop="lastSync" label="最后同步时间" min-width="160" />
- <el-table-column label="操作" min-width="180">
- <template slot-scope="{ row }">
- <el-button size="mini" type="success" :disabled="row.status === 'syncing'" @click="syncPlatform(row)">立即同步</el-button>
- <el-button size="mini" plain @click="editPlatform(row)">编辑配置</el-button>
- </template>
- </el-table-column>
- </el-table>
- </section>
- <el-dialog title="系统设置" :visible.sync="settingsVisible" width="480px">
- <el-form label-position="top">
- <el-form-item label="全局同步频率"><el-select v-model="settings.frequency"><el-option label="每 5 分钟" value="5" /><el-option label="每 30 分钟" value="30" /><el-option label="每小时" value="60" /></el-select></el-form-item>
- <el-form-item label="失败重试次数"><el-input-number v-model="settings.retry" :min="1" :max="10" /></el-form-item>
- <el-form-item label="销售额下降告警阈值"><el-input-number v-model="settings.warning" :min="1" :max="100" /> %</el-form-item>
- </el-form>
- <span slot="footer"><el-button @click="settingsVisible = false">取消</el-button><el-button type="primary" @click="saveSettings">保存设置</el-button></span>
- </el-dialog>
- </div>
- </template>
- <script>
- import { analyzeSaleOverviewWithFile, getSaleOverviewResults } from '@/api/client'
- export default {
- name: 'SaleDataMonitor',
- data() {
- return {
- loading: false,
- file: null,
- results: {},
- expandedLog: '',
- settingsVisible: false,
- settings: { frequency: '5', retry: 5, warning: 20 },
- platforms: [
- { name: '天猫', status: 'online', label: '在线', icon: 'el-icon-success', rate: '1,280 单/小时', lastSync: '刚刚', frequency: '每 5 分钟' },
- { name: '京东', status: 'syncing', label: '同步中', icon: 'el-icon-loading', rate: '856 单/小时', lastSync: '2 分钟前', frequency: '每 10 分钟' },
- { name: '抖音', status: 'online', label: '在线', icon: 'el-icon-success', rate: '2,340 单/小时', lastSync: '刚刚', frequency: '每 10 分钟' },
- { name: '拼多多', status: 'error', label: '异常', icon: 'el-icon-error', rate: '0 单/小时', lastSync: '1 小时前', frequency: '每 30 分钟' },
- { name: '快手', status: 'offline', label: '未连接', icon: 'el-icon-remove', rate: '0 单/小时', lastSync: '3 天前', frequency: '每 30 分钟' }
- ],
- logs: [
- { id: '1', icon: 'el-icon-success', title: '天猫 - 同步完成', time: '14:32:15', detail: '同步数据量:2,845 条 | 耗时:4.2 秒 | 成功率:100%' },
- { id: '2', icon: 'el-icon-loading', title: '京东 - 正在同步', time: '14:31:48', detail: '正在同步第 1,568/3,200 条数据' },
- { id: '3', icon: 'el-icon-warning-outline', title: '拼多多 - 同步失败:API 连接超时', time: '14:28:15', detail: '连接超时 30 秒 | 重试次数:3/5 | 下次重试:5 分钟后' }
- ]
- }
- },
- computed: {
- summary() {
- return this.results.summary || {}
- },
- todayRevenue() {
- return Number(this.summary.total_revenue || 128560)
- },
- todayOrders() {
- return Number(this.summary.total_orders || 2340)
- },
- revenueGrowth() {
- return Number(this.summary.revenue_growth || 15.3).toFixed(1)
- },
- orderGrowth() {
- return Number(this.summary.order_growth || 8.7).toFixed(1)
- }
- },
- mounted() {
- this.loadCachedResults()
- },
- methods: {
- handleFileChange(file) {
- this.file = file.raw
- this.refreshData()
- },
- async loadCachedResults() {
- try {
- const response = await getSaleOverviewResults()
- if (response && response.success) this.results = response.data || {}
- } catch (e) {
- this.results = {}
- }
- },
- async refreshData() {
- this.loading = true
- try {
- if (this.file) {
- const response = await analyzeSaleOverviewWithFile(this.file)
- if (!response.success) throw new Error(response.message || '分析失败')
- this.results = response.data || {}
- this.$modal.msgSuccess('数据已刷新')
- } else {
- await this.loadCachedResults()
- if (Object.keys(this.results || {}).length) {
- this.$modal.msgSuccess('已加载缓存数据')
- } else {
- this.$modal.msgWarning('暂无缓存数据,请先上传销售数据')
- }
- }
- } catch (error) {
- this.$modal.msgError(error.message || '刷新失败')
- } finally {
- this.loading = false
- }
- },
- syncPlatform(row) {
- this.$modal.msgSuccess(`开始同步 ${row.name}`)
- },
- editPlatform(row) {
- this.$modal.msg(`编辑 ${row.name} 配置`)
- },
- addSource() {
- this.$modal.msg('数据源配置入口已打开')
- },
- saveSettings() {
- this.settingsVisible = false
- this.$modal.msgSuccess('设置已保存')
- },
- exportData() {
- const blob = new Blob([JSON.stringify(this.results, null, 2)], { type: 'application/json;charset=utf-8' })
- const url = URL.createObjectURL(blob)
- const link = document.createElement('a')
- link.href = url
- link.download = `sale_monitor_${Date.now()}.json`
- link.click()
- URL.revokeObjectURL(url)
- },
- formatMoney(value) { return `¥${Math.round(Number(value || 0)).toLocaleString()}` },
- formatNumber(value) { return Math.round(Number(value || 0)).toLocaleString() }
- }
- }
- </script>
- <style scoped lang="scss">
- .sales-page { padding: 20px; max-width: 1400px; margin: 0 auto; background: #f0f2f5; min-height: calc(100vh - 84px); }
- .header-bar, .panel, .overview-card { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,.06); }
- .header-bar { display: flex; justify-content: space-between; align-items: center; padding: 16px 24px; margin-bottom: 20px; gap: 16px; flex-wrap: wrap; }
- .header-title { margin: 0; font-size: 20px; font-weight: 600; color: #303133; }
- .header-actions { display: flex; gap: 10px; flex-wrap: wrap; }
- .panel { padding: 24px; margin-bottom: 20px; }
- .panel h3 { margin: 0 0 20px; font-size: 16px; color: #303133; }
- .platform-grid { display: grid; grid-template-columns: repeat(5,1fr); gap: 15px; }
- .platform-card { background: #f8f9fa; border-left: 4px solid #909399; border-radius: 8px; padding: 18px; text-align: center; display: flex; flex-direction: column; gap: 8px; }
- .platform-card.online { border-left-color: #67c23a; }
- .platform-card.syncing { border-left-color: #e6a23c; }
- .platform-card.error { border-left-color: #f56c6c; }
- .platform-card i { font-size: 24px; }
- .platform-card span, .platform-card em { color: #909399; font-size: 12px; font-style: normal; }
- .log-section { margin-top: 22px; padding-top: 18px; border-top: 1px solid #f0f0f0; }
- .log-header { display: flex; justify-content: space-between; color: #606266; margin-bottom: 12px; }
- .log-item { background: #f8f9fa; border-radius: 8px; padding: 12px 15px; margin-bottom: 8px; cursor: pointer; }
- .log-item.expanded { background: #e8f4f8; border-left: 3px solid #17a2b8; }
- .log-line { display: flex; align-items: center; gap: 10px; }
- .log-line span { flex: 1; }
- .log-line time { color: #909399; font-size: 12px; }
- .log-item p { margin: 10px 0 0 26px; color: #606266; font-size: 12px; }
- .overview-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
- .overview-card { padding: 24px; display: flex; justify-content: space-between; align-items: center; }
- .overview-card span { color: #909399; font-size: 13px; }
- .overview-card strong { display: block; font-size: 34px; margin: 8px 0; }
- .overview-card em { font-style: normal; }
- .overview-card > i { width: 60px; height: 60px; border-radius: 15px; display: flex; align-items: center; justify-content: center; font-size: 28px; }
- .overview-card .sales { background: rgba(102,126,234,.12); color: #667eea; }
- .overview-card .orders { background: rgba(103,194,58,.12); color: #67c23a; }
- .up { color: #67c23a; }
- .panel-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
- .panel-head h3 { margin: 0; }
- .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; background: #909399; }
- .status-dot.online { background: #67c23a; }
- .status-dot.syncing { background: #e6a23c; }
- .status-dot.error { background: #f56c6c; }
- @media (max-width: 1200px) { .platform-grid { grid-template-columns: repeat(3,1fr); } }
- @media (max-width: 800px) { .platform-grid, .overview-grid { grid-template-columns: 1fr; } }
- </style>
|