|
@@ -0,0 +1,658 @@
|
|
|
+<template>
|
|
|
+ <div class="dashboard-tabs report-container">
|
|
|
+ <!-- 标签页头部 -->
|
|
|
+ <div class="tabs-header">
|
|
|
+ <el-tabs v-model="activeTab" type="card" @tab-change="handleTabChange" @tab-remove="handleTabRemove">
|
|
|
+ <el-tab-pane
|
|
|
+ v-for="tab in tabs"
|
|
|
+ :key="tab.id"
|
|
|
+ :label="tab.name"
|
|
|
+ :name="tab.id"
|
|
|
+ :closable="tabs.length > 1"
|
|
|
+ >
|
|
|
+ </el-tab-pane>
|
|
|
+ <template #addIcon>
|
|
|
+ <el-button type="primary" size="small" @click="showAddDialog = true">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 新增报表
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-tabs>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 标签页内容 -->
|
|
|
+ <div class="tabs-content">
|
|
|
+ <div v-for="tab in tabs" :key="tab.id" v-show="activeTab === tab.id" class="tab-pane">
|
|
|
+ <!-- 报表操作栏 -->
|
|
|
+ <div class="report-actions">
|
|
|
+ <div class="left-actions">
|
|
|
+ <h3 class="report-title">{{ tab.name }}</h3>
|
|
|
+ <span class="report-desc">{{ tab.description }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="right-actions">
|
|
|
+ <el-button size="small" @click="refreshReport(tab.id)" class="sa-button-refresh">
|
|
|
+ <el-icon><Refresh /></el-icon>
|
|
|
+ 刷新数据
|
|
|
+ </el-button>
|
|
|
+ <el-button size="small" @click="editReport(tab)">
|
|
|
+ <el-icon><Edit /></el-icon>
|
|
|
+ 编辑
|
|
|
+ </el-button>
|
|
|
+ <el-button size="small" type="danger" @click="deleteReport(tab.id)" :disabled="tabs.length <= 1">
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ <el-button size="small" @click="exportReport(tab.id)">
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ 导出报表
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 报表内容 -->
|
|
|
+ <div class="report-content" v-loading="tab.loading">
|
|
|
+ <!-- 统计卡片 -->
|
|
|
+ <div class="stats-cards" v-if="tab.stats">
|
|
|
+ <div class="stats-card" v-for="(stat, index) in tab.stats" :key="index">
|
|
|
+ <div class="stats-content">
|
|
|
+ <div class="stats-icon" :class="stat.type || 'primary'">
|
|
|
+ <el-icon :size="24">
|
|
|
+ <component :is="stat.icon" />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="stats-info">
|
|
|
+ <div class="stats-value">{{ stat.value }}</div>
|
|
|
+ <div class="stats-label">{{ stat.label }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="stats-trend" v-if="stat.trend" :class="stat.trend > 0 ? 'positive' : 'negative'">
|
|
|
+ <el-icon :size="12">
|
|
|
+ <ArrowUp v-if="stat.trend > 0" />
|
|
|
+ <ArrowDown v-else />
|
|
|
+ </el-icon>
|
|
|
+ <span>{{ Math.abs(stat.trend) }}%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图表区域 -->
|
|
|
+ <div class="chart-area" v-if="tab.chartData">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h4 class="chart-title">{{ tab.chartTitle || '数据趋势' }}</h4>
|
|
|
+ <div class="chart-controls">
|
|
|
+ <el-radio-group v-model="tab.chartType" size="small" @change="updateChart(tab.id)">
|
|
|
+ <el-radio-button label="line">折线图</el-radio-button>
|
|
|
+ <el-radio-button label="bar">柱状图</el-radio-button>
|
|
|
+ <el-radio-button label="pie">饼图</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <v-chart :option="tab.chartOption" autoresize style="height: 400px;" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <div class="table-area" v-if="tab.tableData">
|
|
|
+ <div class="table-header">
|
|
|
+ <h4 class="table-title">详细数据</h4>
|
|
|
+ <div class="table-actions">
|
|
|
+ <el-input
|
|
|
+ v-model="tab.searchKeyword"
|
|
|
+ placeholder="搜索"
|
|
|
+ size="small"
|
|
|
+ style="width: 200px;"
|
|
|
+ @input="searchTable(tab.id)"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="table-content">
|
|
|
+ <el-table :data="tab.filteredTableData || tab.tableData" stripe border>
|
|
|
+ <el-table-column
|
|
|
+ v-for="column in tab.tableColumns"
|
|
|
+ :key="column.prop"
|
|
|
+ :prop="column.prop"
|
|
|
+ :label="column.label"
|
|
|
+ :width="column.width"
|
|
|
+ :min-width="column.minWidth"
|
|
|
+ :align="column.align || 'center'"
|
|
|
+ :sortable="column.sortable"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span v-if="column.formatter" v-html="column.formatter(row[column.prop], row)"></span>
|
|
|
+ <span v-else>{{ row[column.prop] }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 新增报表对话框 -->
|
|
|
+ <el-dialog v-model="showAddDialog" title="新增报表" width="500px" class="report-dialog sa-dialog">
|
|
|
+ <el-form :model="newReportForm" :rules="newReportRules" ref="newReportFormRef" label-width="80px">
|
|
|
+ <el-form-item label="报表名称" prop="name">
|
|
|
+ <el-input v-model="newReportForm.name" placeholder="请输入报表名称" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="报表描述" prop="description">
|
|
|
+ <el-input v-model="newReportForm.description" type="textarea" placeholder="请输入报表描述" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="报表类型" prop="type">
|
|
|
+ <el-select v-model="newReportForm.type" placeholder="请选择报表类型">
|
|
|
+ <el-option label="销售报表" value="sales" />
|
|
|
+ <el-option label="用户报表" value="user" />
|
|
|
+ <el-option label="订单报表" value="order" />
|
|
|
+ <el-option label="财务报表" value="finance" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="showAddDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="addReport" :loading="addingReport">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 编辑报表对话框 -->
|
|
|
+ <el-dialog v-model="showEditDialog" title="编辑报表" width="500px">
|
|
|
+ <el-form :model="editReportForm" :rules="newReportRules" ref="editReportFormRef" label-width="80px">
|
|
|
+ <el-form-item label="报表名称" prop="name">
|
|
|
+ <el-input v-model="editReportForm.name" placeholder="请输入报表名称" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="报表描述" prop="description">
|
|
|
+ <el-input v-model="editReportForm.description" type="textarea" placeholder="请输入报表描述" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="报表类型" prop="type">
|
|
|
+ <el-select v-model="editReportForm.type" placeholder="请选择报表类型">
|
|
|
+ <el-option label="销售报表" value="sales" />
|
|
|
+ <el-option label="用户报表" value="user" />
|
|
|
+ <el-option label="订单报表" value="order" />
|
|
|
+ <el-option label="财务报表" value="finance" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="showEditDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="updateReport" :loading="updatingReport">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import {
|
|
|
+ Plus,
|
|
|
+ Refresh,
|
|
|
+ Edit,
|
|
|
+ Delete,
|
|
|
+ Download,
|
|
|
+ Search,
|
|
|
+ ArrowUp,
|
|
|
+ ArrowDown,
|
|
|
+ TrendCharts,
|
|
|
+ DataAnalysis,
|
|
|
+ Money,
|
|
|
+ User
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
+import VChart from 'vue-echarts'
|
|
|
+import * as echarts from 'echarts'
|
|
|
+import { api } from '../../data.service.js'
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const activeTab = ref('1')
|
|
|
+const showAddDialog = ref(false)
|
|
|
+const showEditDialog = ref(false)
|
|
|
+const addingReport = ref(false)
|
|
|
+const updatingReport = ref(false)
|
|
|
+const newReportFormRef = ref()
|
|
|
+const editReportFormRef = ref()
|
|
|
+
|
|
|
+// 标签页数据
|
|
|
+const tabs = ref([
|
|
|
+ {
|
|
|
+ id: '1',
|
|
|
+ name: '销售概览',
|
|
|
+ description: '销售数据总览和趋势分析',
|
|
|
+ type: 'sales',
|
|
|
+ loading: false,
|
|
|
+ chartType: 'line',
|
|
|
+ chartTitle: '销售趋势',
|
|
|
+ searchKeyword: '',
|
|
|
+ stats: [
|
|
|
+ {
|
|
|
+ label: '总销售额',
|
|
|
+ value: '¥125,680',
|
|
|
+ icon: 'Money',
|
|
|
+ color: '#409EFF',
|
|
|
+ trend: 12.5
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '订单数量',
|
|
|
+ value: '1,234',
|
|
|
+ icon: 'DataAnalysis',
|
|
|
+ color: '#67C23A',
|
|
|
+ trend: 8.2
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '新用户',
|
|
|
+ value: '456',
|
|
|
+ icon: 'User',
|
|
|
+ color: '#E6A23C',
|
|
|
+ trend: -2.1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '转化率',
|
|
|
+ value: '3.2%',
|
|
|
+ icon: 'TrendCharts',
|
|
|
+ color: '#F56C6C',
|
|
|
+ trend: 5.8
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ chartData: [],
|
|
|
+ chartOption: {},
|
|
|
+ tableData: [],
|
|
|
+ tableColumns: [
|
|
|
+ { prop: 'date', label: '日期', width: 120 },
|
|
|
+ { prop: 'sales', label: '销售额', width: 120, formatter: (val) => `¥${val}` },
|
|
|
+ { prop: 'orders', label: '订单数', width: 100 },
|
|
|
+ { prop: 'users', label: '新用户', width: 100 },
|
|
|
+ { prop: 'conversion', label: '转化率', width: 100, formatter: (val) => `${val}%` }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+])
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const newReportForm = reactive({
|
|
|
+ name: '',
|
|
|
+ description: '',
|
|
|
+ type: ''
|
|
|
+})
|
|
|
+
|
|
|
+const editReportForm = reactive({
|
|
|
+ id: '',
|
|
|
+ name: '',
|
|
|
+ description: '',
|
|
|
+ type: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const newReportRules = {
|
|
|
+ name: [{ required: true, message: '请输入报表名称', trigger: 'blur' }],
|
|
|
+ type: [{ required: true, message: '请选择报表类型', trigger: 'change' }]
|
|
|
+}
|
|
|
+
|
|
|
+// 方法
|
|
|
+const handleTabChange = (tabName) => {
|
|
|
+ activeTab.value = tabName
|
|
|
+ loadReportData(tabName)
|
|
|
+}
|
|
|
+
|
|
|
+const handleTabRemove = (tabName) => {
|
|
|
+ deleteReport(tabName)
|
|
|
+}
|
|
|
+
|
|
|
+const refreshReport = async (tabId) => {
|
|
|
+ const tab = tabs.value.find(t => t.id === tabId)
|
|
|
+ if (tab) {
|
|
|
+ tab.loading = true
|
|
|
+ await loadReportData(tabId)
|
|
|
+ tab.loading = false
|
|
|
+ ElMessage.success('数据已刷新')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const editReport = (tab) => {
|
|
|
+ editReportForm.id = tab.id
|
|
|
+ editReportForm.name = tab.name
|
|
|
+ editReportForm.description = tab.description
|
|
|
+ editReportForm.type = tab.type
|
|
|
+ showEditDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const deleteReport = async (tabId) => {
|
|
|
+ if (tabs.value.length <= 1) {
|
|
|
+ ElMessage.warning('至少保留一个报表')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定要删除这个报表吗?', '确认删除', {
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+
|
|
|
+ // 调用API删除报表
|
|
|
+ await api.operating.spectaculars.delete(tabId)
|
|
|
+
|
|
|
+ // 从本地数组中移除
|
|
|
+ const index = tabs.value.findIndex(t => t.id === tabId)
|
|
|
+ if (index > -1) {
|
|
|
+ tabs.value.splice(index, 1)
|
|
|
+
|
|
|
+ // 如果删除的是当前激活的标签页,切换到第一个
|
|
|
+ if (activeTab.value === tabId) {
|
|
|
+ activeTab.value = tabs.value[0].id
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ ElMessage.error('删除失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const exportReport = async (tabId) => {
|
|
|
+ const tab = tabs.value.find(t => t.id === tabId)
|
|
|
+ if (tab) {
|
|
|
+ ElMessage.info('导出功能开发中')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const addReport = async () => {
|
|
|
+ try {
|
|
|
+ await newReportFormRef.value.validate()
|
|
|
+ addingReport.value = true
|
|
|
+
|
|
|
+ // 调用API创建报表
|
|
|
+ const response = await api.operating.spectaculars.add({
|
|
|
+ name: newReportForm.name,
|
|
|
+ description: newReportForm.description,
|
|
|
+ type: newReportForm.type
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加到本地数组
|
|
|
+ const newTab = {
|
|
|
+ id: response.data.id || Date.now().toString(),
|
|
|
+ name: newReportForm.name,
|
|
|
+ description: newReportForm.description,
|
|
|
+ type: newReportForm.type,
|
|
|
+ loading: false,
|
|
|
+ chartType: 'line',
|
|
|
+ chartTitle: '数据趋势',
|
|
|
+ searchKeyword: '',
|
|
|
+ stats: [],
|
|
|
+ chartData: [],
|
|
|
+ chartOption: {},
|
|
|
+ tableData: [],
|
|
|
+ tableColumns: []
|
|
|
+ }
|
|
|
+
|
|
|
+ tabs.value.push(newTab)
|
|
|
+ activeTab.value = newTab.id
|
|
|
+
|
|
|
+ // 重置表单
|
|
|
+ Object.assign(newReportForm, { name: '', description: '', type: '' })
|
|
|
+ showAddDialog.value = false
|
|
|
+
|
|
|
+ ElMessage.success('创建成功')
|
|
|
+
|
|
|
+ // 加载新报表数据
|
|
|
+ await loadReportData(newTab.id)
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('创建失败')
|
|
|
+ } finally {
|
|
|
+ addingReport.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const updateReport = async () => {
|
|
|
+ try {
|
|
|
+ await editReportFormRef.value.validate()
|
|
|
+ updatingReport.value = true
|
|
|
+
|
|
|
+ // 调用API更新报表
|
|
|
+ await api.operating.spectaculars.update({
|
|
|
+ id: editReportForm.id,
|
|
|
+ name: editReportForm.name,
|
|
|
+ description: editReportForm.description,
|
|
|
+ type: editReportForm.type
|
|
|
+ })
|
|
|
+
|
|
|
+ // 更新本地数据
|
|
|
+ const tab = tabs.value.find(t => t.id === editReportForm.id)
|
|
|
+ if (tab) {
|
|
|
+ tab.name = editReportForm.name
|
|
|
+ tab.description = editReportForm.description
|
|
|
+ tab.type = editReportForm.type
|
|
|
+ }
|
|
|
+
|
|
|
+ showEditDialog.value = false
|
|
|
+ ElMessage.success('更新成功')
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('更新失败')
|
|
|
+ } finally {
|
|
|
+ updatingReport.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const updateChart = (tabId) => {
|
|
|
+ const tab = tabs.value.find(t => t.id === tabId)
|
|
|
+ if (tab && tab.chartData) {
|
|
|
+ generateChartOption(tab)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const searchTable = (tabId) => {
|
|
|
+ const tab = tabs.value.find(t => t.id === tabId)
|
|
|
+ if (tab && tab.tableData) {
|
|
|
+ if (!tab.searchKeyword) {
|
|
|
+ tab.filteredTableData = tab.tableData
|
|
|
+ } else {
|
|
|
+ tab.filteredTableData = tab.tableData.filter(row => {
|
|
|
+ return Object.values(row).some(value =>
|
|
|
+ String(value).toLowerCase().includes(tab.searchKeyword.toLowerCase())
|
|
|
+ )
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadReportData = async (tabId) => {
|
|
|
+ const tab = tabs.value.find(t => t.id === tabId)
|
|
|
+ if (!tab) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ tab.loading = true
|
|
|
+
|
|
|
+ // 根据报表类型加载不同的数据
|
|
|
+ switch (tab.type) {
|
|
|
+ case 'sales':
|
|
|
+ await loadSalesData(tab)
|
|
|
+ break
|
|
|
+ case 'user':
|
|
|
+ await loadUserData(tab)
|
|
|
+ break
|
|
|
+ case 'order':
|
|
|
+ await loadOrderData(tab)
|
|
|
+ break
|
|
|
+ case 'finance':
|
|
|
+ await loadFinanceData(tab)
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ await loadDefaultData(tab)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成图表配置
|
|
|
+ generateChartOption(tab)
|
|
|
+
|
|
|
+ // 初始化表格过滤数据
|
|
|
+ tab.filteredTableData = tab.tableData
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('数据加载失败')
|
|
|
+ } finally {
|
|
|
+ tab.loading = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadSalesData = async (tab) => {
|
|
|
+ // 模拟销售数据
|
|
|
+ tab.chartData = [
|
|
|
+ { date: '2024-01-01', value: 1200 },
|
|
|
+ { date: '2024-01-02', value: 1350 },
|
|
|
+ { date: '2024-01-03', value: 1100 },
|
|
|
+ { date: '2024-01-04', value: 1450 },
|
|
|
+ { date: '2024-01-05', value: 1600 }
|
|
|
+ ]
|
|
|
+
|
|
|
+ tab.tableData = [
|
|
|
+ { date: '2024-01-01', sales: 1200, orders: 45, users: 12, conversion: 2.8 },
|
|
|
+ { date: '2024-01-02', sales: 1350, orders: 52, users: 15, conversion: 3.1 },
|
|
|
+ { date: '2024-01-03', sales: 1100, orders: 38, users: 8, conversion: 2.5 },
|
|
|
+ { date: '2024-01-04', sales: 1450, orders: 58, users: 18, conversion: 3.5 },
|
|
|
+ { date: '2024-01-05', sales: 1600, orders: 62, users: 22, conversion: 3.8 }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+const loadUserData = async (tab) => {
|
|
|
+ // 模拟用户数据
|
|
|
+ tab.stats = [
|
|
|
+ { label: '总用户数', value: '12,345', icon: 'User', color: '#409EFF', trend: 15.2 },
|
|
|
+ { label: '活跃用户', value: '8,901', icon: 'TrendCharts', color: '#67C23A', trend: 8.7 },
|
|
|
+ { label: '新增用户', value: '234', icon: 'DataAnalysis', color: '#E6A23C', trend: -3.2 },
|
|
|
+ { label: '留存率', value: '72.1%', icon: 'Money', color: '#F56C6C', trend: 4.5 }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+const loadOrderData = async (tab) => {
|
|
|
+ // 模拟订单数据
|
|
|
+ tab.stats = [
|
|
|
+ { label: '总订单数', value: '5,678', icon: 'DataAnalysis', color: '#409EFF', trend: 12.3 },
|
|
|
+ { label: '待处理', value: '123', icon: 'TrendCharts', color: '#E6A23C', trend: -5.1 },
|
|
|
+ { label: '已完成', value: '5,234', icon: 'Money', color: '#67C23A', trend: 18.9 },
|
|
|
+ { label: '退款率', value: '2.1%', icon: 'User', color: '#F56C6C', trend: -1.2 }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+const loadFinanceData = async (tab) => {
|
|
|
+ // 模拟财务数据
|
|
|
+ tab.stats = [
|
|
|
+ { label: '总收入', value: '¥234,567', icon: 'Money', color: '#409EFF', trend: 22.1 },
|
|
|
+ { label: '总支出', value: '¥123,456', icon: 'DataAnalysis', color: '#F56C6C', trend: 8.3 },
|
|
|
+ { label: '净利润', value: '¥111,111', icon: 'TrendCharts', color: '#67C23A', trend: 35.7 },
|
|
|
+ { label: '利润率', value: '47.4%', icon: 'User', color: '#E6A23C', trend: 12.8 }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+const loadDefaultData = async (tab) => {
|
|
|
+ // 默认数据
|
|
|
+ tab.chartData = []
|
|
|
+ tab.tableData = []
|
|
|
+ tab.stats = []
|
|
|
+}
|
|
|
+
|
|
|
+const generateChartOption = (tab) => {
|
|
|
+ if (!tab.chartData || tab.chartData.length === 0) return
|
|
|
+
|
|
|
+ const xData = tab.chartData.map(item => item.date)
|
|
|
+ const yData = tab.chartData.map(item => item.value)
|
|
|
+
|
|
|
+ const baseOption = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'cross'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: [tab.chartTitle || '数据']
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: xData
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (tab.chartType) {
|
|
|
+ case 'line':
|
|
|
+ tab.chartOption = {
|
|
|
+ ...baseOption,
|
|
|
+ series: [{
|
|
|
+ name: tab.chartTitle || '数据',
|
|
|
+ type: 'line',
|
|
|
+ data: yData,
|
|
|
+ smooth: true,
|
|
|
+ areaStyle: {
|
|
|
+ opacity: 0.3
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'bar':
|
|
|
+ tab.chartOption = {
|
|
|
+ ...baseOption,
|
|
|
+ series: [{
|
|
|
+ name: tab.chartTitle || '数据',
|
|
|
+ type: 'bar',
|
|
|
+ data: yData
|
|
|
+ }]
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'pie':
|
|
|
+ tab.chartOption = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ left: 'left'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: tab.chartTitle || '数据',
|
|
|
+ type: 'pie',
|
|
|
+ radius: '50%',
|
|
|
+ data: tab.chartData.map((item, index) => ({
|
|
|
+ value: item.value,
|
|
|
+ name: item.date || `数据${index + 1}`
|
|
|
+ })),
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化
|
|
|
+onMounted(() => {
|
|
|
+ loadReportData(activeTab.value)
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+@import '../styles/report-common.scss';
|
|
|
+
|
|
|
+.dashboard-tabs {
|
|
|
+ .stats-trend {
|
|
|
+ &.positive {
|
|
|
+ color: var(--el-color-success);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.negative {
|
|
|
+ color: var(--el-color-danger);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|