ソースを参照

feat: 优化细节

叶静 4 週間 前
コミット
1c8988add2

+ 85 - 0
src/app/shop/admin/data/data.service.js

@@ -30,6 +30,91 @@ const api = {
         method: 'GET',
       }),
   },
+  // 新增数据报表模块API - 基于OpenAPI定义,添加operating前缀
+  operating: {
+    // 报表管理相关API
+    spectaculars: {
+      // 添加报表
+      add: (data) =>
+        request({
+          url: '/operating/spectaculars/add',
+          method: 'POST',
+          data,
+        }),
+      
+      // 删除报表
+      delete: (id) =>
+        request({
+          url: `/operating/spectaculars/delete/${id}`,
+          method: 'DELETE',
+        }),
+      
+      // 获取报表列表
+      list: (params) =>
+        request({
+          url: '/operating/spectaculars/list',
+          method: 'GET',
+          params,
+        }),
+      
+      // 更新报表
+      update: (data) =>
+        request({
+          url: '/operating/spectaculars/update',
+          method: 'PUT',
+          data,
+        }),
+      
+      // 获取昨日数据
+      yesterdayData: () =>
+        request({
+          url: '/operating/spectaculars/yesterdayData',
+          method: 'GET',
+        }),
+    },
+    
+    // 维度管理相关API
+    dimensions: {
+      // 添加维度
+      add: (data) =>
+        request({
+          url: '/operating/spectaculars/addDimension',
+          method: 'POST',
+          data,
+        }),
+      
+      // 删除维度
+      delete: (id) =>
+        request({
+          url: `/operating/spectaculars/deleteDimension/${id}`,
+          method: 'DELETE',
+        }),
+      
+      // 获取维度列表
+      list: (params) =>
+        request({
+          url: '/operating/spectaculars/listDimension',
+          method: 'GET',
+          params,
+        }),
+      
+      // 更新维度
+      update: (data) =>
+        request({
+          url: '/operating/spectaculars/updateDimension',
+          method: 'PUT',
+          data,
+        }),
+      
+      // 添加维度视图
+      addView: (data) =>
+        request({
+          url: '/operating/spectaculars/addDimensionView',
+          method: 'POST',
+          data,
+        }),
+    },
+  },
 };
 
 export { route, api };

+ 524 - 0
src/app/shop/admin/data/report/components/add-chart-dialog.vue

@@ -0,0 +1,524 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="新增图表"
+    width="50%"
+    class="add-chart-dialog"
+    @close="handleClose"
+  >
+    <div class="dialog-content">
+      <el-form :model="formData" :rules="formRules" ref="formRef" label-width="100px">
+        <!-- 基本信息 -->
+        <div class="section">
+          <h4 class="section-title">基本信息</h4>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="图表名称" prop="name">
+                <el-input v-model="formData.name" placeholder="请输入图表名称" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="图表类型" prop="type">
+                <el-select v-model="formData.type" placeholder="请选择图表类型" style="width: 100%">
+                  <el-option label="折线图" value="line">
+                    <div class="option-item">
+                      <el-icon><DataLine /></el-icon>
+                      <span>折线图</span>
+                    </div>
+                  </el-option>
+                  <el-option label="柱状图" value="bar">
+                    <div class="option-item">
+                      <el-icon><Histogram /></el-icon>
+                      <span>柱状图</span>
+                    </div>
+                  </el-option>
+                  <el-option label="饼图" value="pie">
+                    <div class="option-item">
+                      <el-icon><PieChart /></el-icon>
+                      <span>饼图</span>
+                    </div>
+                  </el-option>
+                  <el-option label="面积图" value="area">
+                    <div class="option-item">
+                      <el-icon><TrendCharts /></el-icon>
+                      <span>面积图</span>
+                    </div>
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-form-item label="图表描述">
+            <el-input 
+              v-model="formData.description" 
+              type="textarea" 
+              :rows="2"
+              placeholder="请输入图表描述"
+            />
+          </el-form-item>
+        </div>
+
+        <!-- 数据源配置 -->
+        <div class="section">
+          <h4 class="section-title">数据源配置</h4>
+          <el-form-item label="数据源" prop="dataSource">
+            <el-select v-model="formData.dataSource" placeholder="请选择数据源" style="width: 100%">
+              <el-option label="销售数据" value="sales" />
+              <el-option label="订单数据" value="orders" />
+              <el-option label="用户数据" value="users" />
+              <el-option label="商品数据" value="goods" />
+              <el-option label="自定义SQL" value="custom" />
+            </el-select>
+          </el-form-item>
+          
+          <el-form-item label="时间范围" v-if="formData.dataSource">
+            <el-select v-model="formData.timeRange" placeholder="请选择时间范围" style="width: 100%">
+              <el-option label="最近7天" value="7d" />
+              <el-option label="最近30天" value="30d" />
+              <el-option label="最近90天" value="90d" />
+              <el-option label="最近1年" value="1y" />
+              <el-option label="自定义" value="custom" />
+            </el-select>
+          </el-form-item>
+          
+          <el-form-item label="自定义SQL" v-if="formData.dataSource === 'custom'">
+            <el-input 
+              v-model="formData.customSql" 
+              type="textarea" 
+              :rows="4"
+              placeholder="请输入SQL查询语句"
+            />
+          </el-form-item>
+        </div>
+
+        <!-- 图表配置 -->
+        <div class="section">
+          <h4 class="section-title">图表配置</h4>
+          
+          <!-- 折线图/柱状图配置 -->
+          <template v-if="['line', 'bar', 'area'].includes(formData.type)">
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="X轴字段">
+                  <el-select v-model="formData.config.xAxis" placeholder="请选择X轴字段" style="width: 100%">
+                    <el-option label="日期" value="date" />
+                    <el-option label="月份" value="month" />
+                    <el-option label="分类" value="category" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="Y轴字段">
+                  <el-select v-model="formData.config.yAxis" placeholder="请选择Y轴字段" style="width: 100%">
+                    <el-option label="销售额" value="amount" />
+                    <el-option label="订单数" value="orders" />
+                    <el-option label="用户数" value="users" />
+                    <el-option label="商品数" value="goods" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </template>
+          
+          <!-- 饼图配置 -->
+          <template v-if="formData.type === 'pie'">
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="分类字段">
+                  <el-select v-model="formData.config.categoryField" placeholder="请选择分类字段" style="width: 100%">
+                    <el-option label="商品分类" value="category" />
+                    <el-option label="用户类型" value="userType" />
+                    <el-option label="订单状态" value="status" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="数值字段">
+                  <el-select v-model="formData.config.valueField" placeholder="请选择数值字段" style="width: 100%">
+                    <el-option label="销售额" value="amount" />
+                    <el-option label="数量" value="count" />
+                    <el-option label="占比" value="percentage" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </template>
+          
+          <!-- 通用配置 -->
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="图表高度">
+                <el-slider 
+                  v-model="formData.config.height" 
+                  :min="200" 
+                  :max="600" 
+                  :step="50"
+                  :marks="{ 200: '200px', 300: '300px', 400: '400px', 500: '500px', 600: '600px' }"
+                  show-input
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="显示图例">
+                <el-switch v-model="formData.config.showLegend" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="显示网格">
+                <el-switch v-model="formData.config.showGrid" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="数据标签">
+                <el-switch v-model="formData.config.showDataLabels" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 样式配置 -->
+        <div class="section">
+          <h4 class="section-title">样式配置</h4>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="主题色">
+                <el-color-picker v-model="formData.config.primaryColor" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="背景色">
+                <el-color-picker v-model="formData.config.backgroundColor" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </el-form>
+
+      <!-- 预览区域 -->
+      <div class="section">
+        <h4 class="section-title">图表预览</h4>
+        <div class="chart-preview">
+          <div class="preview-container" v-loading="previewLoading">
+            <v-chart :option="previewOption" autoresize :style="{ height: formData.config.height + 'px' }" />
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleClose">取消</el-button>
+        <el-button @click="handlePreview" :loading="previewLoading">
+          预览图表
+        </el-button>
+        <el-button type="primary" @click="handleSave" :loading="saving">
+          保存图表
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, computed, watch } from 'vue';
+import { ElMessage } from 'element-plus';
+import {
+  DataLine,
+  Histogram,
+  PieChart,
+  TrendCharts
+} from '@element-plus/icons-vue';
+import VChart from 'vue-echarts';
+import { use } from 'echarts/core';
+import {
+  CanvasRenderer
+} from 'echarts/renderers';
+import {
+  LineChart,
+  BarChart,
+  PieChart as EChartsPieChart
+} from 'echarts/charts';
+import {
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  GridComponent
+} from 'echarts/components';
+
+use([
+  CanvasRenderer,
+  LineChart,
+  BarChart,
+  EChartsPieChart,
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  GridComponent
+]);
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  chartData: {
+    type: Object,
+    default: () => ({})
+  }
+});
+
+const emit = defineEmits(['update:visible', 'save']);
+
+// 响应式数据
+const formRef = ref();
+const saving = ref(false);
+const previewLoading = ref(false);
+
+const formData = reactive({
+  name: '',
+  type: 'line',
+  description: '',
+  dataSource: '',
+  timeRange: '30d',
+  customSql: '',
+  config: {
+    xAxis: '',
+    yAxis: '',
+    categoryField: '',
+    valueField: '',
+    height: 300,
+    showLegend: true,
+    showGrid: true,
+    showDataLabels: false,
+    primaryColor: '#409eff',
+    backgroundColor: '#ffffff'
+  }
+});
+
+const formRules = {
+  name: [
+    { required: true, message: '请输入图表名称', trigger: 'blur' },
+    { min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' }
+  ],
+  type: [
+    { required: true, message: '请选择图表类型', trigger: 'change' }
+  ],
+  dataSource: [
+    { required: true, message: '请选择数据源', trigger: 'change' }
+  ]
+};
+
+// 预览图表配置
+const previewOption = ref({});
+
+// 方法
+const handleClose = () => {
+  emit('update:visible', false);
+  resetForm();
+};
+
+const resetForm = () => {
+  formRef.value?.resetFields();
+  Object.assign(formData, {
+    name: '',
+    type: 'line',
+    description: '',
+    dataSource: '',
+    timeRange: '30d',
+    customSql: '',
+    config: {
+      xAxis: '',
+      yAxis: '',
+      categoryField: '',
+      valueField: '',
+      height: 300,
+      showLegend: true,
+      showGrid: true,
+      showDataLabels: false,
+      primaryColor: '#409eff',
+      backgroundColor: '#ffffff'
+    }
+  });
+};
+
+const handlePreview = async () => {
+  try {
+    previewLoading.value = true;
+    await generatePreview();
+    ElMessage.success('预览生成成功');
+  } catch (error) {
+    ElMessage.error('预览生成失败');
+  } finally {
+    previewLoading.value = false;
+  }
+};
+
+const handleSave = async () => {
+  try {
+    await formRef.value.validate();
+    
+    saving.value = true;
+    
+    // 模拟保存操作
+    await new Promise(resolve => setTimeout(resolve, 500));
+    
+    const chartData = {
+      id: Date.now().toString(),
+      ...formData,
+      createTime: new Date().toISOString()
+    };
+    
+    emit('save', chartData);
+    ElMessage.success('图表保存成功');
+    handleClose();
+    
+  } catch (error) {
+    console.error('表单验证失败:', error);
+  } finally {
+    saving.value = false;
+  }
+};
+
+const generatePreview = async () => {
+  // 模拟数据生成
+  const mockData = generateMockData();
+  
+  if (formData.type === 'pie') {
+    previewOption.value = {
+      backgroundColor: formData.config.backgroundColor,
+      tooltip: {
+        trigger: 'item',
+        formatter: '{a} <br/>{b}: {c} ({d}%)'
+      },
+      legend: formData.config.showLegend ? {
+        orient: 'vertical',
+        left: 'left'
+      } : undefined,
+      series: [
+        {
+          name: formData.name || '图表数据',
+          type: 'pie',
+          radius: '50%',
+          data: mockData.pieData,
+          label: {
+            show: formData.config.showDataLabels
+          },
+          itemStyle: {
+            color: formData.config.primaryColor
+          }
+        }
+      ]
+    };
+  } else {
+    previewOption.value = {
+      backgroundColor: formData.config.backgroundColor,
+      tooltip: {
+        trigger: 'axis'
+      },
+      legend: formData.config.showLegend ? {
+        data: [formData.name || '数据']
+      } : undefined,
+      grid: formData.config.showGrid ? {
+        left: '3%',
+        right: '4%',
+        bottom: '3%',
+        containLabel: true
+      } : undefined,
+      xAxis: {
+        type: 'category',
+        data: mockData.xAxisData
+      },
+      yAxis: {
+        type: 'value'
+      },
+      series: [
+        {
+          name: formData.name || '数据',
+          type: formData.type,
+          data: mockData.seriesData,
+          label: {
+            show: formData.config.showDataLabels
+          },
+          itemStyle: {
+            color: formData.config.primaryColor
+          },
+          areaStyle: formData.type === 'area' ? {} : undefined
+        }
+      ]
+    };
+  }
+};
+
+const generateMockData = () => {
+  const days = Array.from({ length: 7 }, (_, i) => `${i + 1}日`);
+  const values = Array.from({ length: 7 }, () => Math.floor(Math.random() * 1000) + 100);
+  
+  return {
+    xAxisData: days,
+    seriesData: values,
+    pieData: [
+      { value: 335, name: '类别1' },
+      { value: 310, name: '类别2' },
+      { value: 234, name: '类别3' },
+      { value: 135, name: '类别4' }
+    ]
+  };
+};
+
+// 监听器
+watch(() => props.visible, (newVal) => {
+  if (newVal && props.chartData) {
+    Object.assign(formData, props.chartData);
+  }
+});
+
+watch(() => [formData.type, formData.config.primaryColor, formData.config.backgroundColor], () => {
+  if (formData.type) {
+    handlePreview();
+  }
+}, { deep: true });
+</script>
+
+<style scoped>
+.add-chart-dialog {
+  .dialog-content {
+    max-height: 70vh;
+    overflow-y: auto;
+  }
+  
+  .section {
+    margin-bottom: 24px;
+    
+    .section-title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+      margin: 0 0 16px 0;
+    }
+  }
+  
+  .option-item {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  
+  .chart-preview {
+    .preview-container {
+      border: 1px solid #e4e7ed;
+      border-radius: 8px;
+      padding: 16px;
+      background: #fafafa;
+    }
+  }
+  
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+  }
+}
+</style>

+ 427 - 0
src/app/shop/admin/data/report/components/add-dashboard-dialog.vue

@@ -0,0 +1,427 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="新增看板"
+    width="60%"
+    class="add-dashboard-dialog"
+    @close="handleClose"
+  >
+    <div class="dialog-content">
+      <!-- 看板基本信息 -->
+      <div class="section">
+        <h4 class="section-title">基本信息</h4>
+        <el-form :model="formData" :rules="formRules" ref="formRef" label-width="100px">
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="看板名称" prop="name">
+                <el-input v-model="formData.name" placeholder="请输入看板名称" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="看板类型" prop="type">
+                <el-select v-model="formData.type" placeholder="请选择看板类型" style="width: 100%">
+                  <el-option label="销售看板" value="sales" />
+                  <el-option label="用户看板" value="user" />
+                  <el-option label="订单看板" value="order" />
+                  <el-option label="商品看板" value="goods" />
+                  <el-option label="自定义看板" value="custom" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-form-item label="看板描述" prop="description">
+            <el-input 
+              v-model="formData.description" 
+              type="textarea" 
+              :rows="3"
+              placeholder="请输入看板描述"
+            />
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <!-- 图表配置 -->
+      <div class="section">
+        <div class="section-header">
+          <h4 class="section-title">图表配置</h4>
+          <el-button type="primary" size="small" @click="addChart">
+            <el-icon><Plus /></el-icon>
+            添加图表
+          </el-button>
+        </div>
+        
+        <div class="charts-list" v-if="formData.charts.length > 0">
+          <div 
+            v-for="(chart, index) in formData.charts" 
+            :key="index"
+            class="chart-item"
+          >
+            <div class="chart-preview">
+              <div class="chart-icon" :class="chart.type">
+                <el-icon><component :is="getChartIcon(chart.type)" /></el-icon>
+              </div>
+              <div class="chart-info">
+                <div class="chart-name">{{ chart.name || '未命名图表' }}</div>
+                <div class="chart-desc">{{ getChartTypeLabel(chart.type) }}</div>
+              </div>
+            </div>
+            <div class="chart-actions">
+              <el-button size="small" @click="editChart(index)">
+                <el-icon><Edit /></el-icon>
+                编辑
+              </el-button>
+              <el-button size="small" type="danger" @click="removeChart(index)">
+                <el-icon><Delete /></el-icon>
+                删除
+              </el-button>
+            </div>
+          </div>
+        </div>
+        
+        <div v-else class="empty-charts">
+          <el-empty description="暂无图表,点击上方按钮添加图表" />
+        </div>
+      </div>
+
+      <!-- 布局设置 -->
+      <div class="section">
+        <h4 class="section-title">布局设置</h4>
+        <el-form :model="formData" label-width="100px">
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="列数">
+                <el-slider 
+                  v-model="formData.layout.columns" 
+                  :min="1" 
+                  :max="4" 
+                  :marks="{ 1: '1列', 2: '2列', 3: '3列', 4: '4列' }"
+                  show-stops
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="间距">
+                <el-slider 
+                  v-model="formData.layout.gap" 
+                  :min="8" 
+                  :max="32" 
+                  :marks="{ 8: '紧密', 16: '适中', 24: '宽松', 32: '很宽' }"
+                  show-stops
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-form-item label="自动刷新">
+            <el-switch 
+              v-model="formData.autoRefresh" 
+              active-text="开启" 
+              inactive-text="关闭"
+            />
+            <el-select 
+              v-if="formData.autoRefresh"
+              v-model="formData.refreshInterval"
+              style="margin-left: 16px; width: 120px;"
+            >
+              <el-option label="30秒" :value="30" />
+              <el-option label="1分钟" :value="60" />
+              <el-option label="5分钟" :value="300" />
+              <el-option label="10分钟" :value="600" />
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleClose">取消</el-button>
+        <el-button type="primary" @click="handleSave" :loading="saving">
+          保存看板
+        </el-button>
+      </div>
+    </template>
+
+    <!-- 图表编辑对话框 -->
+    <AddChartDialog 
+      v-model:visible="chartDialogVisible"
+      :chart-data="currentChart"
+      @save="handleChartSave"
+    />
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import {
+  Plus,
+  Edit,
+  Delete,
+  TrendCharts,
+  PieChart,
+  DataLine,
+  Histogram
+} from '@element-plus/icons-vue';
+import AddChartDialog from './add-chart-dialog.vue';
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  }
+});
+
+const emit = defineEmits(['update:visible', 'save']);
+
+// 响应式数据
+const formRef = ref();
+const saving = ref(false);
+const chartDialogVisible = ref(false);
+const currentChart = ref({});
+const currentChartIndex = ref(-1);
+
+const formData = reactive({
+  name: '',
+  type: '',
+  description: '',
+  charts: [],
+  layout: {
+    columns: 2,
+    gap: 16
+  },
+  autoRefresh: false,
+  refreshInterval: 300
+});
+
+const formRules = {
+  name: [
+    { required: true, message: '请输入看板名称', trigger: 'blur' },
+    { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
+  ],
+  type: [
+    { required: true, message: '请选择看板类型', trigger: 'change' }
+  ]
+};
+
+// 计算属性
+const getChartIcon = (type) => {
+  const icons = {
+    line: DataLine,
+    bar: Histogram,
+    pie: PieChart,
+    area: TrendCharts
+  };
+  return icons[type] || TrendCharts;
+};
+
+const getChartTypeLabel = (type) => {
+  const labels = {
+    line: '折线图',
+    bar: '柱状图',
+    pie: '饼图',
+    area: '面积图'
+  };
+  return labels[type] || '未知类型';
+};
+
+// 方法
+const handleClose = () => {
+  emit('update:visible', false);
+  resetForm();
+};
+
+const resetForm = () => {
+  formRef.value?.resetFields();
+  Object.assign(formData, {
+    name: '',
+    type: '',
+    description: '',
+    charts: [],
+    layout: {
+      columns: 2,
+      gap: 16
+    },
+    autoRefresh: false,
+    refreshInterval: 300
+  });
+};
+
+const addChart = () => {
+  currentChart.value = {
+    name: '',
+    type: 'line',
+    dataSource: '',
+    config: {}
+  };
+  currentChartIndex.value = -1;
+  chartDialogVisible.value = true;
+};
+
+const editChart = (index) => {
+  currentChart.value = { ...formData.charts[index] };
+  currentChartIndex.value = index;
+  chartDialogVisible.value = true;
+};
+
+const removeChart = (index) => {
+  formData.charts.splice(index, 1);
+  ElMessage.success('图表删除成功');
+};
+
+const handleChartSave = (chartData) => {
+  if (currentChartIndex.value >= 0) {
+    // 编辑现有图表
+    formData.charts[currentChartIndex.value] = { ...chartData };
+    ElMessage.success('图表更新成功');
+  } else {
+    // 添加新图表
+    formData.charts.push({ ...chartData });
+    ElMessage.success('图表添加成功');
+  }
+  chartDialogVisible.value = false;
+};
+
+const handleSave = async () => {
+  try {
+    await formRef.value.validate();
+    
+    if (formData.charts.length === 0) {
+      ElMessage.warning('请至少添加一个图表');
+      return;
+    }
+    
+    saving.value = true;
+    
+    // 模拟保存操作
+    await new Promise(resolve => setTimeout(resolve, 1000));
+    
+    const dashboardData = {
+      id: Date.now().toString(),
+      ...formData,
+      createTime: new Date().toISOString()
+    };
+    
+    emit('save', dashboardData);
+    ElMessage.success('看板创建成功');
+    handleClose();
+    
+  } catch (error) {
+    console.error('表单验证失败:', error);
+  } finally {
+    saving.value = false;
+  }
+};
+</script>
+
+<style scoped>
+.add-dashboard-dialog {
+  .dialog-content {
+    max-height: 70vh;
+    overflow-y: auto;
+  }
+  
+  .section {
+    margin-bottom: 32px;
+    
+    .section-title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+      margin: 0 0 16px 0;
+    }
+    
+    .section-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+      
+      .section-title {
+        margin: 0;
+      }
+    }
+  }
+  
+  .charts-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+    
+    .chart-item {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 16px;
+      border: 1px solid #e4e7ed;
+      border-radius: 8px;
+      background: #fafafa;
+      
+      .chart-preview {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+        
+        .chart-icon {
+          width: 40px;
+          height: 40px;
+          border-radius: 8px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          font-size: 20px;
+          
+          &.line {
+            background: linear-gradient(135deg, #409eff, #66b1ff);
+            color: white;
+          }
+          
+          &.bar {
+            background: linear-gradient(135deg, #67c23a, #85ce61);
+            color: white;
+          }
+          
+          &.pie {
+            background: linear-gradient(135deg, #e6a23c, #ebb563);
+            color: white;
+          }
+          
+          &.area {
+            background: linear-gradient(135deg, #f56c6c, #f78989);
+            color: white;
+          }
+        }
+        
+        .chart-info {
+          .chart-name {
+            font-size: 14px;
+            font-weight: 500;
+            color: #303133;
+            margin-bottom: 4px;
+          }
+          
+          .chart-desc {
+            font-size: 12px;
+            color: #909399;
+          }
+        }
+      }
+      
+      .chart-actions {
+        display: flex;
+        gap: 8px;
+      }
+    }
+  }
+  
+  .empty-charts {
+    padding: 40px 0;
+    text-align: center;
+  }
+  
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+  }
+}
+</style>

+ 658 - 0
src/app/shop/admin/data/report/components/dashboard-tabs.vue

@@ -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>

+ 595 - 0
src/app/shop/admin/data/report/components/data-display-dialog.vue

@@ -0,0 +1,595 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    :title="dialogTitle"
+    width="80%"
+    class="data-display-dialog"
+    @close="handleClose"
+  >
+    <div class="dialog-content">
+      <!-- 数据概览 -->
+      <div class="data-overview">
+        <div class="overview-card">
+          <div class="overview-icon" :class="dataType">
+            <el-icon><component :is="iconComponent" /></el-icon>
+          </div>
+          <div class="overview-content">
+            <div class="overview-value">{{ overviewData.value }}</div>
+            <div class="overview-label">{{ overviewData.label }}</div>
+            <div class="overview-trend" :class="overviewData.trend > 0 ? 'positive' : 'negative'">
+              <el-icon><ArrowUp v-if="overviewData.trend > 0" /><ArrowDown v-else /></el-icon>
+              <span>{{ Math.abs(overviewData.trend) }}%</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 时间范围选择 -->
+      <div class="time-range-selector">
+        <el-radio-group v-model="timeRange" @change="handleTimeRangeChange">
+          <el-radio-button label="today">今日</el-radio-button>
+          <el-radio-button label="week">本周</el-radio-button>
+          <el-radio-button label="month">本月</el-radio-button>
+          <el-radio-button label="quarter">本季度</el-radio-button>
+          <el-radio-button label="year">本年</el-radio-button>
+        </el-radio-group>
+        <el-date-picker
+          v-model="customDateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="handleCustomDateChange"
+          style="margin-left: 16px;"
+        />
+      </div>
+
+      <!-- 图表展示 -->
+      <div class="chart-section">
+        <div class="chart-header">
+          <h4>{{ chartTitle }}</h4>
+          <div class="chart-controls">
+            <el-radio-group v-model="chartType" @change="updateChart">
+              <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-loading="chartLoading">
+          <v-chart :option="chartOption" autoresize style="height: 400px;" />
+        </div>
+      </div>
+
+      <!-- 详细数据表格 -->
+      <div class="table-section">
+        <div class="table-header">
+          <h4>详细数据</h4>
+          <div class="table-actions">
+            <el-input
+              v-model="searchKeyword"
+              placeholder="搜索数据"
+              size="small"
+              style="width: 200px;"
+              @input="handleSearch"
+            >
+              <template #prefix>
+                <el-icon><Search /></el-icon>
+              </template>
+            </el-input>
+            <el-button size="small" @click="exportData">
+              <el-icon><Download /></el-icon>
+              导出数据
+            </el-button>
+          </div>
+        </div>
+        <div class="table-content">
+          <el-table
+            :data="filteredTableData"
+            stripe
+            border
+            v-loading="tableLoading"
+            style="width: 100%"
+          >
+            <el-table-column
+              v-for="column in 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 class="table-pagination">
+            <el-pagination
+              v-model:current-page="currentPage"
+              v-model:page-size="pageSize"
+              :page-sizes="[10, 20, 50, 100]"
+              :total="totalCount"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
+import {
+  TrendCharts,
+  Document,
+  User,
+  Box,
+  ArrowUp,
+  ArrowDown,
+  Search,
+  Download
+} from '@element-plus/icons-vue';
+import VChart from 'vue-echarts';
+import { use } from 'echarts/core';
+import {
+  CanvasRenderer
+} from 'echarts/renderers';
+import {
+  LineChart,
+  BarChart,
+  PieChart
+} from 'echarts/charts';
+import {
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  GridComponent
+} from 'echarts/components';
+
+use([
+  CanvasRenderer,
+  LineChart,
+  BarChart,
+  PieChart,
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  GridComponent
+]);
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  dataType: {
+    type: String,
+    required: true
+  }
+});
+
+const emit = defineEmits(['update:visible', 'close']);
+
+// 响应式数据
+const timeRange = ref('month');
+const customDateRange = ref([]);
+const chartType = ref('line');
+const searchKeyword = ref('');
+const currentPage = ref(1);
+const pageSize = ref(20);
+const chartLoading = ref(false);
+const tableLoading = ref(false);
+
+// 计算属性
+const dialogTitle = computed(() => {
+  const titles = {
+    sales: '销售数据详情',
+    orders: '订单数据详情',
+    users: '用户数据详情',
+    goods: '商品数据详情'
+  };
+  return titles[props.dataType] || '数据详情';
+});
+
+const iconComponent = computed(() => {
+  const icons = {
+    sales: TrendCharts,
+    orders: Document,
+    users: User,
+    goods: Box
+  };
+  return icons[props.dataType] || TrendCharts;
+});
+
+const chartTitle = computed(() => {
+  const titles = {
+    sales: '销售趋势图',
+    orders: '订单趋势图',
+    users: '用户增长图',
+    goods: '商品统计图'
+  };
+  return titles[props.dataType] || '数据趋势图';
+});
+
+// 模拟数据
+const overviewData = ref({
+  value: '¥0',
+  label: '总计',
+  trend: 0
+});
+
+const chartOption = ref({});
+const tableData = ref([]);
+const tableColumns = ref([]);
+const totalCount = ref(0);
+
+const filteredTableData = computed(() => {
+  if (!searchKeyword.value) {
+    return tableData.value;
+  }
+  return tableData.value.filter(row => {
+    return Object.values(row).some(value => 
+      String(value).toLowerCase().includes(searchKeyword.value.toLowerCase())
+    );
+  });
+});
+
+// 方法
+const handleClose = () => {
+  emit('update:visible', false);
+  emit('close');
+};
+
+const handleTimeRangeChange = (value) => {
+  customDateRange.value = [];
+  loadData();
+};
+
+const handleCustomDateChange = (value) => {
+  if (value && value.length === 2) {
+    timeRange.value = 'custom';
+    loadData();
+  }
+};
+
+const updateChart = () => {
+  generateChartOption();
+};
+
+const handleSearch = () => {
+  // 搜索逻辑已在计算属性中处理
+};
+
+const handleSizeChange = (size) => {
+  pageSize.value = size;
+  loadData();
+};
+
+const handleCurrentChange = (page) => {
+  currentPage.value = page;
+  loadData();
+};
+
+const exportData = () => {
+  ElMessage.success('数据导出功能开发中...');
+};
+
+const loadData = async () => {
+  try {
+    chartLoading.value = true;
+    tableLoading.value = true;
+    
+    // 模拟API调用
+    await new Promise(resolve => setTimeout(resolve, 1000));
+    
+    // 根据数据类型生成不同的模拟数据
+    generateMockData();
+    generateChartOption();
+    
+  } catch (error) {
+    ElMessage.error('数据加载失败');
+  } finally {
+    chartLoading.value = false;
+    tableLoading.value = false;
+  }
+};
+
+const generateMockData = () => {
+  const mockData = {
+    sales: {
+      overview: { value: '¥1,234,567', label: '总销售额', trend: 12.5 },
+      columns: [
+        { prop: 'date', label: '日期', width: 120 },
+        { prop: 'amount', label: '销售额', width: 120, formatter: (val) => `¥${val}` },
+        { prop: 'orders', label: '订单数', width: 100 },
+        { prop: 'avgOrder', label: '客单价', width: 120, formatter: (val) => `¥${val}` }
+      ],
+      data: Array.from({ length: 50 }, (_, i) => ({
+        date: `2024-01-${String(i + 1).padStart(2, '0')}`,
+        amount: Math.floor(Math.random() * 50000) + 10000,
+        orders: Math.floor(Math.random() * 100) + 20,
+        avgOrder: Math.floor(Math.random() * 500) + 200
+      }))
+    },
+    orders: {
+      overview: { value: '8,456', label: '总订单数', trend: 8.3 },
+      columns: [
+        { prop: 'orderNo', label: '订单号', width: 150 },
+        { prop: 'date', label: '下单时间', width: 150 },
+        { prop: 'amount', label: '订单金额', width: 120, formatter: (val) => `¥${val}` },
+        { prop: 'status', label: '订单状态', width: 100 }
+      ],
+      data: Array.from({ length: 50 }, (_, i) => ({
+        orderNo: `ORD${String(i + 1).padStart(6, '0')}`,
+        date: `2024-01-${String(Math.floor(Math.random() * 28) + 1).padStart(2, '0')} 10:30:00`,
+        amount: Math.floor(Math.random() * 1000) + 100,
+        status: ['已完成', '进行中', '已取消'][Math.floor(Math.random() * 3)]
+      }))
+    },
+    users: {
+      overview: { value: '12,345', label: '总用户数', trend: 15.2 },
+      columns: [
+        { prop: 'userId', label: '用户ID', width: 120 },
+        { prop: 'username', label: '用户名', width: 150 },
+        { prop: 'registerDate', label: '注册时间', width: 150 },
+        { prop: 'orderCount', label: '订单数', width: 100 },
+        { prop: 'totalAmount', label: '消费金额', width: 120, formatter: (val) => `¥${val}` }
+      ],
+      data: Array.from({ length: 50 }, (_, i) => ({
+        userId: `U${String(i + 1).padStart(6, '0')}`,
+        username: `用户${i + 1}`,
+        registerDate: `2024-01-${String(Math.floor(Math.random() * 28) + 1).padStart(2, '0')}`,
+        orderCount: Math.floor(Math.random() * 20) + 1,
+        totalAmount: Math.floor(Math.random() * 5000) + 500
+      }))
+    },
+    goods: {
+      overview: { value: '2,567', label: '总商品数', trend: 5.8 },
+      columns: [
+        { prop: 'goodsId', label: '商品ID', width: 120 },
+        { prop: 'goodsName', label: '商品名称', width: 200 },
+        { prop: 'category', label: '分类', width: 120 },
+        { prop: 'price', label: '价格', width: 100, formatter: (val) => `¥${val}` },
+        { prop: 'stock', label: '库存', width: 100 },
+        { prop: 'sales', label: '销量', width: 100 }
+      ],
+      data: Array.from({ length: 50 }, (_, i) => ({
+        goodsId: `G${String(i + 1).padStart(6, '0')}`,
+        goodsName: `商品${i + 1}`,
+        category: ['电子产品', '服装', '食品', '家居'][Math.floor(Math.random() * 4)],
+        price: Math.floor(Math.random() * 500) + 50,
+        stock: Math.floor(Math.random() * 1000) + 10,
+        sales: Math.floor(Math.random() * 500) + 10
+      }))
+    }
+  };
+  
+  const data = mockData[props.dataType];
+  overviewData.value = data.overview;
+  tableColumns.value = data.columns;
+  tableData.value = data.data;
+  totalCount.value = data.data.length;
+};
+
+const generateChartOption = () => {
+  const chartData = generateChartData();
+  
+  if (chartType.value === 'pie') {
+    chartOption.value = {
+      tooltip: {
+        trigger: 'item',
+        formatter: '{a} <br/>{b}: {c} ({d}%)'
+      },
+      legend: {
+        orient: 'vertical',
+        left: 'left'
+      },
+      series: [
+        {
+          name: chartTitle.value,
+          type: 'pie',
+          radius: '50%',
+          data: chartData.pieData,
+          emphasis: {
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)'
+            }
+          }
+        }
+      ]
+    };
+  } else {
+    chartOption.value = {
+      tooltip: {
+        trigger: 'axis'
+      },
+      legend: {
+        data: [chartTitle.value]
+      },
+      xAxis: {
+        type: 'category',
+        data: chartData.xAxisData
+      },
+      yAxis: {
+        type: 'value'
+      },
+      series: [
+        {
+          name: chartTitle.value,
+          type: chartType.value,
+          data: chartData.seriesData
+        }
+      ]
+    };
+  }
+};
+
+const generateChartData = () => {
+  const days = Array.from({ length: 30 }, (_, i) => `${i + 1}日`);
+  const values = Array.from({ length: 30 }, () => Math.floor(Math.random() * 1000) + 100);
+  
+  return {
+    xAxisData: days,
+    seriesData: values,
+    pieData: [
+      { value: 335, name: '类别1' },
+      { value: 310, name: '类别2' },
+      { value: 234, name: '类别3' },
+      { value: 135, name: '类别4' },
+      { value: 1548, name: '类别5' }
+    ]
+  };
+};
+
+// 监听器
+watch(() => props.visible, (newVal) => {
+  if (newVal) {
+    loadData();
+  }
+});
+
+watch(() => props.dataType, () => {
+  if (props.visible) {
+    loadData();
+  }
+});
+</script>
+
+<style scoped>
+.data-display-dialog {
+  .dialog-content {
+    padding: 0;
+  }
+  
+  .data-overview {
+    margin-bottom: 24px;
+    
+    .overview-card {
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      border-radius: 12px;
+      padding: 24px;
+      color: white;
+      display: flex;
+      align-items: center;
+      gap: 20px;
+      
+      .overview-icon {
+        width: 60px;
+        height: 60px;
+        border-radius: 12px;
+        background: rgba(255, 255, 255, 0.2);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 28px;
+      }
+      
+      .overview-content {
+        flex: 1;
+        
+        .overview-value {
+          font-size: 32px;
+          font-weight: 600;
+          margin-bottom: 4px;
+        }
+        
+        .overview-label {
+          font-size: 16px;
+          opacity: 0.9;
+          margin-bottom: 8px;
+        }
+        
+        .overview-trend {
+          display: flex;
+          align-items: center;
+          gap: 4px;
+          font-size: 14px;
+          
+          &.positive {
+            color: #67c23a;
+          }
+          
+          &.negative {
+            color: #f56c6c;
+          }
+        }
+      }
+    }
+  }
+  
+  .time-range-selector {
+    display: flex;
+    align-items: center;
+    margin-bottom: 24px;
+    padding: 16px;
+    background: #f8f9fa;
+    border-radius: 8px;
+  }
+  
+  .chart-section {
+    margin-bottom: 32px;
+    
+    .chart-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+      
+      h4 {
+        margin: 0;
+        font-size: 18px;
+        color: #303133;
+      }
+    }
+    
+    .chart-container {
+      background: white;
+      border-radius: 8px;
+      padding: 16px;
+      border: 1px solid #e4e7ed;
+    }
+  }
+  
+  .table-section {
+    .table-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+      
+      h4 {
+        margin: 0;
+        font-size: 18px;
+        color: #303133;
+      }
+      
+      .table-actions {
+        display: flex;
+        gap: 12px;
+        align-items: center;
+      }
+    }
+    
+    .table-content {
+      background: white;
+      border-radius: 8px;
+      
+      .table-pagination {
+        padding: 16px;
+        display: flex;
+        justify-content: center;
+      }
+    }
+  }
+}
+</style>

+ 709 - 0
src/app/shop/admin/data/report/components/dimension-management.vue

@@ -0,0 +1,709 @@
+<template>
+  <div class="dimension-management report-container">
+    <el-card>
+      <template #header>
+        <div class="report-header">
+          <div class="header-content">
+            <h2 class="report-title">维度管理</h2>
+            <p class="report-subtitle">管理数据报表的维度配置</p>
+          </div>
+          <div class="header-actions">
+            <el-button type="primary" @click="handleAdd" :icon="Plus">
+              新增维度
+            </el-button>
+            <el-button @click="refreshList" :icon="Refresh" class="sa-button-refresh">
+              刷新
+            </el-button>
+          </div>
+        </div>
+      </template>
+
+      <!-- 搜索区域 -->
+      <div class="search-section">
+        <el-form :model="searchForm" inline class="search-form">
+          <el-form-item>
+            <el-input v-model="searchForm.keyword" placeholder="请输入维度名称或编码" clearable
+              @keyup.enter="handleSearch" class="search-input">
+              <template #prefix>
+                <el-icon>
+                  <Search />
+                </el-icon>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleSearch" :icon="Search">
+              搜索
+            </el-button>
+            <el-button @click="handleReset" :icon="RefreshLeft">
+              重置
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <!-- 数据表格 -->
+      <div class="table-section">
+        <el-table v-loading="loading" :data="tableData" stripe border style="width: 100%"
+          @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" />
+          <el-table-column prop="id" label="ID" width="80" />
+          <el-table-column prop="name" label="维度名称" min-width="150">
+            <template #default="{ row }">
+              <div class="dimension-name">
+                <el-tag :type="getDimensionTypeTag(row.type)" size="small" style="margin-right: 8px;">
+                  {{ getDimensionTypeLabel(row.type) }}
+                </el-tag>
+                <span>{{ row.name }}</span>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="code" label="维度编码" width="120" />
+          <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
+          <el-table-column prop="dataType" label="数据类型" width="100">
+            <template #default="{ row }">
+              <el-tag size="small" :type="getDataTypeTag(row.dataType)">
+                {{ getDataTypeLabel(row.dataType) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="unit" label="单位" width="80" />
+          <el-table-column prop="status" label="状态" width="80">
+            <template #default="{ row }">
+              <el-switch v-model="row.status" :active-value="1" :inactive-value="0" @change="handleStatusChange(row)" />
+            </template>
+          </el-table-column>
+          <el-table-column prop="createTime" label="创建时间" width="160">
+            <template #default="{ row }">
+              {{ formatDate(row.createTime) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="180" fixed="right">
+            <template #default="{ row }">
+              <el-button size="small" type="primary" link @click="handleEdit(row)">
+                <el-icon>
+                  <Edit />
+                </el-icon>
+                编辑
+              </el-button>
+              <el-button size="small" type="info" link @click="handleView(row)">
+                <el-icon>
+                  <View />
+                </el-icon>
+                查看
+              </el-button>
+              <el-button size="small" type="danger" link @click="handleDelete(row)">
+                <el-icon>
+                  <Delete />
+                </el-icon>
+                删除
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 批量操作 -->
+        <div class="batch-actions" v-if="selectedRows.length > 0">
+          <span class="batch-info">
+            已选择 {{ selectedRows.length }} 项
+          </span>
+          <div class="batch-buttons">
+            <el-button type="danger" size="small" @click="handleBatchDelete">
+              批量删除
+            </el-button>
+            <el-button type="success" size="small" @click="handleBatchEnable">
+              批量启用
+            </el-button>
+            <el-button type="warning" size="small" @click="handleBatchDisable">
+              批量禁用
+            </el-button>
+          </div>
+        </div>
+
+        <!-- 分页 -->
+        <div class="pagination-section">
+          <el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.size"
+            :page-sizes="[10, 20, 50, 100]" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
+            @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+        </div>
+      </div>
+    </el-card>
+
+    <!-- 添加/编辑对话框 -->
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" :close-on-click-modal="false"
+      @close="handleDialogClose" class="report-dialog sa-dialog">
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="维度名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入维度名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="维度编码" prop="code">
+              <el-input v-model="formData.code" placeholder="请输入维度编码" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="维度类型" prop="type">
+              <el-select v-model="formData.type" placeholder="请选择维度类型" style="width: 100%;">
+                <el-option label="时间维度" value="time" />
+                <el-option label="地区维度" value="region" />
+                <el-option label="产品维度" value="product" />
+                <el-option label="用户维度" value="user" />
+                <el-option label="渠道维度" value="channel" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="数据类型" prop="dataType">
+              <el-select v-model="formData.dataType" placeholder="请选择数据类型" style="width: 100%;">
+                <el-option label="字符串" value="string" />
+                <el-option label="数字" value="number" />
+                <el-option label="日期" value="date" />
+                <el-option label="布尔值" value="boolean" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="单位" prop="unit">
+              <el-input v-model="formData.unit" placeholder="请输入单位" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态" prop="status">
+              <el-radio-group v-model="formData.status">
+                <el-radio :label="1">启用</el-radio>
+                <el-radio :label="0">禁用</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="描述" prop="description">
+          <el-input v-model="formData.description" type="textarea" :rows="3"
+            placeholder="请输入描述" />
+        </el-form-item>
+
+        <el-form-item label="配置项" prop="config">
+          <el-input v-model="formData.config" type="textarea" :rows="4" placeholder="请输入配置项(JSON格式)" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleSubmit" :loading="submitting">
+            {{ isEdit ? '更新' : '创建' }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 查看详情对话框 -->
+    <el-dialog v-model="viewDialogVisible" title="维度详情" width="500px">
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="维度名称">{{ viewData.name }}</el-descriptions-item>
+        <el-descriptions-item label="维度编码">{{ viewData.code }}</el-descriptions-item>
+        <el-descriptions-item label="维度类型">
+          <el-tag :type="getDimensionTypeTag(viewData.type)" size="small">
+            {{ getDimensionTypeLabel(viewData.type) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="数据类型">
+          <el-tag :type="getDataTypeTag(viewData.dataType)" size="small">
+            {{ getDataTypeLabel(viewData.dataType) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="单位">{{ viewData.unit || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="viewData.status === 1 ? 'success' : 'danger'" size="small">
+            {{ viewData.status === 1 ? '启用' : '禁用' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间" :span="2">
+          {{ formatDate(viewData.createTime) }}
+        </el-descriptions-item>
+        <el-descriptions-item label="描述" :span="2">
+          {{ viewData.description || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="配置项" :span="2">
+          <pre v-if="viewData.config" class="config-display">{{ formatConfig(viewData.config) }}</pre>
+          <span v-else>-</span>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+  Plus, Refresh, Search, RefreshLeft, Edit, View, Delete,
+  Check, Close
+} from '@element-plus/icons-vue'
+import { api } from '../../data.service.js'
+
+// 响应式数据
+const loading = ref(false)
+const submitting = ref(false)
+const dialogVisible = ref(false)
+const viewDialogVisible = ref(false)
+const isEdit = ref(false)
+const formRef = ref()
+const selectedRows = ref([])
+
+// 搜索表单
+const searchForm = reactive({
+  keyword: ''
+})
+
+// 分页数据
+const pagination = reactive({
+  page: 1,
+  size: 20,
+  total: 0
+})
+
+// 表格数据
+const tableData = ref([])
+
+// 表单数据
+const formData = reactive({
+  id: null,
+  name: '',
+  code: '',
+  type: '',
+  dataType: '',
+  unit: '',
+  description: '',
+  config: '',
+  status: 1
+})
+
+// 查看数据
+const viewData = reactive({})
+
+// 表单验证规则
+const formRules = computed(() => ({
+  name: [
+    { required: true, message: '请输入维度名称', trigger: 'blur' },
+    { min: 2, max: 50, message: '维度名称长度应在2-50个字符之间', trigger: 'blur' }
+  ],
+  code: [
+    { required: true, message: '请输入维度编码', trigger: 'blur' },
+    { pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, message: '维度编码格式不正确,应以字母开头,只能包含字母、数字和下划线', trigger: 'blur' }
+  ],
+  type: [
+    { required: true, message: '请选择维度类型', trigger: 'change' }
+  ],
+  dataType: [
+    { required: true, message: '请选择数据类型', trigger: 'change' }
+  ]
+}))
+
+// 计算属性
+const dialogTitle = computed(() => isEdit.value ? '编辑维度' : '新增维度')
+
+// 工具方法
+const getDimensionTypeLabel = (type) => {
+  const typeMap = {
+    time: '时间维度',
+    region: '地区维度',
+    product: '产品维度',
+    user: '用户维度',
+    channel: '渠道维度'
+  }
+  return typeMap[type] || type
+}
+
+const getDimensionTypeTag = (type) => {
+  const tagMap = {
+    time: 'primary',
+    region: 'success',
+    product: 'warning',
+    user: 'info',
+    channel: 'danger'
+  }
+  return tagMap[type] || ''
+}
+
+const getDataTypeLabel = (dataType) => {
+  const typeMap = {
+    string: '字符串',
+    number: '数字',
+    date: '日期',
+    boolean: '布尔值'
+  }
+  return typeMap[dataType] || dataType
+}
+
+const getDataTypeTag = (dataType) => {
+  const tagMap = {
+    string: 'primary',
+    number: 'success',
+    date: 'warning',
+    boolean: 'info'
+  }
+  return tagMap[dataType] || ''
+}
+
+const formatDate = (dateStr) => {
+  if (!dateStr) return '-'
+  return new Date(dateStr).toLocaleString('zh-CN')
+}
+
+const formatConfig = (config) => {
+  try {
+    return JSON.stringify(JSON.parse(config), null, 2)
+  } catch {
+    return config
+  }
+}
+
+// 数据加载
+const loadData = async () => {
+  loading.value = true
+  try {
+    const params = {
+      page: pagination.page,
+      size: pagination.size,
+      ...searchForm
+    }
+
+    const response = await api.operating.dimensions.list(params)
+    if (response.code === 200) {
+      tableData.value = response.data.list || []
+      pagination.total = response.data.total || 0
+    } else {
+      ElMessage.error(response.message || '数据加载失败')
+    }
+  } catch (error) {
+    console.error('加载维度数据失败:', error)
+    ElMessage.error('数据加载失败')
+    // 模拟数据
+    tableData.value = generateMockData()
+    pagination.total = 50
+  } finally {
+    loading.value = false
+  }
+}
+
+// 生成模拟数据
+const generateMockData = () => {
+  const types = ['time', 'region', 'product', 'user', 'channel']
+  const dataTypes = ['string', 'number', 'date', 'boolean']
+  const data = []
+
+  for (let i = 1; i <= 20; i++) {
+    data.push({
+      id: i,
+      name: `维度${i}`,
+      code: `dimension_${i}`,
+      type: types[Math.floor(Math.random() * types.length)],
+      dataType: dataTypes[Math.floor(Math.random() * dataTypes.length)],
+      unit: i % 3 === 0 ? '个' : i % 3 === 1 ? '元' : '',
+      description: `这是维度${i}的描述信息`,
+      config: i % 4 === 0 ? JSON.stringify({ format: 'YYYY-MM-DD', precision: 2 }) : '',
+      status: Math.random() > 0.3 ? 1 : 0,
+      createTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString()
+    })
+  }
+
+  return data
+}
+
+// 事件处理
+const handleAdd = () => {
+  isEdit.value = false
+  resetForm()
+  dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+  isEdit.value = true
+  Object.assign(formData, row)
+  dialogVisible.value = true
+}
+
+const handleView = (row) => {
+  Object.assign(viewData, row)
+  viewDialogVisible.value = true
+}
+
+const handleDelete = async (row) => {
+  try {
+    await ElMessageBox.confirm(
+      `确定要删除维度 "${row.name}" 吗?`,
+      '删除确认',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+
+    const response = await api.operating.dimensions.delete(row.id)
+    if (response.code === 200) {
+      ElMessage.success('删除成功')
+      loadData()
+    } else {
+      ElMessage.error(response.message || '删除失败')
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除维度失败:', error)
+      ElMessage.error('删除失败')
+    }
+  }
+}
+
+const handleStatusChange = async (row) => {
+  try {
+    const response = await api.operating.dimensions.update(row.id, {
+      status: row.status
+    })
+    if (response.code === 200) {
+      ElMessage.success('状态更新成功')
+    } else {
+      ElMessage.error(response.message || '状态更新失败')
+      // 回滚状态
+      row.status = row.status === 1 ? 0 : 1
+    }
+  } catch (error) {
+    console.error('更新状态失败:', error)
+    ElMessage.error('状态更新失败')
+    // 回滚状态
+    row.status = row.status === 1 ? 0 : 1
+  }
+}
+
+const handleSearch = () => {
+  pagination.page = 1
+  loadData()
+}
+
+const handleReset = () => {
+  Object.assign(searchForm, {
+    keyword: ''
+  })
+  pagination.page = 1
+  loadData()
+}
+
+const refreshList = () => {
+  loadData()
+}
+
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection
+}
+
+const handleBatchDelete = async () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请选择要删除的项目')
+    return
+  }
+
+  try {
+    await ElMessageBox.confirm(
+      `确定要批量删除 ${selectedRows.value.length} 个项目吗?`,
+      '批量删除确认',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+
+    const ids = selectedRows.value.map(row => row.id)
+    // 这里应该调用批量删除API
+    ElMessage.success('批量删除成功')
+    loadData()
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('批量删除失败:', error)
+      ElMessage.error('批量删除失败')
+    }
+  }
+}
+
+const handleBatchEnable = async () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请选择要启用的项目')
+    return
+  }
+
+  try {
+    const ids = selectedRows.value.map(row => row.id)
+    // 这里应该调用批量更新API
+    ElMessage.success('批量启用成功')
+    loadData()
+  } catch (error) {
+    console.error('批量启用失败:', error)
+    ElMessage.error('批量启用失败')
+  }
+}
+
+const handleBatchDisable = async () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请选择要禁用的项目')
+    return
+  }
+
+  try {
+    const ids = selectedRows.value.map(row => row.id)
+    // 这里应该调用批量更新API
+    ElMessage.success('批量禁用成功')
+    loadData()
+  } catch (error) {
+    console.error('批量禁用失败:', error)
+    ElMessage.error('批量禁用失败')
+  }
+}
+
+const handleSizeChange = (size) => {
+  pagination.size = size
+  pagination.page = 1
+  loadData()
+}
+
+const handleCurrentChange = (page) => {
+  pagination.page = page
+  loadData()
+}
+
+const handleSubmit = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+
+    // 验证配置项JSON格式
+    if (formData.config) {
+      try {
+        JSON.parse(formData.config)
+      } catch {
+        ElMessage.error('配置项格式错误,请输入有效的JSON格式')
+        return
+      }
+    }
+
+    submitting.value = true
+
+    const submitData = { ...formData }
+    delete submitData.id
+
+    let response
+    if (isEdit.value) {
+      response = await api.operating.dimensions.update(formData.id, submitData)
+    } else {
+      response = await api.operating.dimensions.add(submitData)
+    }
+
+    if (response.code === 200) {
+      ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
+      dialogVisible.value = false
+      loadData()
+    } else {
+      ElMessage.error(response.message || '操作失败')
+    }
+  } catch (error) {
+    if (error !== false) { // 不是表单验证错误
+      console.error('提交失败:', error)
+      ElMessage.error('操作失败')
+    }
+  } finally {
+    submitting.value = false
+  }
+}
+
+const handleDialogClose = () => {
+  resetForm()
+}
+
+const resetForm = () => {
+  Object.assign(formData, {
+    id: null,
+    name: '',
+    code: '',
+    type: '',
+    dataType: '',
+    unit: '',
+    description: '',
+    config: '',
+    status: 1
+  })
+
+  if (formRef.value) {
+    formRef.value.clearValidate()
+  }
+}
+
+// 生命周期
+onMounted(() => {
+  loadData()
+})
+</script>
+
+<style scoped>
+@import '../styles/report-common.scss';
+
+.dimension-management {
+  .dimension-name {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    .dimension-icon {
+      width: 20px;
+      height: 20px;
+      border-radius: 4px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 12px;
+      color: white;
+
+      &.time {
+        background: #409eff;
+      }
+
+      &.region {
+        background: #67c23a;
+      }
+
+      &.product {
+        background: #e6a23c;
+      }
+
+      &.user {
+        background: #f56c6c;
+      }
+
+      &.channel {
+        background: #909399;
+      }
+    }
+  }
+
+  .config-display {
+    background: var(--sa-background-assist);
+    border: 1px solid var(--sa-space);
+    border-radius: 4px;
+    padding: 12px;
+    font-family: 'Courier New', monospace;
+    font-size: 12px;
+    white-space: pre-wrap;
+    max-height: 200px;
+    overflow-y: auto;
+  }
+}
+</style>

+ 555 - 0
src/app/shop/admin/data/report/components/report-chart.vue

@@ -0,0 +1,555 @@
+<template>
+  <div class="report-chart">
+    <el-card>
+      <template #header>
+        <div class="chart-header">
+          <div class="chart-title-section">
+            <h3 class="chart-title">{{ title || '默认图表' }}</h3>
+            <p class="chart-subtitle" v-if="subtitle">{{ subtitle }}</p>
+          </div>
+          <div class="chart-actions">
+            <!-- 图表类型切换 -->
+            <el-radio-group v-model="currentType" size="small" @change="handleTypeChange" class="chart-type-selector">
+              <el-radio-button
+                v-for="type in availableTypes"
+                :key="type.value"
+                :label="type.value"
+                :disabled="!type.enabled"
+              >
+                {{ type.label }}
+              </el-radio-button>
+            </el-radio-group>
+            
+            <div class="chart-controls">
+              <!-- 时间范围选择 -->
+              <el-date-picker
+                v-if="showDatePicker"
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                size="small"
+                @change="handleDateChange"
+              />
+              
+              <!-- 刷新按钮 -->
+              <el-button size="small" @click="refreshChart" class="sa-button-refresh">
+                <el-icon><Refresh /></el-icon>
+              </el-button>
+              
+              <!-- 全屏按钮 -->
+              <el-button size="small" @click="toggleFullscreen">
+                <el-icon><FullScreen /></el-icon>
+              </el-button>
+            </div>
+          </div>
+        </div>
+      </template>
+      
+      <div class="chart-container" :class="{ 'fullscreen': isFullscreen }" v-loading="loading">
+        <v-chart
+          ref="chartRef"
+          :option="chartOption"
+          :autoresize="true"
+          :style="chartStyle"
+          @click="handleChartClick"
+        />
+        
+        <!-- 无数据状态 -->
+        <div v-if="!loading && (!data || data.length === 0)" class="no-data">
+          <el-empty description="暂无数据" />
+        </div>
+      </div>
+      
+      <!-- 图表说明 -->
+      <div v-if="description" class="chart-description">
+        <el-text type="info" size="small">{{ description }}</el-text>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import { Refresh, FullScreen } from '@element-plus/icons-vue'
+import VChart from 'vue-echarts'
+import * as echarts from 'echarts'
+
+// Props
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  subtitle: {
+    type: String,
+    default: ''
+  },
+  description: {
+    type: String,
+    default: ''
+  },
+  data: {
+    type: Array,
+    default: () => []
+  },
+  type: {
+    type: String,
+    default: 'line',
+    validator: (value) => ['line', 'bar', 'pie', 'scatter', 'radar', 'funnel'].includes(value)
+  },
+  height: {
+    type: [String, Number],
+    default: 400
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  },
+  showDatePicker: {
+    type: Boolean,
+    default: true
+  },
+  colors: {
+    type: Array,
+    default: () => ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
+  },
+  xAxisKey: {
+    type: String,
+    default: 'x'
+  },
+  yAxisKey: {
+    type: String,
+    default: 'y'
+  },
+  seriesKey: {
+    type: String,
+    default: 'series'
+  },
+  options: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+// Emits
+const emit = defineEmits(['refresh', 'dateChange', 'chartClick', 'typeChange'])
+
+// 响应式数据
+const chartRef = ref()
+const currentType = ref(props.type)
+const dateRange = ref([])
+const isFullscreen = ref(false)
+
+// 可用的图表类型
+const availableTypes = computed(() => [
+  { label: '折线图', value: 'line', enabled: true },
+  { label: '柱状图', value: 'bar', enabled: true },
+  { label: '饼图', value: 'pie', enabled: true },
+  { label: '散点图', value: 'scatter', enabled: true },
+  { label: '雷达图', value: 'radar', enabled: false },
+  { label: '漏斗图', value: 'funnel', enabled: false }
+])
+
+// 计算属性
+const chartStyle = computed(() => ({
+  height: typeof props.height === 'number' ? `${props.height}px` : props.height,
+  width: '100%'
+}))
+
+const chartOption = computed(() => {
+  if (!props.data || props.data.length === 0) {
+    return {}
+  }
+  
+  const baseOption = {
+    color: props.colors,
+    tooltip: {
+      trigger: currentType.value === 'pie' ? 'item' : 'axis',
+      axisPointer: {
+        type: currentType.value === 'line' ? 'cross' : 'shadow'
+      },
+      backgroundColor: 'rgba(50, 50, 50, 0.9)',
+      borderColor: '#333',
+      textStyle: {
+        color: '#fff'
+      }
+    },
+    legend: {
+      show: true,
+      top: 'top',
+      textStyle: {
+        color: 'var(--sa-subtitle)'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      top: '10%',
+      containLabel: true
+    },
+    ...props.options
+  }
+  
+  switch (currentType.value) {
+    case 'line':
+      return generateLineChart(baseOption)
+    case 'bar':
+      return generateBarChart(baseOption)
+    case 'pie':
+      return generatePieChart(baseOption)
+    case 'scatter':
+      return generateScatterChart(baseOption)
+    case 'radar':
+      return generateRadarChart(baseOption)
+    case 'funnel':
+      return generateFunnelChart(baseOption)
+    default:
+      return generateLineChart(baseOption)
+  }
+})
+
+// 图表生成方法
+const generateLineChart = (baseOption) => {
+  const xData = props.data.map(item => item[props.xAxisKey])
+  const series = extractSeries()
+  
+  return {
+    ...baseOption,
+    xAxis: {
+      type: 'category',
+      data: xData,
+      axisLine: {
+        lineStyle: {
+          color: 'var(--sa-space)'
+        }
+      },
+      axisLabel: {
+        color: 'var(--sa-subfont)'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: 'var(--sa-space)'
+        }
+      },
+      axisLabel: {
+        color: 'var(--sa-subfont)'
+      },
+      splitLine: {
+        lineStyle: {
+          color: 'var(--sa-space)',
+          type: 'dashed'
+        }
+      }
+    },
+    series: series.map(s => ({
+      ...s,
+      type: 'line',
+      smooth: true,
+      symbol: 'circle',
+      symbolSize: 6,
+      lineStyle: {
+        width: 3
+      },
+      areaStyle: {
+        opacity: 0.1
+      }
+    }))
+  }
+}
+
+const generateBarChart = (baseOption) => {
+  const xData = props.data.map(item => item[props.xAxisKey])
+  const series = extractSeries()
+  
+  return {
+    ...baseOption,
+    xAxis: {
+      type: 'category',
+      data: xData,
+      axisLine: {
+        lineStyle: {
+          color: 'var(--sa-space)'
+        }
+      },
+      axisLabel: {
+        color: 'var(--sa-subfont)'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: 'var(--sa-space)'
+        }
+      },
+      axisLabel: {
+        color: 'var(--sa-subfont)'
+      },
+      splitLine: {
+        lineStyle: {
+          color: 'var(--sa-space)',
+          type: 'dashed'
+        }
+      }
+    },
+    series: series.map(s => ({
+      ...s,
+      type: 'bar',
+      barWidth: '60%',
+      itemStyle: {
+        borderRadius: [4, 4, 0, 0]
+      }
+    }))
+  }
+}
+
+const generatePieChart = (baseOption) => {
+  const pieData = props.data.map(item => ({
+    name: item[props.xAxisKey],
+    value: item[props.yAxisKey]
+  }))
+  
+  return {
+    ...baseOption,
+    tooltip: {
+      trigger: 'item',
+      formatter: '{a} <br/>{b}: {c} ({d}%)',
+      backgroundColor: 'rgba(50, 50, 50, 0.9)',
+      borderColor: '#333',
+      textStyle: {
+        color: '#fff'
+      }
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left',
+      textStyle: {
+        color: 'var(--sa-subtitle)'
+      }
+    },
+    series: [{
+      name: props.title,
+      type: 'pie',
+      radius: ['40%', '70%'],
+      center: ['60%', '50%'],
+      data: pieData,
+      emphasis: {
+        itemStyle: {
+          shadowBlur: 10,
+          shadowOffsetX: 0,
+          shadowColor: 'rgba(0, 0, 0, 0.5)'
+        }
+      },
+      label: {
+        show: true,
+        formatter: '{b}: {d}%'
+      },
+      labelLine: {
+        show: true
+      }
+    }]
+  }
+}
+
+const generateScatterChart = (baseOption) => {
+  const scatterData = props.data.map(item => [
+    item[props.xAxisKey],
+    item[props.yAxisKey]
+  ])
+  
+  return {
+    ...baseOption,
+    xAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: 'var(--sa-space)'
+        }
+      },
+      axisLabel: {
+        color: 'var(--sa-subfont)'
+      },
+      splitLine: {
+        lineStyle: {
+          color: 'var(--sa-space)',
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: 'var(--sa-space)'
+        }
+      },
+      axisLabel: {
+        color: 'var(--sa-subfont)'
+      },
+      splitLine: {
+        lineStyle: {
+          color: 'var(--sa-space)',
+          type: 'dashed'
+        }
+      }
+    },
+    series: [{
+      name: props.title,
+      type: 'scatter',
+      data: scatterData,
+      symbolSize: 8,
+      itemStyle: {
+        opacity: 0.8
+      }
+    }]
+  }
+}
+
+const generateRadarChart = (baseOption) => {
+  // 雷达图实现
+  return baseOption
+}
+
+const generateFunnelChart = (baseOption) => {
+  // 漏斗图实现
+  return baseOption
+}
+
+// 提取系列数据
+const extractSeries = () => {
+  if (!props.data || props.data.length === 0) return []
+  
+  // 如果数据中有series字段,按series分组
+  if (props.data[0][props.seriesKey]) {
+    const seriesMap = new Map()
+    
+    props.data.forEach(item => {
+      const seriesName = item[props.seriesKey]
+      if (!seriesMap.has(seriesName)) {
+        seriesMap.set(seriesName, [])
+      }
+      seriesMap.get(seriesName).push(item[props.yAxisKey])
+    })
+    
+    return Array.from(seriesMap.entries()).map(([name, data]) => ({
+      name,
+      data
+    }))
+  } else {
+    // 单系列数据
+    return [{
+      name: props.title,
+      data: props.data.map(item => item[props.yAxisKey])
+    }]
+  }
+}
+
+// 事件处理
+const handleTypeChange = (type) => {
+  currentType.value = type
+  emit('typeChange', type)
+}
+
+const handleDateChange = (dates) => {
+  emit('dateChange', dates)
+}
+
+const refreshChart = () => {
+  emit('refresh')
+  ElMessage.success('刷新成功')
+}
+
+const handleChartClick = (params) => {
+  emit('chartClick', params)
+}
+
+const toggleFullscreen = () => {
+  isFullscreen.value = !isFullscreen.value
+  nextTick(() => {
+    if (chartRef.value) {
+      chartRef.value.resize()
+    }
+  })
+}
+
+// 监听数据变化
+watch(() => props.data, () => {
+  nextTick(() => {
+    if (chartRef.value) {
+      chartRef.value.resize()
+    }
+  })
+}, { deep: true })
+
+// 监听类型变化
+watch(() => props.type, (newType) => {
+  currentType.value = newType
+})
+
+// 窗口大小变化处理
+const handleResize = () => {
+  if (chartRef.value) {
+    chartRef.value.resize()
+  }
+}
+
+// 生命周期
+onMounted(() => {
+  window.addEventListener('resize', handleResize)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', handleResize)
+})
+
+// 暴露方法
+defineExpose({
+  resize: () => {
+    if (chartRef.value) {
+      chartRef.value.resize()
+    }
+  },
+  getChart: () => chartRef.value?.getChart(),
+  downloadImage: (name = 'chart') => {
+    const chart = chartRef.value?.getChart()
+    if (chart) {
+      const url = chart.getDataURL({
+        type: 'png',
+        backgroundColor: '#fff'
+      })
+      const link = document.createElement('a')
+      link.download = `${name}.png`
+      link.href = url
+      link.click()
+      ElMessage.success('下载成功')
+    }
+  }
+})
+</script>
+
+<style scoped>
+@import '../styles/report-common.scss';
+
+.chart-container {
+  &.fullscreen {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100vw;
+    height: 100vh;
+    z-index: 9999;
+    background: white;
+    
+    .chart-content {
+      height: calc(100vh - 60px);
+    }
+  }
+}
+</style>

+ 830 - 0
src/app/shop/admin/data/report/components/report-detail-dialog.vue

@@ -0,0 +1,830 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    :title="reportData.name || '报表详情'"
+    width="90%"
+    class="report-detail-dialog"
+    @close="handleClose"
+  >
+    <div class="dialog-content">
+      <!-- 报表头部信息 -->
+      <div class="report-header">
+        <div class="header-left">
+          <div class="report-title">
+            <h3>{{ reportData.name || '未命名报表' }}</h3>
+            <div class="report-meta">
+              <el-tag :type="getStatusType(reportData.status)">{{ getStatusText(reportData.status) }}</el-tag>
+              <span class="meta-item">创建时间:{{ formatDate(reportData.createTime) }}</span>
+              <span class="meta-item">更新时间:{{ formatDate(reportData.updateTime) }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="header-right">
+          <el-button-group>
+            <el-button @click="refreshReport" :loading="refreshing">
+              <el-icon><Refresh /></el-icon>
+              刷新
+            </el-button>
+            <el-button @click="exportReport">
+              <el-icon><Download /></el-icon>
+              导出
+            </el-button>
+            <el-button @click="editReport">
+              <el-icon><Edit /></el-icon>
+              编辑
+            </el-button>
+            <el-button type="primary" @click="shareReport">
+              <el-icon><Share /></el-icon>
+              分享
+            </el-button>
+          </el-button-group>
+        </div>
+      </div>
+
+      <!-- 筛选条件 -->
+      <div class="filter-section">
+        <el-card shadow="never" class="filter-card">
+          <template #header>
+            <div class="card-header">
+              <span>筛选条件</span>
+              <el-button text @click="toggleFilterExpand">
+                <el-icon><component :is="filterExpanded ? 'ArrowUp' : 'ArrowDown'" /></el-icon>
+                {{ filterExpanded ? '收起' : '展开' }}
+              </el-button>
+            </div>
+          </template>
+          <div v-show="filterExpanded" class="filter-content">
+            <el-form :model="filterForm" inline>
+              <el-form-item label="时间范围">
+                <el-date-picker
+                  v-model="filterForm.dateRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  @change="handleFilterChange"
+                />
+              </el-form-item>
+              <el-form-item label="数据类型">
+                <el-select v-model="filterForm.dataType" placeholder="请选择" @change="handleFilterChange">
+                  <el-option label="全部" value="" />
+                  <el-option label="销售数据" value="sales" />
+                  <el-option label="订单数据" value="orders" />
+                  <el-option label="用户数据" value="users" />
+                  <el-option label="商品数据" value="goods" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="状态">
+                <el-select v-model="filterForm.status" placeholder="请选择" @change="handleFilterChange">
+                  <el-option label="全部" value="" />
+                  <el-option label="正常" value="normal" />
+                  <el-option label="异常" value="error" />
+                  <el-option label="待处理" value="pending" />
+                </el-select>
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" @click="applyFilter">
+                  <el-icon><Search /></el-icon>
+                  查询
+                </el-button>
+                <el-button @click="resetFilter">
+                  <el-icon><RefreshLeft /></el-icon>
+                  重置
+                </el-button>
+              </el-form-item>
+            </el-form>
+          </div>
+        </el-card>
+      </div>
+
+      <!-- 数据概览 -->
+      <div class="overview-section">
+        <el-row :gutter="20">
+          <el-col :span="6" v-for="(item, index) in overviewData" :key="index">
+            <el-card shadow="hover" class="overview-card">
+              <div class="overview-item">
+                <div class="overview-icon" :class="item.type">
+                  <el-icon><component :is="item.icon" /></el-icon>
+                </div>
+                <div class="overview-content">
+                  <div class="overview-value">{{ item.value }}</div>
+                  <div class="overview-label">{{ item.label }}</div>
+                  <div class="overview-trend" :class="item.trend > 0 ? 'up' : 'down'">
+                    <el-icon><component :is="item.trend > 0 ? 'TrendCharts' : 'Bottom'" /></el-icon>
+                    {{ Math.abs(item.trend) }}%
+                  </div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+      </div>
+
+      <!-- 图表展示区域 -->
+      <div class="charts-section">
+        <el-card shadow="never">
+          <template #header>
+            <div class="card-header">
+              <span>数据图表</span>
+              <div class="chart-controls">
+                <el-radio-group v-model="chartType" @change="handleChartTypeChange">
+                  <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-button label="area">面积图</el-radio-button>
+                </el-radio-group>
+              </div>
+            </div>
+          </template>
+          <div class="chart-container" v-loading="chartLoading">
+            <v-chart :option="chartOption" autoresize style="height: 400px;" />
+          </div>
+        </el-card>
+      </div>
+
+      <!-- 详细数据表格 -->
+      <div class="table-section">
+        <el-card shadow="never">
+          <template #header>
+            <div class="card-header">
+              <span>详细数据</span>
+              <div class="table-controls">
+                <el-input
+                  v-model="searchKeyword"
+                  placeholder="搜索数据..."
+                  style="width: 200px; margin-right: 12px;"
+                  @input="handleSearch"
+                >
+                  <template #prefix>
+                    <el-icon><Search /></el-icon>
+                  </template>
+                </el-input>
+                <el-button @click="exportTableData">
+                  <el-icon><Download /></el-icon>
+                  导出表格
+                </el-button>
+              </div>
+            </div>
+          </template>
+          <el-table
+            :data="tableData"
+            v-loading="tableLoading"
+            stripe
+            border
+            style="width: 100%"
+            @sort-change="handleSortChange"
+          >
+            <el-table-column prop="date" label="日期" width="120" sortable />
+            <el-table-column prop="type" label="数据类型" width="100">
+              <template #default="{ row }">
+                <el-tag :type="getDataTypeColor(row.type)">{{ getDataTypeLabel(row.type) }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="value" label="数值" width="120" sortable>
+              <template #default="{ row }">
+                <span class="number-value">{{ formatNumber(row.value) }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="growth" label="增长率" width="100" sortable>
+              <template #default="{ row }">
+                <span :class="row.growth >= 0 ? 'growth-positive' : 'growth-negative'">
+                  {{ row.growth >= 0 ? '+' : '' }}{{ row.growth }}%
+                </span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="status" label="状态" width="100">
+              <template #default="{ row }">
+                <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
+            <el-table-column label="操作" width="120" fixed="right">
+              <template #default="{ row, $index }">
+                <el-button size="small" @click="viewDetail(row)">
+                  查看
+                </el-button>
+                <el-button size="small" type="primary" @click="editRow(row, $index)">
+                  编辑
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          
+          <!-- 分页 -->
+          <div class="pagination-wrapper">
+            <el-pagination
+              v-model:current-page="pagination.currentPage"
+              v-model:page-size="pagination.pageSize"
+              :page-sizes="[10, 20, 50, 100]"
+              :total="pagination.total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </el-card>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleClose">关闭</el-button>
+        <el-button type="primary" @click="saveReport">
+          保存报表
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, computed, watch, onMounted } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import {
+  Refresh,
+  Download,
+  Edit,
+  Share,
+  Search,
+  RefreshLeft,
+  ArrowUp,
+  ArrowDown,
+  TrendCharts,
+  Bottom,
+  DataLine,
+  Histogram,
+  PieChart,
+  Money,
+  ShoppingCart,
+  User,
+  Goods
+} from '@element-plus/icons-vue';
+import VChart from 'vue-echarts';
+import { use } from 'echarts/core';
+import {
+  CanvasRenderer
+} from 'echarts/renderers';
+import {
+  LineChart,
+  BarChart,
+  PieChart as EChartsPieChart
+} from 'echarts/charts';
+import {
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  GridComponent
+} from 'echarts/components';
+
+use([
+  CanvasRenderer,
+  LineChart,
+  BarChart,
+  EChartsPieChart,
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  GridComponent
+]);
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  reportData: {
+    type: Object,
+    default: () => ({})
+  }
+});
+
+const emit = defineEmits(['update:visible', 'save']);
+
+// 响应式数据
+const refreshing = ref(false);
+const filterExpanded = ref(true);
+const chartLoading = ref(false);
+const tableLoading = ref(false);
+const chartType = ref('line');
+const searchKeyword = ref('');
+
+const filterForm = reactive({
+  dateRange: [],
+  dataType: '',
+  status: ''
+});
+
+const pagination = reactive({
+  currentPage: 1,
+  pageSize: 20,
+  total: 0
+});
+
+// 概览数据
+const overviewData = ref([
+  {
+    label: '总销售额',
+    value: '¥1,234,567',
+    trend: 12.5,
+    type: 'sales',
+    icon: 'Money'
+  },
+  {
+    label: '订单数量',
+    value: '8,456',
+    trend: -3.2,
+    type: 'orders',
+    icon: 'ShoppingCart'
+  },
+  {
+    label: '用户数量',
+    value: '12,345',
+    trend: 8.7,
+    type: 'users',
+    icon: 'User'
+  },
+  {
+    label: '商品数量',
+    value: '2,567',
+    trend: 5.4,
+    type: 'goods',
+    icon: 'Goods'
+  }
+]);
+
+// 表格数据
+const tableData = ref([]);
+
+// 图表配置
+const chartOption = ref({});
+
+// 方法
+const handleClose = () => {
+  emit('update:visible', false);
+};
+
+const refreshReport = async () => {
+  refreshing.value = true;
+  try {
+    await loadReportData();
+    ElMessage.success('报表刷新成功');
+  } catch (error) {
+    ElMessage.error('报表刷新失败');
+  } finally {
+    refreshing.value = false;
+  }
+};
+
+const exportReport = () => {
+  ElMessage.info('导出功能开发中...');
+};
+
+const editReport = () => {
+  ElMessage.info('编辑功能开发中...');
+};
+
+const shareReport = () => {
+  ElMessage.info('分享功能开发中...');
+};
+
+const toggleFilterExpand = () => {
+  filterExpanded.value = !filterExpanded.value;
+};
+
+const handleFilterChange = () => {
+  // 筛选条件变化时的处理
+};
+
+const applyFilter = () => {
+  loadTableData();
+  loadChartData();
+  ElMessage.success('筛选条件已应用');
+};
+
+const resetFilter = () => {
+  Object.assign(filterForm, {
+    dateRange: [],
+    dataType: '',
+    status: ''
+  });
+  loadTableData();
+  loadChartData();
+  ElMessage.success('筛选条件已重置');
+};
+
+const handleChartTypeChange = () => {
+  loadChartData();
+};
+
+const handleSearch = () => {
+  // 搜索处理
+  loadTableData();
+};
+
+const handleSortChange = ({ prop, order }) => {
+  // 排序处理
+  loadTableData();
+};
+
+const handleSizeChange = (size) => {
+  pagination.pageSize = size;
+  loadTableData();
+};
+
+const handleCurrentChange = (page) => {
+  pagination.currentPage = page;
+  loadTableData();
+};
+
+const exportTableData = () => {
+  ElMessage.info('导出表格功能开发中...');
+};
+
+const viewDetail = (row) => {
+  ElMessage.info(`查看详情: ${row.date}`);
+};
+
+const editRow = (row, index) => {
+  ElMessage.info(`编辑行: ${index + 1}`);
+};
+
+const saveReport = () => {
+  ElMessage.success('报表保存成功');
+  handleClose();
+};
+
+// 工具方法
+const formatDate = (date) => {
+  if (!date) return '-';
+  return new Date(date).toLocaleDateString('zh-CN');
+};
+
+const formatNumber = (num) => {
+  if (typeof num !== 'number') return num;
+  return num.toLocaleString();
+};
+
+const getStatusType = (status) => {
+  const types = {
+    normal: 'success',
+    error: 'danger',
+    pending: 'warning',
+    active: 'success',
+    inactive: 'info'
+  };
+  return types[status] || 'info';
+};
+
+const getStatusText = (status) => {
+  const texts = {
+    normal: '正常',
+    error: '异常',
+    pending: '待处理',
+    active: '活跃',
+    inactive: '非活跃'
+  };
+  return texts[status] || '未知';
+};
+
+const getDataTypeColor = (type) => {
+  const colors = {
+    sales: 'success',
+    orders: 'primary',
+    users: 'warning',
+    goods: 'info'
+  };
+  return colors[type] || 'info';
+};
+
+const getDataTypeLabel = (type) => {
+  const labels = {
+    sales: '销售',
+    orders: '订单',
+    users: '用户',
+    goods: '商品'
+  };
+  return labels[type] || '未知';
+};
+
+// 数据加载方法
+const loadReportData = async () => {
+  await Promise.all([
+    loadTableData(),
+    loadChartData()
+  ]);
+};
+
+const loadTableData = async () => {
+  tableLoading.value = true;
+  try {
+    // 模拟数据加载
+    await new Promise(resolve => setTimeout(resolve, 500));
+    
+    const mockData = Array.from({ length: 50 }, (_, index) => ({
+      date: new Date(Date.now() - index * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
+      type: ['sales', 'orders', 'users', 'goods'][index % 4],
+      value: Math.floor(Math.random() * 10000) + 1000,
+      growth: (Math.random() - 0.5) * 20,
+      status: ['normal', 'error', 'pending'][index % 3],
+      remark: `备注信息 ${index + 1}`
+    }));
+    
+    tableData.value = mockData.slice(
+      (pagination.currentPage - 1) * pagination.pageSize,
+      pagination.currentPage * pagination.pageSize
+    );
+    pagination.total = mockData.length;
+  } catch (error) {
+    ElMessage.error('数据加载失败');
+  } finally {
+    tableLoading.value = false;
+  }
+};
+
+const loadChartData = async () => {
+  chartLoading.value = true;
+  try {
+    // 模拟数据加载
+    await new Promise(resolve => setTimeout(resolve, 300));
+    
+    const days = Array.from({ length: 7 }, (_, i) => {
+      const date = new Date();
+      date.setDate(date.getDate() - i);
+      return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
+    }).reverse();
+    
+    const values = Array.from({ length: 7 }, () => Math.floor(Math.random() * 1000) + 100);
+    
+    if (chartType.value === 'pie') {
+      chartOption.value = {
+        tooltip: {
+          trigger: 'item',
+          formatter: '{a} <br/>{b}: {c} ({d}%)'
+        },
+        legend: {
+          orient: 'vertical',
+          left: 'left'
+        },
+        series: [
+          {
+            name: '数据分布',
+            type: 'pie',
+            radius: '50%',
+            data: [
+              { value: 335, name: '销售数据' },
+              { value: 310, name: '订单数据' },
+              { value: 234, name: '用户数据' },
+              { value: 135, name: '商品数据' }
+            ]
+          }
+        ]
+      };
+    } else {
+      chartOption.value = {
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          data: ['数据趋势']
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          data: days
+        },
+        yAxis: {
+          type: 'value'
+        },
+        series: [
+          {
+            name: '数据趋势',
+            type: chartType.value,
+            data: values,
+            areaStyle: chartType.value === 'area' ? {} : undefined
+          }
+        ]
+      };
+    }
+  } catch (error) {
+    ElMessage.error('图表数据加载失败');
+  } finally {
+    chartLoading.value = false;
+  }
+};
+
+// 监听器
+watch(() => props.visible, (newVal) => {
+  if (newVal) {
+    loadReportData();
+  }
+});
+
+// 生命周期
+onMounted(() => {
+  if (props.visible) {
+    loadReportData();
+  }
+});
+</script>
+
+<style scoped>
+.report-detail-dialog {
+  .dialog-content {
+    max-height: 80vh;
+    overflow-y: auto;
+  }
+  
+  .report-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 24px;
+    padding: 20px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 12px;
+    color: white;
+    
+    .header-left {
+      .report-title {
+        h3 {
+          margin: 0 0 8px 0;
+          font-size: 24px;
+          font-weight: 600;
+        }
+        
+        .report-meta {
+          display: flex;
+          align-items: center;
+          gap: 16px;
+          font-size: 14px;
+          opacity: 0.9;
+          
+          .meta-item {
+            display: flex;
+            align-items: center;
+          }
+        }
+      }
+    }
+    
+    .header-right {
+      .el-button-group {
+        .el-button {
+          background: rgba(255, 255, 255, 0.1);
+          border-color: rgba(255, 255, 255, 0.2);
+          color: white;
+          
+          &:hover {
+            background: rgba(255, 255, 255, 0.2);
+          }
+        }
+      }
+    }
+  }
+  
+  .filter-section,
+  .overview-section,
+  .charts-section,
+  .table-section {
+    margin-bottom: 24px;
+  }
+  
+  .filter-card {
+    .card-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+    }
+    
+    .filter-content {
+      .el-form {
+        .el-form-item {
+          margin-bottom: 16px;
+        }
+      }
+    }
+  }
+  
+  .overview-section {
+    .overview-card {
+      .overview-item {
+        display: flex;
+        align-items: center;
+        gap: 16px;
+        
+        .overview-icon {
+          width: 48px;
+          height: 48px;
+          border-radius: 12px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          font-size: 24px;
+          color: white;
+          
+          &.sales {
+            background: linear-gradient(135deg, #67c23a, #85ce61);
+          }
+          
+          &.orders {
+            background: linear-gradient(135deg, #409eff, #66b1ff);
+          }
+          
+          &.users {
+            background: linear-gradient(135deg, #e6a23c, #ebb563);
+          }
+          
+          &.goods {
+            background: linear-gradient(135deg, #f56c6c, #f78989);
+          }
+        }
+        
+        .overview-content {
+          flex: 1;
+          
+          .overview-value {
+            font-size: 24px;
+            font-weight: 600;
+            color: #303133;
+            margin-bottom: 4px;
+          }
+          
+          .overview-label {
+            font-size: 14px;
+            color: #909399;
+            margin-bottom: 4px;
+          }
+          
+          .overview-trend {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            font-size: 12px;
+            font-weight: 500;
+            
+            &.up {
+              color: #67c23a;
+            }
+            
+            &.down {
+              color: #f56c6c;
+            }
+          }
+        }
+      }
+    }
+  }
+  
+  .charts-section,
+  .table-section {
+    .card-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+    }
+    
+    .chart-container {
+      padding: 16px 0;
+    }
+    
+    .table-controls {
+      display: flex;
+      align-items: center;
+    }
+  }
+  
+  .number-value {
+    font-weight: 500;
+    color: #303133;
+  }
+  
+  .growth-positive {
+    color: #67c23a;
+    font-weight: 500;
+  }
+  
+  .growth-negative {
+    color: #f56c6c;
+    font-weight: 500;
+  }
+  
+  .pagination-wrapper {
+    display: flex;
+    justify-content: center;
+    margin-top: 20px;
+  }
+  
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+  }
+}
+</style>

+ 532 - 222
src/app/shop/admin/data/report/index.vue

@@ -1,130 +1,226 @@
 <template>
-  <el-container class="data-report-view panel-block">
-    <el-header class="sa-header">
-      <!-- 简化搜索组件 -->
-      <div class="search-container">
-        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
-          v-model="currentSearchParams" @search="handleSearch" @reset="handleReset">
-        </sa-search-simple>
+  <div class="data-report-main">
+    <!-- 顶部导航栏 -->
+    <div class="top-navbar">
+      <div class="navbar-left">
+        <div class="logo-section">
+          <div class="logo-icon">📊</div>
+          <span class="logo-text">数据报表</span>
+        </div>
+      </div>
+      <div class="navbar-right">
+        <el-button type="text" class="nav-button">
+          <el-icon><Bell /></el-icon>
+        </el-button>
+        <el-button type="text" class="nav-button">
+          <el-icon><Setting /></el-icon>
+        </el-button>
+        <div class="user-avatar">
+          <el-avatar size="small">U</el-avatar>
+        </div>
       </div>
-      <div class="sa-title sa-flex sa-row-between">
-        <div class="label sa-flex">数据报表</div>
-        <div>
-          <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button icon="Download" type="primary" @click="exportData">导出</el-button>
+    </div>
+
+    <!-- 主要内容区域 -->
+    <div class="main-content">
+      <!-- 左侧导航栏 -->
+      <div class="sidebar">
+        <div class="sidebar-menu">
+          <div class="menu-item active" @click="activeTab = 'overview'">
+            <div class="menu-icon">📈</div>
+            <span class="menu-text">概览</span>
+          </div>
+          <div class="menu-item" @click="activeTab = 'charts'; showDataDisplay()">
+            <div class="menu-icon">📊</div>
+            <span class="menu-text">图表</span>
+          </div>
+          <div class="menu-item" @click="activeTab = 'dashboard'; showAddDashboard()">
+            <div class="menu-icon">📋</div>
+            <span class="menu-text">看板</span>
+          </div>
+          <div class="menu-item" @click="activeTab = 'reports'; showReportDetail()">
+            <div class="menu-icon">📄</div>
+            <span class="menu-text">报表</span>
+          </div>
+          <div class="menu-item" @click="showDimensionManagement">
+            <div class="menu-icon">⚙️</div>
+            <span class="menu-text">设置</span>
+          </div>
         </div>
       </div>
-    </el-header>
-    <el-main class="sa-p-0">
-      <!-- 统计卡片 -->
-      <div class="stats-cards">
-        <el-row :gutter="20">
-          <el-col :span="6">
-            <el-card class="stats-card">
-              <div class="stats-content">
-                <div class="stats-value">{{ statsData.totalSales || 0 }}</div>
-                <div class="stats-label">总销售额(৳)</div>
+
+      <!-- 右侧内容区域 -->
+      <div class="content-area">
+        <!-- 概览页面 -->
+        <div v-if="activeTab === 'overview'" class="overview-content">
+          <!-- 页面标题 -->
+          <div class="page-header">
+            <h1 class="page-title">数据概览</h1>
+            <p class="page-subtitle">实时数据分析与监控</p>
+          </div>
+          <!-- 统计卡片 -->
+          <div class="stats-overview">
+            <div class="stats-grid">
+              <div class="stat-card" @click="showDataDisplay('sales')">
+                <div class="stat-icon sales">💰</div>
+                <div class="stat-content">
+                  <div class="stat-value">৳{{ statsData.totalSales || 0 }}</div>
+                  <div class="stat-label">总销售额</div>
+                </div>
+                <div class="stat-action">
+                  <el-icon><ArrowRight /></el-icon>
+                </div>
               </div>
-            </el-card>
-          </el-col>
-          <el-col :span="6">
-            <el-card class="stats-card">
-              <div class="stats-content">
-                <div class="stats-value">{{ statsData.totalOrders || 0 }}</div>
-                <div class="stats-label">总订单数</div>
+              <div class="stat-card" @click="showDataDisplay('orders')">
+                <div class="stat-icon orders">📦</div>
+                <div class="stat-content">
+                  <div class="stat-value">{{ statsData.totalOrders || 0 }}</div>
+                  <div class="stat-label">总订单数</div>
+                </div>
+                <div class="stat-action">
+                  <el-icon><ArrowRight /></el-icon>
+                </div>
               </div>
-            </el-card>
-          </el-col>
-          <el-col :span="6">
-            <el-card class="stats-card">
-              <div class="stats-content">
-                <div class="stats-value">{{ statsData.totalUsers || 0 }}</div>
-                <div class="stats-label">总用户数</div>
+              <div class="stat-card" @click="showDataDisplay('users')">
+                <div class="stat-icon users">👥</div>
+                <div class="stat-content">
+                  <div class="stat-value">{{ statsData.totalUsers || 0 }}</div>
+                  <div class="stat-label">总用户数</div>
+                </div>
+                <div class="stat-action">
+                  <el-icon><ArrowRight /></el-icon>
+                </div>
               </div>
-            </el-card>
-          </el-col>
-          <el-col :span="6">
-            <el-card class="stats-card">
-              <div class="stats-content">
-                <div class="stats-value">{{ statsData.totalGoods || 0 }}</div>
-                <div class="stats-label">总商品数</div>
+              <div class="stat-card" @click="showDataDisplay('goods')">
+                <div class="stat-icon goods">🛍️</div>
+                <div class="stat-content">
+                  <div class="stat-value">{{ statsData.totalGoods || 0 }}</div>
+                  <div class="stat-label">总商品数</div>
+                </div>
+                <div class="stat-action">
+                  <el-icon><ArrowRight /></el-icon>
+                </div>
               </div>
-            </el-card>
-          </el-col>
-        </el-row>
-      </div>
+            </div>
+          </div>
+
+          <!-- 图表区域 -->
+          <div class="charts-section">
+            <DashboardTabs ref="dashboardRef" @refresh="refreshData" />
+          </div>
+        </div>
 
-      <!-- 数据表格 -->
-      <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
-        <el-table height="100%" class="sa-table" :data="table.data" @selection-change="changeSelection"
-          @sort-change="fieldFilter" row-key="id" stripe>
-          <template #empty>
-            <sa-empty />
-          </template>
-          <el-table-column type="selection" width="48" align="center"></el-table-column>
-          <el-table-column prop="date" label="日期" min-width="120" sortable="custom">
-          </el-table-column>
-          <el-table-column label="销售额" min-width="120">
-            <template #default="scope"> ৳{{ scope.row.sales_amount || 0 }} </template>
-          </el-table-column>
-          <el-table-column label="订单数" min-width="100">
-            <template #default="scope">
-              {{ scope.row.order_count || 0 }}
-            </template>
-          </el-table-column>
-          <el-table-column label="新增用户" min-width="100">
-            <template #default="scope">
-              {{ scope.row.new_users || 0 }}
-            </template>
-          </el-table-column>
-          <el-table-column label="访问量" min-width="100">
-            <template #default="scope">
-              {{ scope.row.page_views || 0 }}
-            </template>
-          </el-table-column>
-          <el-table-column label="转化率" min-width="100">
-            <template #default="scope"> {{ scope.row.conversion_rate || 0 }}% </template>
-          </el-table-column>
-          <el-table-column label="客单价" min-width="120">
-            <template #default="scope"> ৳{{ scope.row.avg_order_value || 0 }} </template>
-          </el-table-column>
-        </el-table>
+        <!-- 图表页面 -->
+        <div v-if="activeTab === 'charts'" class="charts-content">
+          <div class="page-header">
+            <h1 class="page-title">数据图表</h1>
+            <p class="page-subtitle">可视化数据分析</p>
+          </div>
+          <div class="chart-placeholder">
+            <div class="placeholder-content">
+              <div class="placeholder-icon">📊</div>
+              <h3>图表功能开发中</h3>
+              <p>点击此处查看数据图表</p>
+            </div>
+          </div>
+        </div>
+
+        <!-- 看板页面 -->
+        <div v-if="activeTab === 'dashboard'" class="dashboard-content">
+          <div class="page-header">
+            <h1 class="page-title">数据看板</h1>
+            <p class="page-subtitle">自定义数据看板</p>
+          </div>
+          <div class="dashboard-placeholder">
+            <div class="placeholder-content">
+              <div class="placeholder-icon">📋</div>
+              <h3>看板功能开发中</h3>
+              <p>新增看板和图表功能</p>
+            </div>
+          </div>
+        </div>
+
+        <!-- 报表页面 -->
+        <div v-if="activeTab === 'reports'" class="reports-content">
+          <div class="page-header">
+            <h1 class="page-title">数据报表</h1>
+            <p class="page-subtitle">详细数据报表</p>
+          </div>
+          <div class="report-placeholder">
+            <div class="placeholder-content">
+              <div class="placeholder-icon">📄</div>
+              <h3>报表功能开发中</h3>
+              <p>报表详情页面</p>
+            </div>
+          </div>
+        </div>
       </div>
-    </el-main>
-    <sa-view-bar>
-      <template #left>
-        <sa-batch-handle :batchHandleTools="batchHandleTools" :selectedLeng="table.selected.length"
-          @batchHandle="batchHandle"></sa-batch-handle>
-      </template>
-      <template #right>
-        <sa-pagination :pageData="pageData" @updateFn="getData" />
-      </template>
-    </sa-view-bar>
-  </el-container>
+    </div>
+
+    <!-- 维度管理对话框 -->
+    <el-dialog
+      v-model="dimensionDialogVisible"
+      title="维度管理"
+      width="90%"
+      :close-on-click-modal="false"
+      top="5vh"
+    >
+      <DimensionManagement />
+    </el-dialog>
+    
+    <!-- 数据展示对话框 -->
+    <DataDisplayDialog 
+      v-model:visible="dataDisplayVisible" 
+      :data-type="currentDataType"
+      @close="handleDataDisplayClose"
+    />
+
+    <!-- 新增看板对话框 -->
+    <AddDashboardDialog 
+      v-model:visible="addDashboardVisible"
+      @save="handleDashboardSave"
+    />
+
+    <!-- 报表详情对话框 -->
+    <ReportDetailDialog 
+      v-model:visible="reportDetailVisible"
+      :report-data="currentReportData"
+      @save="handleReportSave"
+    />
+  </div>
 </template>
 <script setup>
 import { onMounted, reactive, ref } from 'vue';
 import { api } from '../data.service';
 import { ElMessage } from 'element-plus';
-import { usePagination } from '@/sheep/hooks';
-const { pageData } = usePagination();
-
-// 搜索字段配置
-const searchFields = reactive({
-  date_range: {
-    type: 'daterange',
-    label: '日期范围',
-    placeholder: '请选择日期范围',
-    width: 300,
-  },
-});
-// 默认搜索值
-const defaultSearchValues = reactive({
-  date_range: [],
-});
+import { Bell, Setting, ArrowRight } from '@element-plus/icons-vue';
+import DashboardTabs from './components/dashboard-tabs.vue';
+import DimensionManagement from './components/dimension-management.vue';
+import DataDisplayDialog from './components/data-display-dialog.vue';
+import AddDashboardDialog from './components/add-dashboard-dialog.vue';
+import ReportDetailDialog from './components/report-detail-dialog.vue';
+
+const components = {
+  DashboardTabs,
+  DimensionManagement,
+  DataDisplayDialog,
+  AddDashboardDialog,
+  ReportDetailDialog
+};
 
-// 当前搜索条件 - 使用 ref 支持双向绑定
-const currentSearchParams = ref({});
+// 当前活动标签
+const activeTab = ref('overview');
+// 维度管理对话框
+const dimensionDialogVisible = ref(false);
+// 数据展示对话框
+const dataDisplayVisible = ref(false);
+const currentDataType = ref('');
+const dashboardRef = ref();
+// 新增看板对话框
+const addDashboardVisible = ref(false);
+// 报表详情对话框
+const reportDetailVisible = ref(false);
+const currentReportData = ref(null);
 
 // 统计数据
 const statsData = reactive({
@@ -134,138 +230,352 @@ const statsData = reactive({
   totalGoods: 0,
 });
 
-// 列表
-const table = reactive({
-  data: [],
-  order: '',
-  sort: '',
-  selected: [],
-});
-const loading = ref(true);
-
 // 获取统计数据
 async function getStatsData() {
-  const { code, data } = await api.report.getStats();
-  if (code == 200) {
-    Object.assign(statsData, data);
+  try {
+    const { code, data } = await api.report.getStats();
+    if (code == 200) {
+      Object.assign(statsData, data);
+    }
+  } catch (error) {
+    console.error('获取统计数据失败:', error);
   }
 }
 
-// 获取数据
-async function getData(page, searchParams = null) {
-  if (page) pageData.page = page;
-  loading.value = true;
-
-  // 构建请求参数 - 优先使用传入的参数,否则使用双向绑定的搜索条件
-  const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
-
-  const { code, data } = await api.report.list({
-    page: pageData.page,
-    size: pageData.size,
-    order: table.order,
-    ...finalSearchParams,
-    sort: table.sort,
-  });
-  console.log('API 响应:', error, data);
-  if (code == 200) {
-    table.data = data.data;
-    pageData.page = data.current_page;
-    pageData.size = data.per_page;
-    pageData.total = data.total;
-  }
-  loading.value = false;
-}
+// 显示数据展示页面
+const showDataDisplay = (type) => {
+  currentDataType.value = type;
+  dataDisplayVisible.value = true;
+};
 
-// table 字段排序
-function fieldFilter({ prop, order }) {
-  table.order = order == 'ascending' ? 'asc' : 'desc';
-  table.sort = prop;
-  getData();
-}
+// 处理数据展示对话框关闭
+const handleDataDisplayClose = () => {
+  dataDisplayVisible.value = false;
+  currentDataType.value = '';
+};
 
-//table批量选择
-function changeSelection(row) {
-  table.selected = row;
-}
+// 显示新增看板页面
+const showAddDashboard = () => {
+  addDashboardVisible.value = true;
+};
 
-// 导出数据
-async function exportData() {
-  ElMessage.success('导出功能开发中...');
-}
+// 处理看板保存
+const handleDashboardSave = (dashboardData) => {
+  console.log('保存看板数据:', dashboardData);
+  addDashboardVisible.value = false;
+  ElMessage.success('看板创建成功!');
+  refreshData();
+};
 
-// 分页/批量操作
-const batchHandleTools = [
-  {
-    type: 'export',
-    label: '导出选中',
-    auth: 'shop.admin.data.report.export',
-    class: 'primary',
-  },
-];
+// 显示报表详情页面
+const showReportDetail = () => {
+  currentReportData.value = {
+    title: '数据报表详情',
+    type: 'general'
+  };
+  reportDetailVisible.value = true;
+};
 
-async function batchHandle(type) {
-  let ids = [];
-  table.selected.forEach((row) => {
-    ids.push(row.id);
-  });
-  switch (type) {
-    case 'export':
-      ElMessage.success('批量导出功能开发中...');
-      break;
-  }
-}
+// 处理报表保存
+const handleReportSave = (reportData) => {
+  console.log('保存报表数据:', reportData);
+  reportDetailVisible.value = false;
+  ElMessage.success('报表保存成功!');
+  refreshData();
+};
 
-// 搜索处理
-const handleSearch = (searchParams) => {
-  // 由于使用了 v-model,currentSearchParams 会自动更新
-  // 直接调用 getData,会自动使用当前的搜索条件
-  getData(1);
+// 刷新数据
+const refreshData = () => {
+  getStatsData();
+  if (dashboardRef.value) {
+    dashboardRef.value.refreshAllTabs();
+  }
 };
 
-// 重置处理
-const handleReset = () => {
-  // 由于使用了 v-model,currentSearchParams 会自动清空
-  // 直接调用 getData,会自动使用当前的搜索条件
-  getData(1);
+// 显示维度管理
+const showDimensionManagement = () => {
+  dimensionDialogVisible.value = true;
 };
 
 onMounted(() => {
   getStatsData();
-  getData();
+  refreshData();
 });
 </script>
-<style lang="scss" scoped>
-.data-report-view {
-  .el-header {
-    height: auto;
-  }
+<style scoped>
+.data-report-main {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #f5f7fa;
+}
 
-  .el-main {
-    .stats-cards {
-      margin-bottom: 20px;
-
-      .stats-card {
-        .stats-content {
-          text-align: center;
-
-          .stats-value {
-            font-size: 24px;
-            font-weight: bold;
-            color: #409eff;
-            margin-bottom: 8px;
-          }
-
-          .stats-label {
-            font-size: 14px;
-            color: #666;
-          }
-        }
-      }
-    }
+/* 顶部导航栏 */
+.top-navbar {
+  height: 60px;
+  background: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 24px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+}
 
-    .sa-table-wrap {
-      height: calc(100% - 140px);
-    }
+.navbar-left {
+  display: flex;
+  align-items: center;
+}
+
+.logo-section {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.logo-icon {
+  font-size: 24px;
+}
+
+.logo-text {
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.navbar-right {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.nav-button {
+  color: #606266;
+  font-size: 18px;
+}
+
+.user-avatar {
+  margin-left: 8px;
+}
+
+/* 主要内容区域 */
+.main-content {
+  flex: 1;
+  display: flex;
+  overflow: hidden;
+}
+
+/* 左侧导航栏 */
+.sidebar {
+  width: 200px;
+  background: #ffffff;
+  border-right: 1px solid #e4e7ed;
+  padding: 24px 0;
+}
+
+.sidebar-menu {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  padding: 0 16px;
+}
+
+.menu-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 16px;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  color: #606266;
+}
+
+.menu-item:hover {
+  background: #f0f9ff;
+  color: #409eff;
+}
+
+.menu-item.active {
+  background: #409eff;
+  color: #ffffff;
+}
+
+.menu-icon {
+  font-size: 18px;
+  width: 20px;
+  text-align: center;
+}
+
+.menu-text {
+  font-size: 14px;
+  font-weight: 500;
+}
+
+/* 右侧内容区域 */
+.content-area {
+  flex: 1;
+  padding: 24px;
+  overflow-y: auto;
+}
+
+/* 页面标题 */
+.page-header {
+  margin-bottom: 32px;
+}
+
+.page-title {
+  font-size: 28px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0 0 8px 0;
+}
+
+.page-subtitle {
+  font-size: 16px;
+  color: #909399;
+  margin: 0;
+}
+
+/* 统计卡片 */
+.stats-overview {
+  margin-bottom: 32px;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  gap: 20px;
+}
+
+.stat-card {
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 24px;
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  transition: all 0.3s ease;
+  cursor: pointer;
+}
+
+.stat-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+}
+
+.stat-action {
+  color: #909399;
+  font-size: 16px;
+  transition: all 0.3s ease;
+}
+
+.stat-card:hover .stat-action {
+  color: #409eff;
+  transform: translateX(2px);
+}
+
+.stat-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24px;
+}
+
+.stat-icon.sales {
+  background: linear-gradient(135deg, #67c23a, #85ce61);
+}
+
+.stat-icon.orders {
+  background: linear-gradient(135deg, #409eff, #66b1ff);
+}
+
+.stat-icon.users {
+  background: linear-gradient(135deg, #e6a23c, #ebb563);
+}
+
+.stat-icon.goods {
+  background: linear-gradient(135deg, #f56c6c, #f78989);
+}
+
+.stat-content {
+  flex: 1;
+}
+
+.stat-value {
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 4px;
+}
+
+.stat-label {
+  font-size: 14px;
+  color: #909399;
+}
+
+/* 图表区域 */
+.charts-section {
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+/* 占位符样式 */
+.chart-placeholder,
+.dashboard-placeholder,
+.report-placeholder {
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 60px 24px;
+  text-align: center;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.placeholder-content {
+  max-width: 400px;
+  margin: 0 auto;
+}
+
+.placeholder-icon {
+  font-size: 64px;
+  margin-bottom: 24px;
+  opacity: 0.6;
+}
+
+.placeholder-content h3 {
+  font-size: 20px;
+  color: #303133;
+  margin: 0 0 12px 0;
+}
+
+.placeholder-content p {
+  font-size: 14px;
+  color: #909399;
+  margin: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .sidebar {
+    width: 60px;
+  }
+  
+  .menu-text {
+    display: none;
+  }
+  
+  .stats-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .content-area {
+    padding: 16px;
   }
 }
 </style>

+ 443 - 0
src/app/shop/admin/data/report/styles/report-common.scss

@@ -0,0 +1,443 @@
+/* ==================
+   数据报表模块通用样式
+   基于项目设计系统优化
+ ==================== */
+
+// 报表容器基础样式
+.report-container {
+  background: var(--sa-background-assist);
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+// 报表头部样式
+.report-header {
+  padding: var(--sa-padding);
+  background: var(--sa-background-assist);
+  border-bottom: 1px solid var(--sa-border);
+  
+  .report-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: var(--sa-title);
+    margin: 0;
+    line-height: 1.5;
+  }
+  
+  .report-subtitle {
+    font-size: 14px;
+    color: var(--sa-subtitle);
+    margin: 4px 0 0 0;
+    line-height: 1.4;
+  }
+  
+  .report-description {
+    font-size: 12px;
+    color: var(--sa-subfont);
+    margin: 8px 0 0 0;
+    line-height: 1.4;
+  }
+}
+
+// 报表操作栏样式
+.report-actions {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px var(--sa-padding);
+  background: var(--sa-background-assist);
+  border-bottom: 1px solid var(--sa-border);
+  
+  .left-actions {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    flex: 1;
+    
+    .report-title {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
+      margin: 0;
+    }
+    
+    .report-desc {
+      font-size: 12px;
+      color: var(--sa-subfont);
+      margin: 0;
+    }
+  }
+  
+  .right-actions {
+    display: flex;
+    gap: 8px;
+    flex-shrink: 0;
+  }
+}
+
+// 统计卡片样式
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+  gap: 16px;
+  margin-bottom: 20px;
+  
+  .stats-card {
+    background: var(--sa-background-assist);
+    border: 1px solid var(--sa-border);
+    border-radius: 8px;
+    transition: all 0.3s ease;
+    
+    &:hover {
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+      transform: translateY(-2px);
+    }
+    
+    .stats-content {
+      display: flex;
+      align-items: center;
+      padding: 20px;
+      
+      .stats-icon {
+        width: 48px;
+        height: 48px;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 16px;
+        font-size: 24px;
+        
+        &.primary {
+          background: var(--el-color-primary-light-9);
+          color: var(--el-color-primary);
+        }
+        
+        &.success {
+          background: var(--el-color-success-light-9);
+          color: var(--el-color-success);
+        }
+        
+        &.warning {
+          background: var(--el-color-warning-light-9);
+          color: var(--el-color-warning);
+        }
+        
+        &.danger {
+          background: var(--el-color-danger-light-9);
+          color: var(--el-color-danger);
+        }
+      }
+      
+      .stats-info {
+        flex: 1;
+        
+        .stats-value {
+          font-size: 28px;
+          font-weight: 700;
+          color: var(--sa-title);
+          margin: 0 0 4px 0;
+          line-height: 1.2;
+        }
+        
+        .stats-label {
+          font-size: 14px;
+          color: var(--sa-subtitle);
+          margin: 0;
+        }
+      }
+      
+      .stats-trend {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        font-size: 12px;
+        font-weight: 500;
+        
+        &.up {
+          color: var(--el-color-success);
+        }
+        
+        &.down {
+          color: var(--el-color-danger);
+        }
+        
+        &.stable {
+          color: var(--sa-subfont);
+        }
+      }
+    }
+  }
+}
+
+// 图表区域样式
+.chart-area {
+  background: var(--sa-background-assist);
+  border: 1px solid var(--sa-border);
+  border-radius: 8px;
+  margin-bottom: 20px;
+  
+  .chart-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px var(--sa-padding);
+    border-bottom: 1px solid var(--sa-border);
+    
+    .chart-title {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
+      margin: 0;
+    }
+    
+    .chart-controls {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+    }
+  }
+  
+  .chart-container {
+    padding: 20px;
+    min-height: 300px;
+    position: relative;
+    
+    .no-data {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      height: 300px;
+      color: var(--sa-subfont);
+      
+      .no-data-icon {
+        font-size: 48px;
+        margin-bottom: 12px;
+        opacity: 0.5;
+      }
+      
+      .no-data-text {
+        font-size: 14px;
+      }
+    }
+  }
+}
+
+// 表格区域样式
+.table-area {
+  background: var(--sa-background-assist);
+  border: 1px solid var(--sa-border);
+  border-radius: 8px;
+  
+  .table-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px var(--sa-padding);
+    border-bottom: 1px solid var(--sa-border);
+    
+    .table-title {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--sa-title);
+      margin: 0;
+    }
+    
+    .table-actions {
+      display: flex;
+      gap: 8px;
+    }
+  }
+  
+  .table-content {
+    padding: 0;
+  }
+}
+
+// 搜索区域样式
+.search-section {
+  background: var(--sa-background-assist);
+  border: 1px solid var(--sa-border);
+  border-radius: 8px;
+  padding: 16px var(--sa-padding);
+  margin-bottom: 16px;
+  
+  .search-form {
+    .el-form-item {
+      margin-bottom: 16px;
+      
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+  
+  .search-actions {
+    display: flex;
+    gap: 8px;
+    justify-content: flex-end;
+    margin-top: 16px;
+  }
+}
+
+// 批量操作样式
+.batch-actions {
+  padding: 12px var(--sa-padding);
+  background: var(--sa-background-hex-hover);
+  border-bottom: 1px solid var(--sa-border);
+  
+  .batch-info {
+    font-size: 12px;
+    color: var(--sa-subfont);
+    margin-bottom: 8px;
+  }
+  
+  .batch-buttons {
+    display: flex;
+    gap: 8px;
+  }
+}
+
+// 分页样式
+.pagination-section {
+  padding: 16px var(--sa-padding);
+  background: var(--sa-background-assist);
+  border-top: 1px solid var(--sa-border);
+  display: flex;
+  justify-content: flex-end;
+}
+
+// 对话框样式优化
+.report-dialog {
+  .el-dialog__header {
+    background: var(--sa-background-assist);
+    border-bottom: 1px solid var(--sa-border);
+  }
+  
+  .el-dialog__body {
+    background: var(--sa-background-assist);
+  }
+  
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    padding-top: 16px;
+    border-top: 1px solid var(--sa-border);
+  }
+}
+
+// 标签页样式优化
+.dashboard-tabs {
+  .tabs-header {
+    background: var(--sa-background-assist);
+    padding: 0 var(--sa-padding);
+    border-bottom: 1px solid var(--sa-border);
+    
+    .el-tabs {
+      .el-tabs__header {
+        margin: 0;
+      }
+      
+      .el-tabs__nav-wrap {
+        &::after {
+          display: none;
+        }
+      }
+      
+      .el-tabs__item {
+        border: 1px solid var(--sa-border);
+        border-bottom: none;
+        background: var(--sa-background-hex-hover);
+        color: var(--sa-subtitle);
+        
+        &.is-active {
+          background: var(--sa-background-assist);
+          color: var(--sa-title);
+          border-color: var(--sa-border);
+        }
+        
+        &:hover {
+          color: var(--sa-title);
+        }
+      }
+    }
+  }
+  
+  .tabs-content {
+    background: var(--sa-background-assist);
+    min-height: 500px;
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .report-actions {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+    
+    .left-actions {
+      flex-direction: column;
+      gap: 8px;
+      align-items: flex-start;
+    }
+    
+    .right-actions {
+      justify-content: flex-start;
+      flex-wrap: wrap;
+    }
+  }
+  
+  .stats-cards {
+    grid-template-columns: 1fr;
+  }
+  
+  .chart-header {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+  }
+  
+  .table-header {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+  }
+  
+  .search-actions {
+    justify-content: stretch;
+    
+    .el-button {
+      flex: 1;
+    }
+  }
+}
+
+// 动画效果
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+  opacity: 0;
+}
+
+.slide-up-enter-active,
+.slide-up-leave-active {
+  transition: all 0.3s ease;
+}
+
+.slide-up-enter-from {
+  transform: translateY(20px);
+  opacity: 0;
+}
+
+.slide-up-leave-to {
+  transform: translateY(-20px);
+  opacity: 0;
+}

+ 1210 - 0
src/app/shop/admin/data/report/txz.openapi (2).json

@@ -0,0 +1,1210 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "txz",
+    "description": "",
+    "version": "1.0.0"
+  },
+  "tags": [],
+  "paths": {
+    "/spectaculars/addDimensionView": {
+      "get": {
+        "summary": "新增纬度视图",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [
+          {
+            "name": "spectId",
+            "in": "query",
+            "description": "",
+            "required": true,
+            "schema": {
+              "type": "integer"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultListDimensionDto"
+                },
+                "example": {
+                  "code": "",
+                  "message": "",
+                  "data": [
+                    {
+                      "id": 0,
+                      "spectacularsDimensionId": 0,
+                      "name": "",
+                      "memo": "",
+                      "unitType": 0,
+                      "viewType": "",
+                      "sections": [
+                        {
+                          "id": 0,
+                          "name": "",
+                          "memo": "",
+                          "statistics": [
+                            {
+                              "id": 0,
+                              "cutDay": "",
+                              "statisticsValue": 0,
+                              "proportion": 0
+                            }
+                          ],
+                          "total": 0
+                        }
+                      ],
+                      "createName": "",
+                      "updateName": "",
+                      "createTime": "",
+                      "updateTime": "",
+                      "selected": false
+                    }
+                  ]
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/add": {
+      "post": {
+        "summary": "新增看板",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Spectaculars",
+                "description": ""
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result",
+                  "description": "统一API响应结果封装"
+                },
+                "example": {
+                  "code": "200",
+                  "message": "SUCCESS",
+                  "data": {}
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/addDimension": {
+      "post": {
+        "summary": "新增维度",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Spectaculars",
+                "description": ""
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result",
+                  "description": "统一API响应结果封装"
+                },
+                "example": {
+                  "code": "200",
+                  "message": "SUCCESS",
+                  "data": {}
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/deleteDimension": {
+      "get": {
+        "summary": "删除维度",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "query",
+            "description": "",
+            "required": true,
+            "schema": {
+              "type": "integer"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result",
+                  "description": "统一API响应结果封装"
+                },
+                "example": {
+                  "code": "200",
+                  "message": "SUCCESS",
+                  "data": {}
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/delete": {
+      "get": {
+        "summary": "spectaculars删除",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "query",
+            "description": "",
+            "required": true,
+            "schema": {
+              "type": "integer"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result",
+                  "description": "统一API响应结果封装"
+                },
+                "example": {
+                  "code": "200",
+                  "message": "SUCCESS",
+                  "data": {}
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/updateDimension": {
+      "post": {
+        "summary": "更新维度",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/DimensionParam",
+                "description": ""
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result",
+                  "description": "统一API响应结果封装"
+                },
+                "example": {
+                  "code": "200",
+                  "message": "SUCCESS",
+                  "data": {}
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/update": {
+      "post": {
+        "summary": "spectaculars更新",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Spectaculars",
+                "description": ""
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Result",
+                  "description": "统一API响应结果封装"
+                },
+                "example": {
+                  "code": "200",
+                  "message": "SUCCESS",
+                  "data": {}
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/dimensionDetail": {
+      "post": {
+        "summary": "获取维度详情",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/DimensionDetailParam",
+                "description": ""
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultDimensionDto"
+                },
+                "example": {
+                  "code": "",
+                  "message": "",
+                  "data": {
+                    "id": 0,
+                    "spectacularsDimensionId": 0,
+                    "name": "",
+                    "memo": "",
+                    "unitType": 0,
+                    "viewType": "",
+                    "sections": [
+                      {
+                        "id": 0,
+                        "name": "",
+                        "memo": "",
+                        "statistics": [
+                          {
+                            "id": 0,
+                            "cutDay": "",
+                            "statisticsValue": 0,
+                            "proportion": 0
+                          }
+                        ],
+                        "total": 0
+                      }
+                    ],
+                    "createName": "",
+                    "updateName": "",
+                    "createTime": "",
+                    "updateTime": "",
+                    "selected": false
+                  }
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/report": {
+      "get": {
+        "summary": "导出维度",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "query",
+            "description": "",
+            "required": true,
+            "schema": {
+              "type": "integer"
+            }
+          },
+          {
+            "name": "startTime",
+            "in": "query",
+            "description": "",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          },
+          {
+            "name": "endTime",
+            "in": "query",
+            "description": "",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          },
+          {
+            "name": "timeInterval",
+            "in": "query",
+            "description": "",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object",
+                  "properties": {}
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/detail": {
+      "post": {
+        "summary": "看板详情",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SpectacularsDetailParam",
+                "description": ""
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultSpectacularsDto"
+                },
+                "example": {
+                  "code": "",
+                  "message": "",
+                  "data": {
+                    "id": 0,
+                    "name": "",
+                    "userId": 0,
+                    "dimensions": [
+                      {
+                        "id": 0,
+                        "spectacularsDimensionId": 0,
+                        "name": "",
+                        "memo": "",
+                        "unitType": 0,
+                        "viewType": "",
+                        "sections": [
+                          {
+                            "id": 0,
+                            "name": "",
+                            "memo": "",
+                            "statistics": [
+                              {
+                                "id": 0,
+                                "cutDay": "",
+                                "statisticsValue": 0,
+                                "proportion": 0
+                              }
+                            ],
+                            "total": 0
+                          }
+                        ],
+                        "createName": "",
+                        "updateName": "",
+                        "createTime": "",
+                        "updateTime": "",
+                        "selected": false
+                      }
+                    ]
+                  }
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/list": {
+      "post": {
+        "summary": "spectaculars获取列表",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/SpectacularsListParam",
+                "description": ""
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultListSpectaculars"
+                },
+                "example": {
+                  "code": "",
+                  "message": "",
+                  "data": [
+                    {
+                      "id": 0,
+                      "name": "",
+                      "userId": 0,
+                      "dimensions": [
+                        {
+                          "id": 0,
+                          "spectId": 0,
+                          "dimensionId": 0,
+                          "viewType": ""
+                        }
+                      ]
+                    }
+                  ]
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    },
+    "/spectaculars/yesterdayData": {
+      "post": {
+        "summary": "获取昨日数据",
+        "deprecated": false,
+        "description": "",
+        "tags": [],
+        "parameters": [],
+        "responses": {
+          "200": {
+            "description": "",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ResultListDimensionBo"
+                },
+                "example": {
+                  "code": "",
+                  "message": "",
+                  "data": [
+                    {
+                      "id": 0,
+                      "name": "",
+                      "unitType": 0,
+                      "viewType": "",
+                      "sections": [
+                        {
+                          "id": 0,
+                          "sectionName": "",
+                          "cutDay": "",
+                          "statisticsValue": 0
+                        }
+                      ]
+                    }
+                  ]
+                }
+              }
+            },
+            "headers": {}
+          }
+        },
+        "security": [
+          {
+            "apikey-header-accesstoken": []
+          }
+        ]
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "DimensionSectionStatisticsDto": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "",
+            "format": "int64"
+          },
+          "cutDay": {
+            "type": "string",
+            "description": "日切时间"
+          },
+          "statisticsValue": {
+            "type": "number",
+            "description": "统计值"
+          },
+          "proportion": {
+            "type": "number",
+            "description": "当日占比"
+          }
+        }
+      },
+      "DimensionSectionDto": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "",
+            "format": "int64"
+          },
+          "name": {
+            "type": "string",
+            "description": "名称"
+          },
+          "memo": {
+            "type": "string",
+            "description": "备注"
+          },
+          "statistics": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/DimensionSectionStatisticsDto",
+              "description": "纬度区间"
+            },
+            "description": "统计值"
+          },
+          "total": {
+            "type": "number",
+            "description": "统计总值"
+          }
+        }
+      },
+      "DimensionDto": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "",
+            "format": "int64"
+          },
+          "spectacularsDimensionId": {
+            "type": "integer",
+            "description": "纬度关系id"
+          },
+          "name": {
+            "type": "string",
+            "description": "名称"
+          },
+          "memo": {
+            "type": "string",
+            "description": "备注"
+          },
+          "unitType": {
+            "type": "integer",
+            "description": "单位类型:1秒 2个",
+            "minimum": -127,
+            "maximum": 128
+          },
+          "viewType": {
+            "type": "string",
+            "description": "显示类型 line:折线图 bar:圆饼图 pie:柱状图"
+          },
+          "sections": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/DimensionSectionDto",
+              "description": "纬度区间"
+            },
+            "description": "纬度区间"
+          },
+          "createName": {
+            "type": "string",
+            "description": "创建人"
+          },
+          "updateName": {
+            "type": "string",
+            "description": "更新人"
+          },
+          "createTime": {
+            "type": "string",
+            "description": "创建时间"
+          },
+          "updateTime": {
+            "type": "string",
+            "description": "更新时间"
+          },
+          "selected": {
+            "type": "boolean",
+            "description": "是否选中",
+            "default": false
+          }
+        }
+      },
+      "ResultListDimensionDto": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "string",
+            "description": ""
+          },
+          "message": {
+            "type": "string",
+            "description": ""
+          },
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/DimensionDto",
+              "description": "纬度"
+            },
+            "description": ""
+          }
+        }
+      },
+      "": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "id",
+            "format": "int64"
+          },
+          "orderNo": {
+            "type": "string",
+            "description": "订单号\norderNo订单号"
+          },
+          "review": {
+            "type": "string",
+            "description": "审核内容"
+          },
+          "userId": {
+            "type": "integer",
+            "description": "用户id\nuserId用户id",
+            "format": "int64"
+          },
+          "freezeId": {
+            "type": "integer",
+            "description": "冻结id",
+            "format": "int64"
+          },
+          "amount": {
+            "type": "number",
+            "description": "交易金额\namount交易金额"
+          },
+          "fee": {
+            "type": "number",
+            "description": "手续费\nfee手续费"
+          },
+          "status": {
+            "type": "integer",
+            "description": "状态 1处理中 2审核通过请求第三方 3审核不通过 4提现成功 5提现失败 6超时取消\nstatus状态 1处理中 2审核通过请求第三方 3审核不通过 4提现成功 5提现失败 6超时取消"
+          },
+          "accountType": {
+            "type": "integer",
+            "description": "账号类型 1钱包 2收益"
+          },
+          "channel": {
+            "type": "string",
+            "description": "渠道\nchannel渠道"
+          },
+          "currency": {
+            "type": "string",
+            "description": "币种\ncurrency币种"
+          },
+          "userName": {
+            "type": "string",
+            "description": "用户名\nuserName用户名"
+          },
+          "userPhone": {
+            "type": "string",
+            "description": "手机号\nuserPhone手机号"
+          },
+          "transTime": {
+            "type": "string",
+            "description": "交易时间\ntransTime交易时间"
+          },
+          "successTime": {
+            "type": "string",
+            "description": "交易成功时间\nsuccessTime交易成功时间"
+          },
+          "createUser": {
+            "type": "string",
+            "description": "创建人\ncreateUser创建人"
+          },
+          "updateUser": {
+            "type": "string",
+            "description": "更新人\nupdateUser更新人"
+          },
+          "updateTime": {
+            "type": "string",
+            "description": "更新时间\nupdateTime更新时间"
+          },
+          "createTime": {
+            "type": "string",
+            "description": "创建时间\ncreateTime创建时间"
+          },
+          "version": {
+            "type": "string",
+            "description": "版本号\nversion版本号"
+          },
+          "bank": {
+            "type": "string",
+            "description": "银行"
+          },
+          "bankAccountName": {
+            "type": "string",
+            "description": "用户名\n银行账号姓名"
+          },
+          "bankAccount": {
+            "type": "string",
+            "description": "银行"
+          }
+        }
+      },
+      "SpectacularsDimension": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": ""
+          },
+          "spectId": {
+            "type": "integer",
+            "description": ""
+          },
+          "dimensionId": {
+            "type": "integer",
+            "description": "",
+            "format": "int64"
+          },
+          "viewType": {
+            "type": "string",
+            "description": "显示类型 line:折线图 bar:圆饼图 pie:柱状图"
+          }
+        }
+      },
+      "Result": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "string",
+            "description": ""
+          },
+          "message": {
+            "type": "string",
+            "description": ""
+          },
+          "data": {
+            "$ref": "#/components/schemas/1",
+            "description": ""
+          }
+        }
+      },
+      "Spectaculars": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": ""
+          },
+          "name": {
+            "type": "string",
+            "description": ""
+          },
+          "userId": {
+            "type": "integer",
+            "description": ""
+          },
+          "dimensions": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/SpectacularsDimension",
+              "description": "表名:r_spectaculars_dimension\n表注释:看板纬度关系表"
+            },
+            "description": "关联纬度"
+          }
+        }
+      },
+      "DimensionParam": {
+        "type": "object",
+        "properties": {
+          "viewType": {
+            "type": "string",
+            "description": "显示类型 line:折线图 bar:圆饼图 pie:柱状图"
+          },
+          "id": {
+            "type": "integer",
+            "description": "id"
+          }
+        }
+      },
+      "ResultDimensionDto": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "string",
+            "description": ""
+          },
+          "message": {
+            "type": "string",
+            "description": ""
+          },
+          "data": {
+            "$ref": "#/components/schemas/DimensionDto",
+            "description": ""
+          }
+        }
+      },
+      "DimensionDetailParam": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "id"
+          },
+          "startTime": {
+            "type": "string",
+            "description": "开始时间"
+          },
+          "endTime": {
+            "type": "string",
+            "description": "结束时间"
+          },
+          "timeInterval": {
+            "type": "integer",
+            "description": "时间间隔"
+          },
+          "type": {
+            "type": "integer",
+            "description": "统计类型 1按日统计 2按周统计 3按月统计 4按年统计"
+          },
+          "granularity": {
+            "type": "integer",
+            "description": "颗粒度 1全部应用 2应用级 3渠道级\n颗粒度 1全部应用 2应用级 (默认) 3渠道级"
+          },
+          "appCode": {
+            "type": "string",
+            "description": "应用编号"
+          }
+        }
+      },
+      "SpectacularsDto": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "看板id"
+          },
+          "name": {
+            "type": "string",
+            "description": "名称"
+          },
+          "userId": {
+            "type": "integer",
+            "description": "用户id"
+          },
+          "dimensions": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/DimensionDto",
+              "description": "纬度"
+            },
+            "description": "纬度"
+          }
+        }
+      },
+      "ResultSpectacularsDto": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "string",
+            "description": ""
+          },
+          "message": {
+            "type": "string",
+            "description": ""
+          },
+          "data": {
+            "$ref": "#/components/schemas/SpectacularsDto",
+            "description": ""
+          }
+        }
+      },
+      "SpectacularsDetailParam": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "id"
+          },
+          "startTime": {
+            "type": "string",
+            "description": "开始时间"
+          },
+          "endTime": {
+            "type": "string",
+            "description": "结束时间"
+          },
+          "timeInterval": {
+            "type": "integer",
+            "description": "时间间隔"
+          },
+          "type": {
+            "type": "integer",
+            "description": "统计类型 1按日统计 2按周统计 3按月统计 4按年统计"
+          }
+        }
+      },
+      "ResultListSpectaculars": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "string",
+            "description": ""
+          },
+          "message": {
+            "type": "string",
+            "description": ""
+          },
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/Spectaculars",
+              "description": "com.txz.operating.model.Spectaculars"
+            },
+            "description": ""
+          }
+        }
+      },
+      "SpectacularsListParam": {
+        "type": "object",
+        "properties": {
+          "page": {
+            "type": "integer",
+            "description": "页码"
+          },
+          "size": {
+            "type": "integer",
+            "description": "页码"
+          },
+          "userId": {
+            "type": "string",
+            "description": "用户id"
+          }
+        }
+      },
+      "DimensionSectionBo": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "",
+            "format": "int64"
+          },
+          "sectionName": {
+            "type": "string",
+            "description": "名称"
+          },
+          "cutDay": {
+            "type": "string",
+            "description": "日切时间"
+          },
+          "statisticsValue": {
+            "type": "number",
+            "description": "统计值"
+          }
+        }
+      },
+      "DimensionBo": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "",
+            "format": "int64"
+          },
+          "name": {
+            "type": "string",
+            "description": "名称"
+          },
+          "unitType": {
+            "type": "integer",
+            "description": "单位类型:1秒 2个",
+            "minimum": -127,
+            "maximum": 128
+          },
+          "viewType": {
+            "type": "string",
+            "description": "显示类型 line:折线图 bar:圆饼图 pie:柱状图"
+          },
+          "sections": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/DimensionSectionBo",
+              "description": "纬度区间"
+            },
+            "description": "纬度区间值"
+          }
+        }
+      },
+      "ResultListDimensionBo": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "string",
+            "description": ""
+          },
+          "message": {
+            "type": "string",
+            "description": ""
+          },
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/DimensionBo",
+              "description": "纬度"
+            },
+            "description": ""
+          }
+        }
+      }
+    },
+    "securitySchemes": {
+      "apikey-header-accesstoken": {
+        "type": "apiKey",
+        "in": "header",
+        "name": "accesstoken"
+      }
+    }
+  },
+  "servers": [],
+  "security": []
+}

+ 3 - 2
src/app/shop/admin/order/order/detail.vue

@@ -200,7 +200,7 @@
           <el-table-column :label="t('modules.order.username')" min-width="120" align="center">
             <template #default="{ row }">
               <el-link type="primary" @click="openUserDetail(row.uid)">
-                {{ row.name || '--' }}
+                {{ row.nickname || '--' }}
               </el-link>
             </template>
           </el-table-column>
@@ -315,7 +315,8 @@ async function getOrderDetail() {
     if (code == 200) {
       state.orderDetail = data;
       // 获取拼团信息
-      await getPinkList(data.id);
+
+      if (data.storePink.id) await getPinkList(data.storePink.id);
     }
   } catch (error) {
     console.error('获取订单详情失败:', error);

+ 63 - 1
src/locales/en-US/index.json

@@ -1346,7 +1346,69 @@
       "configure": "Configure",
       "test": "Test",
       "backup": "Backup",
-      "restore": "Restore"
+      "restore": "Restore",
+      "dashboard": "Dashboard",
+      "addReport": "Add Report",
+      "editReport": "Edit Report",
+      "deleteReport": "Delete Report",
+      "reportName": "Report Name",
+      "reportType": "Report Type",
+      "reportDescription": "Report Description",
+      "chartType": "Chart Type",
+      "lineChart": "Line Chart",
+      "barChart": "Bar Chart",
+      "pieChart": "Pie Chart",
+      "scatterChart": "Scatter Chart",
+      "radarChart": "Radar Chart",
+      "funnelChart": "Funnel Chart",
+      "dimensionManagement": "Dimension Management",
+      "addDimension": "Add Dimension",
+      "editDimension": "Edit Dimension",
+      "deleteDimension": "Delete Dimension",
+      "dimensionName": "Dimension Name",
+      "dimensionType": "Dimension Type",
+      "dimensionCode": "Dimension Code",
+      "dimensionDescription": "Dimension Description",
+      "dataType": "Data Type",
+      "timeDimension": "Time Dimension",
+      "regionDimension": "Region Dimension",
+      "productDimension": "Product Dimension",
+      "userDimension": "User Dimension",
+      "channelDimension": "Channel Dimension",
+      "stringType": "String",
+      "numberType": "Number",
+      "dateType": "Date",
+      "booleanType": "Boolean",
+      "refreshData": "Refresh Data",
+      "exportReport": "Export Report",
+      "fullscreen": "Fullscreen",
+      "exitFullscreen": "Exit Fullscreen",
+      "noDataAvailable": "No Data Available",
+      "loadingData": "Loading Data...",
+      "dateRange": "Date Range",
+      "selectDateRange": "Select Date Range",
+      "last7Days": "Last 7 Days",
+      "last30Days": "Last 30 Days",
+      "last90Days": "Last 90 Days",
+      "customRange": "Custom Range",
+      "confirmDeleteReport": "Are you sure you want to delete this report?",
+      "confirmDeleteDimension": "Are you sure you want to delete this dimension?",
+      "reportNameRequired": "Please enter report name",
+      "reportTypeRequired": "Please select report type",
+      "dimensionNameRequired": "Please enter dimension name",
+      "dimensionCodeRequired": "Please enter dimension code",
+      "dimensionTypeRequired": "Please select dimension type",
+      "dataTypeRequired": "Please select data type",
+      "createReportSuccess": "Report created successfully",
+      "updateReportSuccess": "Report updated successfully",
+      "deleteReportSuccess": "Report deleted successfully",
+      "createDimensionSuccess": "Dimension created successfully",
+      "updateDimensionSuccess": "Dimension updated successfully",
+      "deleteDimensionSuccess": "Dimension deleted successfully",
+      "batchDelete": "Batch Delete",
+      "selectItems": "Please select items to operate",
+      "confirmBatchDelete": "Are you sure you want to delete {count} selected items?",
+      "batchDeleteSuccess": "Batch delete successful"
     },
     "marketing": {
       "pending": "Pending",

+ 63 - 1
src/locales/zh-CN/index.json

@@ -1347,7 +1347,69 @@
       "configure": "配置",
       "test": "测试",
       "backup": "备份",
-      "restore": "恢复"
+      "restore": "恢复",
+      "dashboard": "仪表板",
+      "addReport": "新建报表",
+      "editReport": "编辑报表",
+      "deleteReport": "删除报表",
+      "reportName": "报表名称",
+      "reportType": "报表类型",
+      "reportDescription": "报表描述",
+      "chartType": "图表类型",
+      "lineChart": "折线图",
+      "barChart": "柱状图",
+      "pieChart": "饼图",
+      "scatterChart": "散点图",
+      "radarChart": "雷达图",
+      "funnelChart": "漏斗图",
+      "dimensionManagement": "维度管理",
+      "addDimension": "新建维度",
+      "editDimension": "编辑维度",
+      "deleteDimension": "删除维度",
+      "dimensionName": "维度名称",
+      "dimensionType": "维度类型",
+      "dimensionCode": "维度编码",
+      "dimensionDescription": "维度描述",
+      "dataType": "数据类型",
+      "timeDimension": "时间维度",
+      "regionDimension": "地区维度",
+      "productDimension": "产品维度",
+      "userDimension": "用户维度",
+      "channelDimension": "渠道维度",
+      "stringType": "字符串",
+      "numberType": "数字",
+      "dateType": "日期",
+      "booleanType": "布尔值",
+      "refreshData": "刷新数据",
+      "exportReport": "导出报表",
+      "fullscreen": "全屏",
+      "exitFullscreen": "退出全屏",
+      "noDataAvailable": "暂无数据",
+      "loadingData": "数据加载中...",
+      "dateRange": "日期范围",
+      "selectDateRange": "选择日期范围",
+      "last7Days": "最近7天",
+      "last30Days": "最近30天",
+      "last90Days": "最近90天",
+      "customRange": "自定义范围",
+      "confirmDeleteReport": "确定要删除这个报表吗?",
+      "confirmDeleteDimension": "确定要删除这个维度吗?",
+      "reportNameRequired": "请输入报表名称",
+      "reportTypeRequired": "请选择报表类型",
+      "dimensionNameRequired": "请输入维度名称",
+      "dimensionCodeRequired": "请输入维度编码",
+      "dimensionTypeRequired": "请选择维度类型",
+      "dataTypeRequired": "请选择数据类型",
+      "createReportSuccess": "创建报表成功",
+      "updateReportSuccess": "更新报表成功",
+      "deleteReportSuccess": "删除报表成功",
+      "createDimensionSuccess": "创建维度成功",
+      "updateDimensionSuccess": "更新维度成功",
+      "deleteDimensionSuccess": "删除维度成功",
+      "batchDelete": "批量删除",
+      "selectItems": "请选择要操作的项目",
+      "confirmBatchDelete": "确定要删除选中的{count}个项目吗?",
+      "batchDeleteSuccess": "批量删除成功"
     },
     "marketing": {
       "pending": "待开始",