Pārlūkot izejas kodu

feat: 优化细节调整

叶静 1 mēnesi atpakaļ
vecāks
revīzija
dc6d5ccaff

+ 50 - 40
src/app/shop/admin/data/data.service.js

@@ -22,49 +22,50 @@ const route = {
 };
 
 const api = {
+  // 数据报表模块API - 基于OpenAPI定义,添加report前缀
   report: {
-    ...CRUD('shop/admin/data/report', ['list']),
-    getStats: () =>
-      request({
-        url: '/shop/admin/data/report/stats',
-        method: 'GET',
-      }),
-  },
-  // 新增数据报表模块API - 基于OpenAPI定义,添加operating前缀
-  operating: {
-    // 报表管理相关API
+    // 看板管理相关API
     spectaculars: {
-      // 添加报表
+      // 新增看板
       add: (data) =>
         request({
           url: '/operating/spectaculars/add',
           method: 'POST',
           data,
         }),
-      
-      // 删除报表
+
+      // 删除看板
       delete: (id) =>
         request({
-          url: `/operating/spectaculars/delete/${id}`,
-          method: 'DELETE',
+          url: '/operating/spectaculars/delete',
+          method: 'GET',
+          params: { id },
         }),
-      
-      // 获取报表列表
+
+      // 获取看板列表
       list: (params) =>
         request({
           url: '/operating/spectaculars/list',
           method: 'GET',
           params,
         }),
-      
-      // 更新报表
+
+      // 更新看板
       update: (data) =>
         request({
           url: '/operating/spectaculars/update',
-          method: 'PUT',
+          method: 'POST',
           data,
         }),
-      
+
+      // 看板详情
+      detail: (id) =>
+        request({
+          url: '/operating/spectaculars/detail',
+          method: 'GET',
+          params: { id },
+        }),
+
       // 获取昨日数据
       yesterdayData: () =>
         request({
@@ -72,46 +73,55 @@ const api = {
           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',
+          url: '/operating/spectaculars/deleteDimension',
           method: 'GET',
-          params,
+          params: { id },
         }),
-      
+
       // 更新维度
       update: (data) =>
         request({
           url: '/operating/spectaculars/updateDimension',
-          method: 'PUT',
+          method: 'POST',
           data,
         }),
-      
-      // 添加维度视图
-      addView: (data) =>
+
+      // 新增维度视图
+      addView: (spectId) =>
         request({
           url: '/operating/spectaculars/addDimensionView',
-          method: 'POST',
-          data,
+          method: 'GET',
+          params: { spectId },
+        }),
+
+      // 获取维度详情
+      detail: (id) =>
+        request({
+          url: '/operating/spectaculars/dimensionDetail',
+          method: 'GET',
+          params: { id },
+        }),
+
+      // 导出维度
+      export: (id) =>
+        request({
+          url: '/operating/spectaculars/exportDimension',
+          method: 'GET',
+          params: { id },
         }),
     },
   },

+ 53 - 83
src/app/shop/admin/data/report/components/add-chart-dialog.vue → src/app/shop/admin/data/report/components/add-chart-modal.vue

@@ -202,11 +202,11 @@
       </div>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button @click="handleClose">取消</el-button>
+      <el-button @click="handleCancel">取消</el-button>
       <el-button @click="handlePreview" :loading="previewLoading">
         预览图表
       </el-button>
-      <el-button type="primary" @click="handleSave" :loading="saving">
+      <el-button type="primary" @click="handleConfirm" :loading="saving">
         保存图表
       </el-button>
     </el-footer>
@@ -214,7 +214,7 @@
 </template>
 
 <script setup>
-import { ref, reactive, computed, watch } from 'vue';
+import { ref, reactive, computed, watch, onMounted } from 'vue';
 import { ElMessage } from 'element-plus';
 import {
   DataLine,
@@ -257,7 +257,7 @@ const props = defineProps({
   }
 });
 
-const emit = defineEmits(['save', 'close']);
+const emit = defineEmits(['confirm', 'cancel']);
 
 // 响应式数据
 const formRef = ref();
@@ -302,48 +302,11 @@ const formRules = {
 const previewOption = ref({});
 
 // 方法
-const handleClose = () => {
-  emit('update:visible', false);
-  resetForm();
+const handleCancel = () => {
+  emit('cancel');
 };
 
-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 () => {
+const handleConfirm = async () => {
   try {
     await formRef.value.validate();
     
@@ -358,9 +321,8 @@ const handleSave = async () => {
       createTime: new Date().toISOString()
     };
     
-    emit('save', chartData);
+    emit('confirm', chartData);
     ElMessage.success('图表保存成功');
-    handleClose();
     
   } catch (error) {
     console.error('表单验证失败:', error);
@@ -369,9 +331,16 @@ const handleSave = async () => {
   }
 };
 
-const handleDialogClose = () => {
-  resetForm();
-  emit('close');
+const handlePreview = async () => {
+  try {
+    previewLoading.value = true;
+    await generatePreview();
+    ElMessage.success('预览生成成功');
+  } catch (error) {
+    ElMessage.error('预览生成失败');
+  } finally {
+    previewLoading.value = false;
+  }
 };
 
 const generatePreview = async () => {
@@ -472,45 +441,46 @@ watch(() => [formData.type, formData.config.primaryColor, formData.config.backgr
     handlePreview();
   }
 }, { deep: true });
+
+// 初始化预览
+onMounted(() => {
+  handlePreview();
+});
 </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;
-    }
-  }
+.section {
+  margin-bottom: 24px;
   
-  .option-item {
-    display: flex;
-    align-items: center;
-    gap: 8px;
+  .section-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+    margin: 0 0 16px 0;
   }
-  
-  .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;
+}
+
+.option-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.chart-preview {
+  .preview-container {
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    padding: 16px;
+    background: #fafafa;
   }
 }
+
+.sa-footer--submit {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 16px 24px;
+  border-top: 1px solid #e4e7ed;
+  background: #fafafa;
+}
 </style>

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

@@ -1,419 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <!-- 看板基本信息 -->
-      <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>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button @click="handleClose">取消</el-button>
-      <el-button type="primary" @click="handleSave" :loading="saving">
-        保存看板
-      </el-button>
-    </el-footer>
-  </el-container>
-
-  <!-- 图表编辑对话框 -->
-  <AddChartDialog 
-    v-model:visible="chartDialogVisible"
-    :chart-data="currentChart"
-    @save="handleChartSave"
-  />
-</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({});
-
-const emit = defineEmits(['save', 'close']);
-
-// 响应式数据
-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;
-  }
-};
-
-const handleChartDialogClose = () => {
-  resetForm();
-  chartDialogVisible.value = false;
-  emit('close');
-};
-</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>

+ 362 - 0
src/app/shop/admin/data/report/components/add-dashboard-modal.vue

@@ -0,0 +1,362 @@
+<template>
+  <el-container>
+    <el-main>
+      <!-- 看板基本信息 -->
+      <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-info">
+              <div class="chart-icon">
+                <el-icon v-if="chart.type === 'line'"><DataLine /></el-icon>
+                <el-icon v-else-if="chart.type === 'bar'"><Histogram /></el-icon>
+                <el-icon v-else-if="chart.type === 'pie'"><PieChart /></el-icon>
+                <el-icon v-else><TrendCharts /></el-icon>
+              </div>
+              <div class="chart-details">
+                <div class="chart-name">{{ chart.name || '未命名图表' }}</div>
+                <div class="chart-type">{{ 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>
+        
+        <el-empty v-else description="暂无图表,请添加图表" />
+      </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-select v-model="formData.layout.columns" style="width: 100%">
+                  <el-option label="1列" :value="1" />
+                  <el-option label="2列" :value="2" />
+                  <el-option label="3列" :value="3" />
+                  <el-option label="4列" :value="4" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="间距">
+                <el-slider 
+                  v-model="formData.layout.gap" 
+                  :min="0" 
+                  :max="40" 
+                  :step="4"
+                  show-input
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="自动刷新">
+                <el-switch v-model="formData.layout.autoRefresh" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="刷新间隔" v-if="formData.layout.autoRefresh">
+                <el-select v-model="formData.layout.refreshInterval" style="width: 100%">
+                  <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-col>
+          </el-row>
+        </el-form>
+      </div>
+    </el-main>
+    <el-footer class="sa-footer--submit">
+      <el-button @click="handleCancel">取消</el-button>
+      <el-button type="primary" @click="handleConfirm" :loading="saving">
+        保存看板
+      </el-button>
+    </el-footer>
+  </el-container>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+import {
+  Plus,
+  Edit,
+  Delete,
+  TrendCharts,
+  PieChart,
+  DataLine,
+  Histogram
+} from '@element-plus/icons-vue';
+import { useModal } from '@/sheep/components/sa-modal/sa-modal.vue';
+import AddChartModal from './add-chart-modal.vue';
+
+const props = defineProps({
+  dashboardData: {
+    type: Object,
+    default: () => ({})
+  }
+});
+
+const emit = defineEmits(['confirm', 'cancel']);
+
+// 响应式数据
+const formRef = ref();
+const saving = ref(false);
+
+const formData = reactive({
+  name: '',
+  type: 'sales',
+  description: '',
+  charts: [],
+  layout: {
+    columns: 2,
+    gap: 16,
+    autoRefresh: false,
+    refreshInterval: 300
+  }
+});
+
+const formRules = {
+  name: [
+    { required: true, message: '请输入看板名称', trigger: 'blur' },
+    { min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' }
+  ],
+  type: [
+    { required: true, message: '请选择看板类型', trigger: 'change' }
+  ],
+  description: [
+    { required: true, message: '请输入看板描述', trigger: 'blur' }
+  ]
+};
+
+// 方法
+const handleCancel = () => {
+  emit('cancel');
+};
+
+const handleConfirm = 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('confirm', dashboardData);
+    ElMessage.success('看板创建成功');
+    
+  } catch (error) {
+    console.error('表单验证失败:', error);
+  } finally {
+    saving.value = false;
+  }
+};
+
+const addChart = () => {
+  useModal(() => import('./add-chart-modal.vue'), {
+    title: '添加图表',
+    width: '80%',
+    height: '80vh',
+    componentProps: {
+      chartData: {}
+    },
+    confirm: (modalRef) => {
+      formData.charts.push({ ...modalRef.chartData });
+      ElMessage.success('图表添加成功');
+      return true;
+    }
+  });
+};
+
+const editChart = (index) => {
+  useModal(() => import('./add-chart-modal.vue'), {
+    title: '编辑图表',
+    width: '80%',
+    height: '80vh',
+    componentProps: {
+      chartData: { ...formData.charts[index] }
+    },
+    confirm: (modalRef) => {
+      formData.charts[index] = { ...modalRef.chartData };
+      ElMessage.success('图表更新成功');
+      return true;
+    }
+  });
+};
+
+const removeChart = (index) => {
+  formData.charts.splice(index, 1);
+  ElMessage.success('图表删除成功');
+};
+
+const getChartTypeLabel = (type) => {
+  const typeMap = {
+    line: '折线图',
+    bar: '柱状图',
+    pie: '饼图',
+    area: '面积图'
+  };
+  return typeMap[type] || '未知类型';
+};
+
+// 初始化数据
+if (props.dashboardData && Object.keys(props.dashboardData).length > 0) {
+  Object.assign(formData, props.dashboardData);
+}
+</script>
+
+<style scoped>
+.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-info {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      
+      .chart-icon {
+        width: 40px;
+        height: 40px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: #f0f9ff;
+        border-radius: 8px;
+        color: #409eff;
+        font-size: 20px;
+      }
+      
+      .chart-details {
+        .chart-name {
+          font-weight: 600;
+          color: #303133;
+          margin-bottom: 4px;
+        }
+        
+        .chart-type {
+          font-size: 12px;
+          color: #909399;
+        }
+      }
+    }
+    
+    .chart-actions {
+      display: flex;
+      gap: 8px;
+    }
+  }
+}
+
+.sa-footer--submit {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 16px 24px;
+  border-top: 1px solid #e4e7ed;
+  background: #fafafa;
+}
+</style>

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

@@ -1,104 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <el-form :model="formData" :rules="formRules" ref="formRef" label-width="80px">
-        <el-form-item label="报表名称" prop="name">
-          <el-input v-model="formData.name" placeholder="请输入报表名称" />
-        </el-form-item>
-        <el-form-item label="报表描述" prop="description">
-          <el-input v-model="formData.description" type="textarea" placeholder="请输入报表描述" />
-        </el-form-item>
-        <el-form-item label="报表类型" prop="type">
-          <el-select v-model="formData.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>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button v-if="type === 'add'" type="primary" @click="handleSave" :loading="saving">
-        保存
-      </el-button>
-      <el-button v-if="type === 'edit'" type="primary" @click="handleSave" :loading="saving">
-        更新
-      </el-button>
-    </el-footer>
-  </el-container>
-</template>
-
-<script setup>
-import { ref, reactive } from 'vue'
-import { ElMessage } from 'element-plus'
-
-const props = defineProps({
-  type: {
-    type: String,
-    default: 'add'
-  },
-  reportData: {
-    type: Object,
-    default: () => ({})
-  }
-})
-
-const emit = defineEmits(['save'])
-
-const formRef = ref()
-const saving = ref(false)
-
-const formData = reactive({
-  name: props.reportData?.name || '',
-  description: props.reportData?.description || '',
-  type: props.reportData?.type || ''
-})
-
-const formRules = {
-  name: [
-    { required: true, message: '请输入报表名称', trigger: 'blur' }
-  ],
-  type: [
-    { required: true, message: '请选择报表类型', trigger: 'change' }
-  ]
-}
-
-// 保存报表
-const handleSave = async () => {
-  if (!formRef.value) return
-  
-  try {
-    await formRef.value.validate()
-    saving.value = true
-    
-    // 模拟保存
-    await new Promise(resolve => setTimeout(resolve, 1000))
-    
-    emit('save', { ...formData })
-    ElMessage.success(props.type === 'add' ? '报表创建成功!' : '报表更新成功!')
-  } catch (error) {
-    console.error('保存失败:', error)
-  } finally {
-    saving.value = false
-  }
-}
-
-// 重置表单
-const resetForm = () => {
-  if (formRef.value) {
-    formRef.value.resetFields()
-  }
-}
-
-// 暴露方法给 useModal
-defineExpose({
-  handleSave,
-  resetForm,
-  saving
-})
-</script>
-
-<style scoped>
-/* 使用 el-container 布局,不需要额外的 padding */
-</style>

+ 105 - 0
src/app/shop/admin/data/report/components/add-report-modal.vue

@@ -0,0 +1,105 @@
+<template>
+  <div class="add-report-modal">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-form-item label="报表名称" prop="name">
+        <el-input v-model="form.name" placeholder="请输入报表名称" />
+      </el-form-item>
+      <el-form-item label="报表描述" prop="description">
+        <el-input 
+          v-model="form.description" 
+          type="textarea" 
+          :rows="3" 
+          placeholder="请输入报表描述" 
+        />
+      </el-form-item>
+      <el-form-item label="报表类型" prop="type">
+        <el-select v-model="form.type" placeholder="请选择报表类型" style="width: 100%">
+          <el-option label="销售报表" value="sales" />
+          <el-option label="用户报表" value="user" />
+          <el-option label="财务报表" value="finance" />
+          <el-option label="运营报表" value="operation" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: '新增报表'
+  },
+  type: {
+    type: String,
+    default: 'add'
+  },
+  reportData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emit = defineEmits(['confirm'])
+
+const formRef = ref()
+const form = reactive({
+  name: '',
+  description: '',
+  type: ''
+})
+
+const rules = {
+  name: [
+    { required: true, message: '请输入报表名称', trigger: 'blur' },
+    { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
+  ],
+  description: [
+    { required: true, message: '请输入报表描述', trigger: 'blur' }
+  ],
+  type: [
+    { required: true, message: '请选择报表类型', trigger: 'change' }
+  ]
+}
+
+// 监听props变化,初始化表单数据
+watch(() => props.reportData, (newData) => {
+  if (newData && Object.keys(newData).length > 0) {
+    Object.assign(form, newData)
+  }
+}, { immediate: true, deep: true })
+
+// 表单验证和提交
+const handleConfirm = async () => {
+  try {
+    await formRef.value.validate()
+    emit('confirm', { ...form })
+    return true
+  } catch (error) {
+    ElMessage.error('请完善表单信息')
+    return false
+  }
+}
+
+// 暴露给父组件的方法
+defineExpose({
+  handleConfirm
+})
+</script>
+
+<style scoped>
+.add-report-modal {
+  padding: 20px;
+}
+
+.el-form {
+  max-width: 500px;
+}
+
+.el-form-item {
+  margin-bottom: 20px;
+}
+</style>

+ 49 - 45
src/app/shop/admin/data/report/components/dashboard-tabs.vue

@@ -141,8 +141,7 @@
 <script setup>
 import { ref, reactive, computed, onMounted, nextTick } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { useModal } from '@/sheep/hooks'
-import AddReportDialog from './add-report-dialog.vue'
+import { useModal } from '@/sheep/components/sa-modal/sa-modal.vue'
 import {
   Plus,
   Refresh,
@@ -254,7 +253,7 @@ const deleteReport = async (tabId) => {
     })
 
     // 调用API删除报表
-    await api.operating.spectaculars.delete(tabId)
+    await api.report.spectaculars.delete(tabId)
     
     // 从本地数组中移除
     const index = tabs.value.findIndex(t => t.id === tabId)
@@ -490,41 +489,48 @@ const generateChartOption = (tab) => {
 
 // 新增报表
 const addReport = () => {
-  useModal(
-    AddReportDialog,
-    { title: '新增报表', type: 'add' },
-    {
-      confirm: (reportData) => {
-        // 添加新标签页
-        const newId = String(tabs.value.length + 1)
-        tabs.value.push({
-          id: newId,
-          name: reportData.name,
-          description: reportData.description,
-          type: reportData.type,
-          loading: false,
-          chartType: 'line',
-          chartTitle: reportData.name + '趋势',
-          searchKeyword: '',
-          stats: [],
-          chartData: [],
-          tableData: [],
-          filteredTableData: []
-        })
-        activeTab.value = newId
-        loadReportData(newId)
-        ElMessage.success('报表创建成功!')
-      },
+  useModal({
+    title: '新增报表',
+    width: '600px',
+    height: '400px',
+    component: () => import('./add-report-modal.vue'),
+    componentProps: {
+      title: '新增报表',
+      type: 'add'
     },
-  )
+    confirm: (reportData) => {
+      // 添加新标签页
+      const newId = String(tabs.value.length + 1)
+      tabs.value.push({
+        id: newId,
+        name: reportData.name,
+        description: reportData.description,
+        type: reportData.type,
+        loading: false,
+        chartType: 'line',
+        chartTitle: reportData.name + '趋势',
+        searchKeyword: '',
+        stats: [],
+        chartData: [],
+        tableData: [],
+        filteredTableData: []
+      })
+      activeTab.value = newId
+      loadReportData(newId)
+      ElMessage.success('报表创建成功!')
+    }
+  })
 }
 
 // 编辑报表
 const editReport = (tab) => {
-  useModal(
-    AddReportDialog,
-    { 
-      title: '编辑报表', 
+  useModal({
+    title: '编辑报表',
+    width: '600px',
+    height: '400px',
+    component: () => import('./add-report-modal.vue'),
+    componentProps: {
+      title: '编辑报表',
       type: 'edit',
       reportData: {
         name: tab.name,
@@ -532,18 +538,16 @@ const editReport = (tab) => {
         type: tab.type
       }
     },
-    {
-      confirm: (reportData) => {
-        // 更新标签页数据
-        tab.name = reportData.name
-        tab.description = reportData.description
-        tab.type = reportData.type
-        tab.chartTitle = reportData.name + '趋势'
-        loadReportData(tab.id)
-        ElMessage.success('报表更新成功!')
-      },
-    },
-  )
+    confirm: (reportData) => {
+      // 更新标签页数据
+      tab.name = reportData.name
+      tab.description = reportData.description
+      tab.type = reportData.type
+      tab.chartTitle = reportData.name + '趋势'
+      loadReportData(tab.id)
+      ElMessage.success('报表更新成功!')
+    }
+  })
 }
 
 // 暴露方法

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

@@ -1,584 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <!-- 数据概览 -->
-      <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>
-    </el-main>
-  </el-container>
-</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({
-  dataType: {
-    type: String,
-    required: true
-  }
-});
-
-const emit = defineEmits(['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('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>

+ 557 - 0
src/app/shop/admin/data/report/components/data-display-modal.vue

@@ -0,0 +1,557 @@
+<template>
+  <div class="data-display-modal">
+    <!-- 数据概览 -->
+    <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-content" v-loading="chartLoading">
+        <v-chart
+          class="chart"
+          :option="chartOption"
+          :loading="chartLoading"
+          autoresize
+        />
+      </div>
+    </div>
+
+    <!-- 数据表格 -->
+    <div class="table-section">
+      <div class="table-header">
+        <h4>详细数据</h4>
+        <div class="table-actions">
+          <el-input
+            v-model="searchKeyword"
+            placeholder="搜索数据"
+            style="width: 200px;"
+            clearable
+            @input="handleSearch"
+          >
+            <template #prefix>
+              <el-icon><Search /></el-icon>
+            </template>
+          </el-input>
+          <el-button type="primary" @click="exportData">
+            <el-icon><Download /></el-icon>
+            导出数据
+          </el-button>
+        </div>
+      </div>
+      <div class="table-content">
+        <el-table
+          :data="tableData"
+          v-loading="tableLoading"
+          stripe
+          border
+          style="width: 100%"
+        >
+          <el-table-column
+            v-for="column in tableColumns"
+            :key="column.prop"
+            :prop="column.prop"
+            :label="column.label"
+            :width="column.width"
+            :formatter="column.formatter"
+          >
+            <template #default="{ row, column }" v-if="column.prop === 'trend'">
+              <span :class="row[column.prop] > 0 ? 'trend-positive' : 'trend-negative'">
+                <el-icon><ArrowUp v-if="row[column.prop] > 0" /><ArrowDown v-else /></el-icon>
+                {{ Math.abs(row[column.prop]) }}%
+              </span>
+            </template>
+            <template #default="{ row, column }" v-else>
+              <span>{{ 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>
+</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({
+  dataType: {
+    type: String,
+    required: true
+  }
+});
+
+const emit = defineEmits(['confirm', 'cancel']);
+
+// 响应式数据
+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: '¥125,680',
+  label: '总销售额',
+  trend: 12.5
+});
+
+const chartOption = ref({});
+const tableData = ref([]);
+const tableColumns = ref([]);
+const totalCount = ref(0);
+
+// 方法
+const handleTimeRangeChange = (value) => {
+  customDateRange.value = [];
+  loadData();
+};
+
+const handleCustomDateChange = (value) => {
+  if (value && value.length === 2) {
+    timeRange.value = '';
+    loadData();
+  }
+};
+
+const updateChart = () => {
+  generateChartOption();
+};
+
+const handleSearch = () => {
+  currentPage.value = 1;
+  loadTableData();
+};
+
+const handleSizeChange = (size) => {
+  pageSize.value = size;
+  currentPage.value = 1;
+  loadTableData();
+};
+
+const handleCurrentChange = (page) => {
+  currentPage.value = page;
+  loadTableData();
+};
+
+const exportData = () => {
+  ElMessage.success('数据导出功能开发中...');
+};
+
+const loadData = () => {
+  loadOverviewData();
+  generateChartOption();
+  loadTableData();
+};
+
+const loadOverviewData = () => {
+  // 模拟加载概览数据
+  const mockData = {
+    sales: { value: '¥125,680', label: '总销售额', trend: 12.5 },
+    orders: { value: '1,234', label: '总订单数', trend: -3.2 },
+    users: { value: '8,567', label: '总用户数', trend: 8.9 },
+    goods: { value: '456', label: '总商品数', trend: 5.1 }
+  };
+  overviewData.value = mockData[props.dataType] || mockData.sales;
+};
+
+const generateChartOption = () => {
+  chartLoading.value = true;
+  
+  // 模拟异步加载
+  setTimeout(() => {
+    const mockData = generateMockChartData();
+    
+    if (chartType.value === 'pie') {
+      chartOption.value = {
+        title: {
+          text: chartTitle.value,
+          left: 'center'
+        },
+        tooltip: {
+          trigger: 'item',
+          formatter: '{a} <br/>{b}: {c} ({d}%)'
+        },
+        legend: {
+          orient: 'vertical',
+          left: 'left'
+        },
+        series: [{
+          name: '数据分布',
+          type: 'pie',
+          radius: '50%',
+          data: mockData.pieData,
+          emphasis: {
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)'
+            }
+          }
+        }]
+      };
+    } else {
+      chartOption.value = {
+        title: {
+          text: chartTitle.value
+        },
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          data: ['数据']
+        },
+        xAxis: {
+          type: 'category',
+          data: mockData.categories
+        },
+        yAxis: {
+          type: 'value'
+        },
+        series: [{
+          name: '数据',
+          type: chartType.value,
+          data: mockData.values
+        }]
+      };
+    }
+    
+    chartLoading.value = false;
+  }, 1000);
+};
+
+const generateMockChartData = () => {
+  const categories = ['1月', '2月', '3月', '4月', '5月', '6月'];
+  const values = categories.map(() => Math.floor(Math.random() * 1000) + 100);
+  const pieData = [
+    { value: 335, name: '类别A' },
+    { value: 310, name: '类别B' },
+    { value: 234, name: '类别C' },
+    { value: 135, name: '类别D' },
+    { value: 1548, name: '类别E' }
+  ];
+  
+  return { categories, values, pieData };
+};
+
+const loadTableData = () => {
+  tableLoading.value = true;
+  
+  // 模拟异步加载
+  setTimeout(() => {
+    const mockTableData = generateMockTableData();
+    tableData.value = mockTableData.data;
+    tableColumns.value = mockTableData.columns;
+    totalCount.value = mockTableData.total;
+    tableLoading.value = false;
+  }, 800);
+};
+
+const generateMockTableData = () => {
+  const columns = [
+    { prop: 'date', label: '日期', width: 120 },
+    { prop: 'value', label: '数值', width: 100 },
+    { prop: 'trend', label: '趋势', width: 100 },
+    { prop: 'remark', label: '备注', width: 200 }
+  ];
+  
+  const data = [];
+  for (let i = 0; i < pageSize.value; i++) {
+    data.push({
+      date: `2024-01-${String(i + 1).padStart(2, '0')}`,
+      value: Math.floor(Math.random() * 10000),
+      trend: (Math.random() - 0.5) * 20,
+      remark: `数据备注 ${i + 1}`
+    });
+  }
+  
+  return {
+    columns,
+    data,
+    total: 156
+  };
+};
+
+// 生命周期
+onMounted(() => {
+  loadData();
+});
+
+// 监听数据类型变化
+watch(() => props.dataType, () => {
+  loadData();
+}, { immediate: true });
+</script>
+
+<style scoped>
+.data-display-modal {
+  padding: 20px;
+  max-height: 80vh;
+  overflow-y: auto;
+}
+
+.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;
+  background: rgba(255, 255, 255, 0.2);
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24px;
+}
+
+.overview-content {
+  flex: 1;
+}
+
+.overview-value {
+  font-size: 32px;
+  font-weight: 700;
+  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;
+}
+
+.overview-trend.positive {
+  color: #67c23a;
+}
+
+.overview-trend.negative {
+  color: #f56c6c;
+}
+
+.time-range-selector {
+  margin-bottom: 24px;
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 16px;
+}
+
+.chart-section {
+  margin-bottom: 32px;
+}
+
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.chart-header h4 {
+  margin: 0;
+  font-size: 18px;
+  color: #303133;
+}
+
+.chart-content {
+  background: white;
+  border-radius: 8px;
+  padding: 20px;
+  border: 1px solid #ebeef5;
+}
+
+.chart {
+  width: 100%;
+  height: 400px;
+}
+
+.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;
+    }
+  }
+}
+
+.trend-positive {
+  color: #67c23a;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.trend-negative {
+  color: #f56c6c;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+</style>

+ 0 - 188
src/app/shop/admin/data/report/components/dimension-edit-dialog.vue

@@ -1,188 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <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>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button v-if="type === 'add'" type="primary" @click="handleSave" :loading="submitting">
-        保存
-      </el-button>
-      <el-button v-if="type === 'edit'" type="primary" @click="handleSave" :loading="submitting">
-        更新
-      </el-button>
-    </el-footer>
-  </el-container>
-</template>
-
-<script setup>
-import { ref, reactive, watch } from 'vue'
-import { ElMessage } from 'element-plus'
-
-const props = defineProps({
-  type: {
-    type: String,
-    default: 'add'
-  },
-  dimensionData: {
-    type: Object,
-    default: () => ({})
-  }
-})
-
-const emit = defineEmits(['save'])
-
-const formRef = ref()
-const submitting = ref(false)
-
-const formData = reactive({
-  name: '',
-  code: '',
-  type: '',
-  dataType: '',
-  unit: '',
-  status: 1,
-  description: '',
-  config: ''
-})
-
-const formRules = {
-  name: [
-    { required: true, message: '请输入维度名称', trigger: 'blur' }
-  ],
-  code: [
-    { required: true, message: '请输入维度编码', trigger: 'blur' }
-  ],
-  type: [
-    { required: true, message: '请选择维度类型', trigger: 'change' }
-  ],
-  dataType: [
-    { required: true, message: '请选择数据类型', trigger: 'change' }
-  ]
-}
-
-// 监听 props 变化,初始化表单数据
-watch(() => props.dimensionData, (newData) => {
-  if (newData && Object.keys(newData).length > 0) {
-    Object.assign(formData, {
-      name: newData.name || '',
-      code: newData.code || '',
-      type: newData.type || '',
-      dataType: newData.dataType || '',
-      unit: newData.unit || '',
-      status: newData.status ?? 1,
-      description: newData.description || '',
-      config: newData.config || ''
-    })
-  }
-}, { immediate: true })
-
-// 保存维度
-const handleSave = async () => {
-  if (!formRef.value) return
-  
-  try {
-    await formRef.value.validate()
-    
-    // 验证配置项格式
-    if (formData.config) {
-      try {
-        JSON.parse(formData.config)
-      } catch (error) {
-        ElMessage.error('配置项格式错误,请输入有效的JSON格式')
-        return
-      }
-    }
-    
-    submitting.value = true
-    
-    // 模拟保存
-    await new Promise(resolve => setTimeout(resolve, 1000))
-    
-    emit('save', { ...formData })
-    ElMessage.success(props.type === 'add' ? '维度创建成功!' : '维度更新成功!')
-  } catch (error) {
-    console.error('保存失败:', error)
-  } finally {
-    submitting.value = false
-  }
-}
-
-// 重置表单
-const resetForm = () => {
-  if (formRef.value) {
-    formRef.value.resetFields()
-  }
-}
-
-// 暴露方法给 useModal
-defineExpose({
-  handleSave,
-  resetForm,
-  submitting
-})
-</script>
-
-<style scoped>
-/* 使用 el-container 布局,不需要额外的 padding */
-</style>

+ 136 - 0
src/app/shop/admin/data/report/components/dimension-edit-modal.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="dimension-edit-modal">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+      <el-form-item label="维度名称" prop="name">
+        <el-input v-model="form.name" placeholder="请输入维度名称" />
+      </el-form-item>
+      <el-form-item label="维度编码" prop="code">
+        <el-input v-model="form.code" placeholder="请输入维度编码" />
+      </el-form-item>
+      <el-form-item label="维度描述" prop="memo">
+        <el-input 
+          v-model="form.memo" 
+          type="textarea" 
+          :rows="3" 
+          placeholder="请输入维度描述" 
+        />
+      </el-form-item>
+      <el-form-item label="单位类型" prop="unitType">
+        <el-select v-model="form.unitType" placeholder="请选择单位类型" style="width: 100%">
+          <el-option label="数量" :value="0" />
+          <el-option label="金额" :value="1" />
+          <el-option label="百分比" :value="2" />
+          <el-option label="时间" :value="3" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="视图类型" prop="viewType">
+        <el-select v-model="form.viewType" placeholder="请选择视图类型" style="width: 100%">
+          <el-option label="折线图" value="line" />
+          <el-option label="柱状图" value="bar" />
+          <el-option label="饼图" value="pie" />
+          <el-option label="表格" value="table" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-switch 
+          v-model="form.status" 
+          :active-value="1" 
+          :inactive-value="0"
+          active-text="启用" 
+          inactive-text="禁用" 
+        />
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: '编辑维度'
+  },
+  type: {
+    type: String,
+    default: 'add'
+  },
+  dimensionData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emit = defineEmits(['confirm'])
+
+const formRef = ref()
+const form = reactive({
+  id: null,
+  name: '',
+  code: '',
+  memo: '',
+  unitType: 0,
+  viewType: '',
+  status: 1
+})
+
+const rules = {
+  name: [
+    { required: true, message: '请输入维度名称', trigger: 'blur' },
+    { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
+  ],
+  code: [
+    { required: true, message: '请输入维度编码', trigger: 'blur' },
+    { pattern: /^[a-zA-Z0-9_]+$/, message: '编码只能包含字母、数字和下划线', trigger: 'blur' }
+  ],
+  memo: [
+    { required: true, message: '请输入维度描述', trigger: 'blur' }
+  ],
+  unitType: [
+    { required: true, message: '请选择单位类型', trigger: 'change' }
+  ],
+  viewType: [
+    { required: true, message: '请选择视图类型', trigger: 'change' }
+  ]
+}
+
+// 监听props变化,初始化表单数据
+watch(() => props.dimensionData, (newData) => {
+  if (newData && Object.keys(newData).length > 0) {
+    Object.assign(form, newData)
+  }
+}, { immediate: true, deep: true })
+
+// 表单验证和提交
+const handleConfirm = async () => {
+  try {
+    await formRef.value.validate()
+    emit('confirm', { ...form })
+    return true
+  } catch (error) {
+    ElMessage.error('请完善表单信息')
+    return false
+  }
+}
+
+// 暴露给父组件的方法
+defineExpose({
+  handleConfirm
+})
+</script>
+
+<style scoped>
+.dimension-edit-modal {
+  padding: 20px;
+}
+
+.el-form {
+  max-width: 600px;
+}
+
+.el-form-item {
+  margin-bottom: 20px;
+}
+</style>

+ 34 - 19
src/app/shop/admin/data/report/components/dimension-management.vue

@@ -141,9 +141,7 @@ import {
   Check, Close
 } from '@element-plus/icons-vue'
 import { api } from '../../data.service.js'
-import { useModal } from '@/composables/useModal'
-import DimensionEditDialog from './dimension-edit-dialog.vue'
-import DimensionViewDialog from './dimension-view-dialog.vue'
+import { useModal } from '@/sheep/components/sa-modal/sa-modal.vue'
 
 // 响应式数据
 const loading = ref(false)
@@ -178,7 +176,7 @@ const loadData = async () => {
       ...searchForm
     }
 
-    const response = await api.operating.dimensions.list(params)
+    const response = await api.report.dimensions.list(params)
     if (response.code === 200) {
       tableData.value = response.data.list || []
       pagination.total = response.data.total || 0
@@ -222,40 +220,57 @@ const generateMockData = () => {
 
 // 事件处理
 const handleAdd = () => {
-  useModal(DimensionEditDialog, {
+  useModal(() => import('./dimension-edit-modal.vue'), {
     title: '新增维度',
     width: '600px',
-    props: {
+    height: '500px',
+    componentProps: {
+      title: '新增维度',
       type: 'add',
       dimensionData: {}
     },
     confirm: async (modalRef) => {
-      await modalRef.handleSave()
-      loadData()
+      const result = await modalRef.handleConfirm()
+      if (result) {
+        await api.report.dimensions.add(modalRef.form)
+        ElMessage.success('新增成功')
+        loadData()
+        return true
+      }
+      return false
     }
   })
 }
 
 const handleEdit = (row) => {
-  useModal(DimensionEditDialog, {
+  useModal(() => import('./dimension-edit-modal.vue'), {
     title: '编辑维度',
     width: '600px',
-    props: {
+    height: '500px',
+    componentProps: {
+      title: '编辑维度',
       type: 'edit',
       dimensionData: row
     },
     confirm: async (modalRef) => {
-      await modalRef.handleSave()
-      loadData()
+      const result = await modalRef.handleConfirm()
+      if (result) {
+        await api.report.dimensions.update(modalRef.form)
+        ElMessage.success('编辑成功')
+        loadData()
+        return true
+      }
+      return false
     }
   })
 }
 
 const handleView = (row) => {
-  useModal(DimensionViewDialog, {
+  useModal(() => import('./dimension-view-modal.vue'), {
     title: '维度详情',
-    width: '500px',
-    props: {
+    width: '700px',
+    height: '600px',
+    componentProps: {
       dimensionData: row
     }
   })
@@ -273,7 +288,7 @@ const handleDelete = async (row) => {
       }
     )
 
-    const response = await api.operating.dimensions.delete(row.id)
+    const response = await api.report.dimensions.delete(row.id)
     if (response.code === 200) {
       ElMessage.success('删除成功')
       loadData()
@@ -290,7 +305,7 @@ const handleDelete = async (row) => {
 
 const handleStatusChange = async (row) => {
   try {
-    const response = await api.operating.dimensions.update(row.id, {
+    const response = await api.report.dimensions.update(row.id, {
       status: row.status
     })
     if (response.code === 200) {
@@ -426,9 +441,9 @@ const handleSubmit = async () => {
 
     let response
     if (isEdit.value) {
-      response = await api.operating.dimensions.update(formData.id, submitData)
+      response = await api.report.dimensions.update(formData.id, submitData)
     } else {
-      response = await api.operating.dimensions.add(submitData)
+      response = await api.report.dimensions.add(submitData)
     }
 
     if (response.code === 200) {

+ 0 - 128
src/app/shop/admin/data/report/components/dimension-view-dialog.vue

@@ -1,128 +0,0 @@
-<template>
-  <el-container>
-    <el-main>
-      <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-main>
-  </el-container>
-</template>
-
-<script setup>
-import { computed } from 'vue'
-
-const props = defineProps({
-  dimensionData: {
-    type: Object,
-    default: () => ({})
-  }
-})
-
-const viewData = computed(() => props.dimensionData || {})
-
-// 获取维度类型标签
-const getDimensionTypeTag = (type) => {
-  const typeMap = {
-    time: 'primary',
-    region: 'success',
-    product: 'warning',
-    user: 'info',
-    channel: 'danger'
-  }
-  return typeMap[type] || 'info'
-}
-
-// 获取维度类型标签文本
-const getDimensionTypeLabel = (type) => {
-  const typeMap = {
-    time: '时间维度',
-    region: '地区维度',
-    product: '产品维度',
-    user: '用户维度',
-    channel: '渠道维度'
-  }
-  return typeMap[type] || type
-}
-
-// 获取数据类型标签
-const getDataTypeTag = (dataType) => {
-  const typeMap = {
-    string: 'primary',
-    number: 'success',
-    date: 'warning',
-    boolean: 'info'
-  }
-  return typeMap[dataType] || 'info'
-}
-
-// 获取数据类型标签文本
-const getDataTypeLabel = (dataType) => {
-  const typeMap = {
-    string: '字符串',
-    number: '数字',
-    date: '日期',
-    boolean: '布尔值'
-  }
-  return typeMap[dataType] || dataType
-}
-
-// 格式化日期
-const formatDate = (date) => {
-  if (!date) return '-'
-  return new Date(date).toLocaleString('zh-CN')
-}
-
-// 格式化配置项
-const formatConfig = (config) => {
-  if (!config) return ''
-  try {
-    return JSON.stringify(JSON.parse(config), null, 2)
-  } catch (error) {
-    return config
-  }
-}
-</script>
-
-<style scoped>
-/* 使用 el-container 布局,不需要额外的 padding */
-
-.config-display {
-  background-color: #f5f7fa;
-  border: 1px solid #e4e7ed;
-  border-radius: 4px;
-  padding: 12px;
-  margin: 0;
-  font-size: 12px;
-  line-height: 1.5;
-  color: #606266;
-  white-space: pre-wrap;
-  word-break: break-all;
-}
-</style>

+ 115 - 0
src/app/shop/admin/data/report/components/dimension-view-modal.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="dimension-view-modal">
+    <div class="dimension-info">
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="维度ID">
+          {{ dimensionData.id || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="维度名称">
+          {{ dimensionData.name || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="维度编码">
+          {{ dimensionData.code || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="单位类型">
+          {{ getUnitTypeLabel(dimensionData.unitType) }}
+        </el-descriptions-item>
+        <el-descriptions-item label="视图类型">
+          {{ getViewTypeLabel(dimensionData.viewType) }}
+        </el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="dimensionData.status === 1 ? 'success' : 'danger'">
+            {{ dimensionData.status === 1 ? '启用' : '禁用' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建人">
+          {{ dimensionData.createName || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间">
+          {{ dimensionData.createTime || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="更新人">
+          {{ dimensionData.updateName || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="更新时间">
+          {{ dimensionData.updateTime || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="维度描述" :span="2">
+          {{ dimensionData.memo || '-' }}
+        </el-descriptions-item>
+      </el-descriptions>
+    </div>
+
+    <!-- 维度数据统计 -->
+    <div v-if="dimensionData.sections && dimensionData.sections.length > 0" class="dimension-stats">
+      <h4>数据统计</h4>
+      <el-table :data="dimensionData.sections" border style="width: 100%">
+        <el-table-column prop="sectionName" label="分组名称" />
+        <el-table-column prop="statisticsValue" label="统计值" />
+        <el-table-column prop="proportion" label="占比" >
+          <template #default="{ row }">
+            {{ (row.proportion * 100).toFixed(2) }}%
+          </template>
+        </el-table-column>
+        <el-table-column prop="total" label="总计" />
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+  dimensionData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+// 获取单位类型标签
+const getUnitTypeLabel = (type) => {
+  const typeMap = {
+    0: '数量',
+    1: '金额',
+    2: '百分比',
+    3: '时间'
+  }
+  return typeMap[type] || '-'
+}
+
+// 获取视图类型标签
+const getViewTypeLabel = (type) => {
+  const typeMap = {
+    'line': '折线图',
+    'bar': '柱状图',
+    'pie': '饼图',
+    'table': '表格'
+  }
+  return typeMap[type] || '-'
+}
+</script>
+
+<style scoped>
+.dimension-view-modal {
+  padding: 20px;
+}
+
+.dimension-info {
+  margin-bottom: 20px;
+}
+
+.dimension-stats {
+  margin-top: 20px;
+}
+
+.dimension-stats h4 {
+  margin-bottom: 15px;
+  color: #303133;
+  font-weight: 600;
+}
+
+:deep(.el-descriptions__label) {
+  font-weight: 600;
+}
+</style>

+ 215 - 257
src/app/shop/admin/data/report/components/report-detail-dialog.vue → src/app/shop/admin/data/report/components/report-detail-modal.vue

@@ -1,238 +1,230 @@
 <template>
-  <el-container>
-    <el-main>
-      <!-- 报表头部信息 -->
-      <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 class="report-detail-modal">
+    <!-- 报表头部信息 -->
+    <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 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 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 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>
+
+    <!-- 筛选条件 -->
+    <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>
-            </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 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>
-          </template>
-          <div class="chart-container" v-loading="chartLoading">
-            <v-chart :option="chartOption" autoresize style="height: 400px;" />
+          </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>
-        </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>
+        </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>
-          </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>
-    </el-main>
-    <el-footer class="sa-footer--submit">
-      <el-button @click="handleClose">关闭</el-button>
-      <el-button type="primary" @click="saveReport">
-        保存报表
-      </el-button>
-    </el-footer>
-  </el-container>
+        </template>
+        
+        <el-table
+          :data="tableData"
+          v-loading="tableLoading"
+          @sort-change="handleSortChange"
+          stripe
+          style="width: 100%"
+        >
+          <el-table-column prop="date" label="日期" sortable width="120" />
+          <el-table-column prop="type" label="数据类型" width="120">
+            <template #default="{ row }">
+              <el-tag :type="getDataTypeColor(row.type)">{{ getDataTypeLabel(row.type) }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="value" label="数值" sortable width="120">
+            <template #default="{ row }">
+              <span class="number-value">{{ formatNumber(row.value) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="growth" label="增长率" sortable width="120">
+            <template #default="{ row }">
+              <span :class="row.growth >= 0 ? 'growth-positive' : 'growth-negative'">
+                {{ row.growth >= 0 ? '+' : '' }}{{ row.growth.toFixed(1) }}%
+              </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="备注" show-overflow-tooltip />
+          <el-table-column label="操作" width="150" 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>
 
 <script setup>
-import { ref, reactive, computed, watch, onMounted } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ref, reactive, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
 import {
   Refresh,
   Download,
@@ -244,9 +236,6 @@ import {
   ArrowDown,
   TrendCharts,
   Bottom,
-  DataLine,
-  Histogram,
-  PieChart,
   Money,
   ShoppingCart,
   User,
@@ -287,8 +276,6 @@ const props = defineProps({
   }
 });
 
-const emit = defineEmits(['save', 'close']);
-
 // 响应式数据
 const refreshing = ref(false);
 const filterExpanded = ref(true);
@@ -348,10 +335,6 @@ const tableData = ref([]);
 const chartOption = ref({});
 
 // 方法
-const handleClose = () => {
-  emit('close');
-};
-
 const refreshReport = async () => {
   refreshing.value = true;
   try {
@@ -437,11 +420,6 @@ const editRow = (row, index) => {
   ElMessage.info(`编辑行: ${index + 1}`);
 };
 
-const saveReport = () => {
-  ElMessage.success('报表保存成功');
-  handleClose();
-};
-
 // 工具方法
 const formatDate = (date) => {
   if (!date) return '-';
@@ -606,28 +584,14 @@ const loadChartData = async () => {
   }
 };
 
-// 监听器
-watch(() => props.visible, (newVal) => {
-  if (newVal) {
-    loadReportData();
-  }
-});
-
 // 生命周期
 onMounted(() => {
-  if (props.visible) {
-    loadReportData();
-  }
+  loadReportData();
 });
 </script>
 
 <style scoped>
-.report-detail-dialog {
-  .dialog-content {
-    max-height: 80vh;
-    overflow-y: auto;
-  }
-  
+.report-detail-modal {
   .report-header {
     display: flex;
     justify-content: space-between;
@@ -807,11 +771,5 @@ onMounted(() => {
     justify-content: center;
     margin-top: 20px;
   }
-  
-  .dialog-footer {
-    display: flex;
-    justify-content: flex-end;
-    gap: 12px;
-  }
 }
 </style>

+ 330 - 163
src/app/shop/admin/data/report/index.vue

@@ -57,57 +57,71 @@
           <div class="page-header">
             <h1 class="page-title">数据概览</h1>
             <p class="page-subtitle">实时数据分析与监控</p>
+            <div class="header-actions">
+              <el-button @click="getYesterdayData" :loading="loading">刷新数据</el-button>
+              <el-button type="primary" @click="showDimensionManagement">
+                <el-icon><Setting /></el-icon>
+                维度管理
+              </el-button>
+            </div>
           </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 v-loading="loading" class="charts-grid">
+            <div 
+              v-for="item in yesterdayData" 
+              :key="item.id" 
+              class="chart-card"
+            >
+              <div class="chart-header">
+                <h3>{{ item.name }}</h3>
+                <div class="chart-actions">
+                  <el-button text size="small" @click="showDataDisplay(item.name)">
+                    查看详情
+                  </el-button>
                 </div>
               </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 class="chart-content">
+                <div class="chart-summary">
+                  <div 
+                    v-for="section in item.sections" 
+                    :key="section.id"
+                    class="summary-item"
+                  >
+                    <div class="summary-label">{{ section.sectionName }}</div>
+                    <div class="summary-value">
+                      {{ formatValue(section.statisticsValue, item.unitType) }}
+                    </div>
+                  </div>
                 </div>
-                <div class="stat-action">
-                  <el-icon><ArrowRight /></el-icon>
+                <!-- 这里可以添加实际的图表组件 -->
+                <div class="chart-placeholder">
+                  <div class="chart-type-indicator">{{ getChartTypeLabel(item.viewType) }}</div>
                 </div>
               </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>
+          
+          <!-- 统计摘要 -->
+          <div class="stats-summary">
+            <div class="summary-card">
+              <div class="summary-title">今日概览</div>
+              <div class="summary-stats">
+                <div class="summary-stat">
+                  <span class="stat-label">总用户数</span>
+                  <span class="stat-value">{{ statsData.totalUsers.toLocaleString() }}</span>
                 </div>
-              </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 class="summary-stat">
+                  <span class="stat-label">总订单数</span>
+                  <span class="stat-value">{{ statsData.totalOrders.toLocaleString() }}</span>
                 </div>
-                <div class="stat-action">
-                  <el-icon><ArrowRight /></el-icon>
+                <div class="summary-stat">
+                  <span class="stat-label">总销售额</span>
+                  <span class="stat-value">¥{{ statsData.totalSales.toLocaleString() }}</span>
                 </div>
               </div>
             </div>
           </div>
-
-          <!-- 图表区域 -->
-          <div class="charts-section">
-            <DashboardTabs ref="dashboardRef" @refresh="refreshData" />
-          </div>
         </div>
 
         <!-- 图表页面 -->
@@ -159,45 +173,31 @@
 
     <!-- 维度管理对话框已改为 useModal 调用 -->
     
-    <!-- 数据展示对话框 -->
-    <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"
-    />
+    <!-- 数据展示对话框已改为 useModal 调用 -->
+
+    <!-- 新增看板对话框已改为 useModal 调用 -->
+
+    <!-- 报表详情对话框已改为 useModal 调用 -->
   </div>
 </template>
 <script setup>
 import { onMounted, reactive, ref } from 'vue';
-import { api } from '../data.service';
 import { ElMessage } from 'element-plus';
 import { Bell, Setting, ArrowRight } from '@element-plus/icons-vue';
-import { useModal } from '@/sheep/hooks';
+import { useModal } from '@/sheep/components/sa-modal/sa-modal.vue';
+import { api } from '../data.service.js';
 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';
+import DataDisplayModal from './components/data-display-modal.vue';
+import AddDashboardModal from './components/add-dashboard-modal.vue';
+import ReportDetailModal from './components/report-detail-modal.vue';
 
 const components = {
   DashboardTabs,
   DimensionManagement,
-  DataDisplayDialog,
-  AddDashboardDialog,
-  ReportDetailDialog
+  DataDisplayModal,
+  AddDashboardModal,
+  ReportDetailModal
 };
 
 // 当前活动标签
@@ -209,9 +209,7 @@ const currentDataType = ref('');
 const dashboardRef = ref();
 // 新增看板对话框
 const addDashboardVisible = ref(false);
-// 报表详情对话框
-const reportDetailVisible = ref(false);
-const currentReportData = ref(null);
+// 报表详情对话框已改为 useModal 调用
 
 // 统计数据
 const statsData = reactive({
@@ -221,63 +219,142 @@ const statsData = reactive({
   totalGoods: 0,
 });
 
-// 获取统计数据
-async function getStatsData() {
+// 昨日数据
+const yesterdayData = ref([]);
+const loading = ref(false);
+
+// 获取昨日数据
+async function getYesterdayData() {
   try {
-    const { code, data } = await api.report.getStats();
-    if (code == 200) {
-      Object.assign(statsData, data);
+    loading.value = true;
+    // 使用真实API或mock数据
+    const response = await api.report.spectaculars.yesterdayData();
+    // 使用API数据
+    const result = response;
+    
+    if (result.code === '200') {
+      yesterdayData.value = result.data;
+      // 计算统计数据
+      calculateStats(result.data);
     }
   } catch (error) {
-    console.error('获取统计数据失败:', error);
+    // API调用失败
+    console.error('Failed to load yesterday data:', error);
+    ElMessage.error('数据加载失败');
+  } finally {
+    loading.value = false;
   }
 }
 
+// 计算统计数据
+function calculateStats(data) {
+  let totalUsers = 0;
+  let totalOrders = 0;
+  let totalSales = 0;
+  let totalGoods = 0;
+  
+  data.forEach(item => {
+    item.sections.forEach(section => {
+      if (item.name.includes('用户')) {
+        totalUsers += section.statisticsValue;
+      } else if (item.name.includes('订单')) {
+        totalOrders += section.statisticsValue;
+      } else if (item.name.includes('金额') || item.name.includes('付费')) {
+        totalSales += section.statisticsValue;
+      } else if (item.name.includes('商品')) {
+        totalGoods += section.statisticsValue;
+      }
+    });
+  });
+  
+  Object.assign(statsData, {
+    totalSales,
+    totalOrders,
+    totalUsers,
+    totalGoods
+  });
+}
+
 // 显示数据展示页面
 const showDataDisplay = (type) => {
-  currentDataType.value = type;
-  dataDisplayVisible.value = true;
-};
-
-// 处理数据展示对话框关闭
-const handleDataDisplayClose = () => {
-  dataDisplayVisible.value = false;
-  currentDataType.value = '';
+  useModal(() => import('./components/data-display-modal.vue'), {
+    title: `${type} - 数据详情`,
+    width: '90%',
+    height: '80vh',
+    componentProps: {
+      dataType: type
+    },
+    confirm: () => {
+      ElMessage.success('数据查看完成');
+      return true;
+    }
+  });
 };
 
 // 显示新增看板页面
 const showAddDashboard = () => {
-  addDashboardVisible.value = true;
-};
-
-// 处理看板保存
-const handleDashboardSave = (dashboardData) => {
-  console.log('保存看板数据:', dashboardData);
-  addDashboardVisible.value = false;
-  ElMessage.success('看板创建成功!');
-  refreshData();
+  useModal(() => import('./components/add-dashboard-modal.vue'), {
+    title: '新增看板',
+    width: '80%',
+    height: '80vh',
+    componentProps: {
+      dashboardData: {}
+    },
+    confirm: (modalRef) => {
+      console.log('保存看板数据:', modalRef.dashboardData);
+      ElMessage.success('看板创建成功!');
+      refreshData();
+      return true;
+    }
+  });
 };
 
 // 显示报表详情页面
 const showReportDetail = () => {
-  currentReportData.value = {
+  useModal(() => import('./components/report-detail-modal.vue'), {
     title: '数据报表详情',
-    type: 'general'
-  };
-  reportDetailVisible.value = true;
+    width: '90%',
+    height: '85vh',
+    componentProps: {
+      reportData: {
+        title: '数据报表详情',
+        type: 'general'
+      }
+    },
+    confirm: (modalRef) => {
+      console.log('保存报表数据:', modalRef.reportData);
+      ElMessage.success('报表保存成功!');
+      refreshData();
+      return true;
+    }
+  });
 };
 
-// 处理报表保存
-const handleReportSave = (reportData) => {
-  console.log('保存报表数据:', reportData);
-  reportDetailVisible.value = false;
-  ElMessage.success('报表保存成功!');
-  refreshData();
-};
+// 格式化数值
+function formatValue(value, unitType) {
+  if (unitType === 1) {
+    // 金额类型
+    return `¥${value.toLocaleString()}`;
+  } else {
+    // 数量类型
+    return value.toLocaleString();
+  }
+}
+
+// 获取图表类型标签
+function getChartTypeLabel(viewType) {
+  const typeMap = {
+    line: '折线图',
+    bar: '柱状图',
+    pie: '饼图',
+    area: '面积图'
+  };
+  return typeMap[viewType] || '图表';
+}
 
 // 刷新数据
 const refreshData = () => {
-  getStatsData();
+  getYesterdayData();
   if (dashboardRef.value) {
     dashboardRef.value.refreshAllTabs();
   }
@@ -285,19 +362,20 @@ const refreshData = () => {
 
 // 显示维度管理
 const showDimensionManagement = () => {
-  useModal(
-    DimensionManagement,
-    { title: '维度管理', width: '90%' },
-    {
-      confirm: () => {
-        refreshData();
-      },
-    },
-  );
+  useModal(() => import('./components/dimension-management.vue'), {
+    title: '维度管理',
+    width: '90%',
+    height: '80vh',
+    componentProps: {},
+    confirm: () => {
+      refreshData();
+      return true;
+    }
+  });
 };
 
 onMounted(() => {
-  getStatsData();
+  getYesterdayData();
   refreshData();
 });
 </script>
@@ -420,101 +498,190 @@ onMounted(() => {
 
 /* 页面标题 */
 .page-header {
-  margin-bottom: 32px;
+  text-align: center;
+  margin-bottom: 40px;
+  padding: 40px 0;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border-radius: 16px;
+  position: relative;
+  overflow: hidden;
+}
+
+.page-header::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="50" cy="50" r="1" fill="%23ffffff" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
+  opacity: 0.3;
 }
 
 .page-title {
-  font-size: 28px;
-  font-weight: 600;
-  color: #303133;
+  font-size: 32px;
+  font-weight: 700;
   margin: 0 0 8px 0;
+  position: relative;
+  z-index: 1;
 }
 
 .page-subtitle {
   font-size: 16px;
-  color: #909399;
-  margin: 0;
+  opacity: 0.9;
+  margin: 0 0 20px 0;
+  position: relative;
+  z-index: 1;
 }
 
-/* 统计卡片 */
-.stats-overview {
-  margin-bottom: 32px;
+.header-actions {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 20px;
 }
 
-.stats-grid {
+.header-actions .el-button {
+  background: rgba(255, 255, 255, 0.2);
+  border: 1px solid rgba(255, 255, 255, 0.3);
+  color: white;
+}
+
+.header-actions .el-button:hover {
+  background: rgba(255, 255, 255, 0.3);
+  border-color: rgba(255, 255, 255, 0.5);
+}
+
+/* 图表网格样式 */
+.charts-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
   gap: 20px;
+  margin-bottom: 30px;
 }
 
-.stat-card {
-  background: #ffffff;
+.chart-card {
+  background: white;
   border-radius: 12px;
-  padding: 24px;
-  display: flex;
-  align-items: center;
-  gap: 16px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  border: 1px solid #f0f0f0;
+  overflow: hidden;
   transition: all 0.3s ease;
-  cursor: pointer;
 }
 
-.stat-card:hover {
+.chart-card:hover {
   transform: translateY(-2px);
-  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+  border-color: #409eff;
 }
 
-.stat-action {
-  color: #909399;
+.chart-header {
+  padding: 20px 24px 16px;
+  border-bottom: 1px solid #f0f0f0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.chart-header h3 {
+  margin: 0;
   font-size: 16px;
-  transition: all 0.3s ease;
+  font-weight: 600;
+  color: #1a1a1a;
 }
 
-.stat-card:hover .stat-action {
-  color: #409eff;
-  transform: translateX(2px);
+.chart-content {
+  padding: 20px 24px;
 }
 
-.stat-icon {
-  width: 48px;
-  height: 48px;
-  border-radius: 12px;
+.chart-summary {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.summary-item {
+  flex: 1;
+  min-width: 120px;
+}
+
+.summary-label {
+  font-size: 12px;
+  color: #666;
+  margin-bottom: 4px;
+}
+
+.summary-value {
+  font-size: 20px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.chart-placeholder {
+  height: 120px;
+  background: #f8f9fa;
+  border-radius: 8px;
   display: flex;
   align-items: center;
   justify-content: center;
-  font-size: 24px;
+  border: 2px dashed #e0e0e0;
 }
 
-.stat-icon.sales {
-  background: linear-gradient(135deg, #67c23a, #85ce61);
+.chart-type-indicator {
+  color: #666;
+  font-size: 14px;
+  font-weight: 500;
 }
 
-.stat-icon.orders {
-  background: linear-gradient(135deg, #409eff, #66b1ff);
+/* 统计摘要样式 */
+.stats-summary {
+  margin-top: 30px;
 }
 
-.stat-icon.users {
-  background: linear-gradient(135deg, #e6a23c, #ebb563);
+.summary-card {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  border: 1px solid #f0f0f0;
 }
 
-.stat-icon.goods {
-  background: linear-gradient(135deg, #f56c6c, #f78989);
+.summary-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1a1a1a;
+  margin-bottom: 20px;
 }
 
-.stat-content {
-  flex: 1;
+.summary-stats {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 24px;
 }
 
-.stat-value {
-  font-size: 24px;
-  font-weight: 600;
-  color: #303133;
-  margin-bottom: 4px;
+.summary-stat {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px;
+  background: #f8f9fa;
+  border-radius: 8px;
 }
 
-.stat-label {
+.summary-stat .stat-label {
   font-size: 14px;
-  color: #909399;
+  color: #666;
+  font-weight: 500;
+}
+
+.summary-stat .stat-value {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1a1a1a;
 }
 
 /* 图表区域 */

+ 2 - 2
src/app/shop/admin/finance/withdraw/audit.vue

@@ -13,14 +13,14 @@
         </el-form-item>
 
         <!-- 通道代码选择 -->
-        <el-form-item v-if="auditType === 'approve' && formData.channelId"
+        <!-- <el-form-item v-if="auditType === 'approve' && formData.channelId"
           :label="t('modules.withdraw.channelCode') + ':'" prop="channelCode">
           <el-radio-group v-model="formData.channelCode">
             <el-radio v-for="code in channelCodeOptions" :key="code.value" :label="code.value">
               {{ code.label }}
             </el-radio>
           </el-radio-group>
-        </el-form-item>
+        </el-form-item> -->
 
         <!-- 审核内容 -->
         <el-form-item