Browse Source

feat: update

叶静 3 weeks ago
parent
commit
da92648bda

+ 0 - 119
src/app/shop/admin/data/adv.json

@@ -1,119 +0,0 @@
-{
-  "code": "200",
-  "data": [
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "1",
-      "memo": "商城停留时间统计",
-      "name": "商城停留时间统计",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 1,
-      "updateName": "",
-      "updateTime": "2020-09-02 10:28:10",
-      "viewType": "line"
-    },
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "2",
-      "memo": "种子统计",
-      "name": "种子统计",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 1,
-      "updateName": "",
-      "updateTime": "2020-09-02 10:28:10",
-      "viewType": "line"
-    },
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "3",
-      "memo": "装饰统计",
-      "name": "装饰统计",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 1,
-      "updateName": "",
-      "updateTime": "2022-08-09 09:15:03",
-      "viewType": "line"
-    },
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "4",
-      "memo": "虫子统计",
-      "name": "虫子统计",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 1,
-      "updateName": "",
-      "updateTime": "2020-09-02 10:28:12",
-      "viewType": "line"
-    },
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "17",
-      "memo": "次日留存",
-      "name": "次日留存",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 2,
-      "updateName": "",
-      "updateTime": "2022-08-09 09:16:40",
-      "viewType": "line"
-    },
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "18",
-      "memo": "7日留存",
-      "name": "7日留存",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 2,
-      "updateName": "",
-      "updateTime": "2022-08-09 09:16:40",
-      "viewType": "line"
-    },
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "21",
-      "memo": "入账笔数",
-      "name": "入账笔数",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 2,
-      "updateName": "",
-      "updateTime": "2022-08-09 09:16:40",
-      "viewType": "line"
-    },
-    {
-      "createName": "admin",
-      "createTime": "2020-01-07 13:30:02",
-      "id": "23",
-      "memo": "出账笔数",
-      "name": "出账笔数",
-      "sections": [],
-      "selected": false,
-      "spectacularsDimensionId": 0,
-      "unitType": 2,
-      "updateName": "",
-      "updateTime": "2022-08-09 09:16:40",
-      "viewType": "line"
-    }
-  ],
-  "message": "SUCCESS",
-  "voice": ""
-}

+ 4 - 12
src/app/shop/admin/data/data.service.js

@@ -43,7 +43,6 @@ const api = {
           method: 'GET',
           params: { id },
         }),
-
       // 获取看板列表
       list: (data) =>
         request({
@@ -87,6 +86,7 @@ const api = {
 
     // 维度管理相关API
     dimensions: {
+      ...CRUD('operating/spectaculars', ['report']),
       // 新增维度
       add: (data) =>
         request({
@@ -112,19 +112,11 @@ const api = {
         }),
 
       // 获取维度详情
-      detail: (id) =>
+      detail: (data) =>
         request({
           url: '/operating/spectaculars/dimensionDetail',
-          method: 'GET',
-          params: { id, userId: userInfo?.id },
-        }),
-
-      // 导出维度
-      export: (id) =>
-        request({
-          url: '/operating/spectaculars/exportDimension',
-          method: 'GET',
-          params: { id, userId: userInfo?.id },
+          method: 'POST',
+          data: { ...data, userId: userInfo?.id },
         }),
     },
 

+ 252 - 0
src/app/shop/admin/data/report/components/chart-tips-box.vue

@@ -0,0 +1,252 @@
+<template>
+  <div v-if="visible" class="chart-tips-overlay" @click="handleOverlayClick">
+    <div class="chart-tips-box" @click.stop>
+      <!-- 标题栏 -->
+      <div class="tips-header">
+        <span class="tips-title">{{ data.title }}</span>
+        <button class="close-btn" @click="$emit('close')">
+          <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
+            <path d="M12 4L4 12M4 4L12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
+          </svg>
+        </button>
+      </div>
+
+      <!-- 数据列表 -->
+      <div class="tips-content">
+        <div v-for="(item, index) in data.items" :key="index" class="tips-item"
+          :class="{ 'is-summary': item.name.includes('汇总') }">
+          <div class="item-left">
+            <div class="color-indicator" :style="{ backgroundColor: item.color }"></div>
+            <span class="item-name">{{ item.name }}</span>
+          </div>
+          <div class="item-right">
+            <span class="item-value">{{ formatNumber(item.value) }}</span>
+            <span class="item-percent">{{ item.percent }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+// Props
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  data: {
+    type: Object,
+    default: () => ({
+      title: '',
+      items: []
+    })
+  }
+})
+
+// Emits
+const emit = defineEmits(['close'])
+
+// 处理遮罩层点击
+function handleOverlayClick() {
+  emit('close')
+}
+
+// 格式化数字
+function formatNumber(value) {
+  if (typeof value !== 'number') return value
+  return value.toLocaleString()
+}
+</script>
+
+<style scoped>
+.chart-tips-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 9999;
+  display: flex;
+  align-items: flex-start;
+  justify-content: center;
+  padding-top: 10vh;
+}
+
+.chart-tips-box {
+  background: #1a1a1a;
+  border-radius: 8px;
+  min-width: 400px;
+  max-width: 600px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
+  overflow: hidden;
+  animation: slideDown 0.3s ease-out;
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-20px);
+  }
+
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.tips-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  background: #1a1a1a;
+  border-bottom: 1px solid #333;
+}
+
+.tips-title {
+  color: #fff;
+  font-size: 16px;
+  font-weight: 500;
+  margin: 0;
+}
+
+.close-btn {
+  background: none;
+  border: none;
+  color: #999;
+  cursor: pointer;
+  padding: 4px;
+  border-radius: 4px;
+  transition: all 0.2s;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.close-btn:hover {
+  color: #fff;
+  background: rgba(255, 255, 255, 0.1);
+}
+
+.tips-content {
+  padding: 8px 0;
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.tips-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 20px;
+  transition: background-color 0.2s;
+}
+
+.tips-item:hover {
+  background: rgba(255, 255, 255, 0.05);
+}
+
+.tips-item.is-summary {
+  border-top: 1px solid #333;
+  background: rgba(255, 255, 255, 0.02);
+}
+
+.item-left {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+
+.color-indicator {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-right: 12px;
+  flex-shrink: 0;
+}
+
+.item-name {
+  color: #fff;
+  font-size: 14px;
+  line-height: 1.4;
+}
+
+.item-right {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.item-value {
+  color: #fff;
+  font-size: 16px;
+  font-weight: 500;
+  min-width: 80px;
+  text-align: right;
+}
+
+.item-percent {
+  color: #4ade80;
+  font-size: 14px;
+  font-weight: 500;
+  min-width: 60px;
+  text-align: right;
+  background: rgba(74, 222, 128, 0.1);
+  padding: 2px 8px;
+  border-radius: 12px;
+  border: 1px solid rgba(74, 222, 128, 0.2);
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .chart-tips-overlay {
+    padding-top: 5vh;
+  }
+
+  .chart-tips-box {
+    min-width: 90vw;
+    max-width: 90vw;
+    margin: 0 5vw;
+  }
+
+  .tips-item {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+  }
+
+  .item-right {
+    width: 100%;
+    justify-content: space-between;
+  }
+
+  .item-value,
+  .item-percent {
+    min-width: auto;
+  }
+}
+
+/* 滚动条样式 */
+.tips-content::-webkit-scrollbar {
+  width: 6px;
+}
+
+.tips-content::-webkit-scrollbar-track {
+  background: rgba(255, 255, 255, 0.1);
+  border-radius: 3px;
+}
+
+.tips-content::-webkit-scrollbar-thumb {
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 3px;
+}
+
+.tips-content::-webkit-scrollbar-thumb:hover {
+  background: rgba(255, 255, 255, 0.5);
+}
+</style>

+ 211 - 462
src/app/shop/admin/data/report/components/dataDisplayEdit.vue

@@ -1,124 +1,58 @@
 <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 class="data-display-edit">
+    <!-- 筛选表单 -->
+    <div class="filter-form">
+      <el-form :model="filterForm" label-width="80px" 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" size="default" clearable />
+        </el-form-item>
+
+        <!-- 时间区间 -->
+        <el-form-item label="时间区间">
+          <el-select v-model="filterForm.timeInterval" placeholder="选择时间区间" size="default" clearable>
+            <el-option label="最近7天" :value="7" />
+            <el-option label="最近30天" :value="30" />
+            <el-option label="最近90天" :value="90" />
+          </el-select>
+        </el-form-item>
+
+        <!-- 汇总周期 -->
+        <el-form-item label="汇总周期">
+          <el-select v-model="filterForm.summaryPeriod" placeholder="选择汇总周期" size="default" clearable>
+            <el-option label="按日" :value="1" />
+            <el-option label="按周" :value="2" />
+            <el-option label="按月" :value="3" />
+          </el-select>
+        </el-form-item>
+
+        <!-- 颗粒度 -->
+        <el-form-item label="颗粒度">
+          <el-select v-model="filterForm.granularity" placeholder="选择颗粒度" size="default" clearable>
+            <el-option label="应用级" :value="2" />
+            <el-option label="页面级" :value="1" />
+            <el-option label="组件级" :value="3" />
+          </el-select>
+        </el-form-item>
+
+        <!-- 操作按钮 -->
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch" :loading="chartLoading">
+            查询
+          </el-button>
+          <el-button @click="handleReset">
+            重置
+          </el-button>
+        </el-form-item>
+      </el-form>
     </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 class="chart-container" v-loading="chartLoading">
+        <report-chart :data="chartData" :type="chartType" :height="500" :title="chartTitle" :show-date-picker="false"
+          @type-change="handleChartTypeChange" />
       </div>
     </div>
   </div>
@@ -127,431 +61,246 @@
 <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
-]);
+import { api } from '../../data.service.js';
+import ReportChart from './report-chart.vue';
 
 const props = defineProps({
-  dataType: {
-    type: String,
-    required: true
+  modal: {
+    type: Object,
+    default: () => ({})
   }
 });
 
 const emit = defineEmits(['confirm', 'cancel']);
 
-// 响应式数据
-const timeRange = ref('month');
-const customDateRange = ref([]);
+// 筛选表单数据
+const filterForm = ref({
+  dateRange: null, // 时间范围,默认为null
+  timeInterval: null, // 时间区间,默认为null
+  summaryPeriod: null, // 汇总周期,默认为null
+  granularity: null // 颗粒度,默认为null
+});
+
 const chartType = ref('line');
-const searchKeyword = ref('');
-const currentPage = ref(1);
-const pageSize = ref(20);
 const chartLoading = ref(false);
-const tableLoading = ref(false);
+const dimensionData = ref(null);
+const chartData = ref([]);
 
 // 计算属性
 const dialogTitle = computed(() => {
-  const titles = {
-    sales: '销售数据详情',
-    orders: '订单数据详情',
-    users: '用户数据详情',
-    goods: '商品数据详情'
-  };
-  return titles[props.dataType] || '数据详情';
+  return dimensionData.value?.name || '数据详情';
 });
 
 const iconComponent = computed(() => {
-  const icons = {
-    sales: TrendCharts,
-    orders: Document,
-    users: User,
-    goods: Box
-  };
-  return icons[props.dataType] || TrendCharts;
+  return TrendCharts;
 });
 
 const chartTitle = computed(() => {
-  const titles = {
-    sales: '销售趋势分析',
-    orders: '订单趋势分析',
-    users: '用户增长分析',
-    goods: '商品数据分析'
-  };
-  return titles[props.dataType] || '数据分析';
+  return dimensionData.value?.name || '数据分析';
 });
 
-// 模拟数据
-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();
+  loadDimensionDetail();
 };
 
-const handleSizeChange = (size) => {
-  pageSize.value = size;
-  currentPage.value = 1;
-  loadTableData();
+const handleReset = () => {
+  filterForm.value = {
+    dateRange: null,
+    timeInterval: null,
+    summaryPeriod: null,
+    granularity: null
+  };
+  loadDimensionDetail();
 };
 
-const handleCurrentChange = (page) => {
-  currentPage.value = page;
-  loadTableData();
-};
+// 处理图表类型切换
+const handleChartTypeChange = async (newType) => {
+  const dimensionId = props.modal.params.id;
+  if (!dimensionId) {
+    ElMessage.error('缺少维度ID');
+    return;
+  }
+
+  try {
+    await api.report.dimensions.update({
+      viewType: newType,
+      id: dimensionId
+    });
 
-const exportData = () => {
-  ElMessage.success('数据导出功能开发中...');
+    chartType.value = newType;
+    ElMessage.success('图表类型更新成功');
+  } catch (error) {
+    console.error('更新图表类型失败:', error);
+    ElMessage.error('更新图表类型失败');
+  }
 };
 
 const loadData = () => {
-  loadOverviewData();
-  generateChartOption();
-  loadTableData();
+  loadDimensionDetail();
 };
 
-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 loadDimensionDetail = async () => {
+  const dimensionId = props.modal?.params.id;
+  if (!dimensionId) {
+    console.warn('缺少维度ID');
+    return;
+  }
 
-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)'
-            }
-          }
-        }]
-      };
+
+  try {
+    const params = {
+      id: dimensionId,
+      timeInterval: filterForm.value.timeInterval || null,
+      type: filterForm.value.summaryPeriod || null,
+      granularity: filterForm.value.granularity || null
+    };
+
+    // 添加时间范围参数
+    if (filterForm.value.dateRange && filterForm.value.dateRange.length === 2) {
+      params.startTime = filterForm.value.dateRange[0];
+      params.endTime = filterForm.value.dateRange[1];
+    } else {
+      params.startTime = null;
+      params.endTime = null;
+    }
+
+    const { code, data } = await api.report.dimensions.detail(params);
+
+    if (code === '200' && data) {
+      dimensionData.value = data;
+      processChartData(data);
+      chartType.value = data.viewType || 'line';
     } 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
-        }]
-      };
+      ElMessage.error('获取维度详情失败');
     }
-    
+  } catch (error) {
+    console.error('获取维度详情失败:', error);
+    ElMessage.error('获取维度详情失败,请重试');
+  } finally {
     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 processChartData = (data) => {
+  if (!data || !data.sections || data.sections.length === 0) {
+    chartData.value = [];
+    return;
+  }
 
-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);
-};
+  // 获取所有日期(从第一个有数据的section中提取)
+  const firstSection = data.sections.find(section =>
+    section.statistics && section.statistics.length > 0
+  );
 
-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}`
-    });
+  if (!firstSection) {
+    chartData.value = [];
+    return;
   }
-  
-  return {
-    columns,
-    data,
-    total: 156
-  };
+
+  const result = [];
+
+  // 为每个非汇总section生成数据
+  data.sections.forEach(section => {
+    if (section.statistics && !section.name.includes('汇总')) {
+      section.statistics.forEach(stat => {
+        result.push({
+          x: stat.cutDay,
+          y: stat.statisticsValue,
+          series: section.name
+        });
+      });
+    }
+  });
+
+  chartData.value = result;
 };
 
-// 生命周期
+
+// 组件挂载时加载数据
 onMounted(() => {
   loadData();
 });
 
-// 监听数据类型变化
-watch(() => props.dataType, () => {
-  loadData();
-}, { immediate: true });
+
 </script>
 
 <style scoped>
-.data-display-modal {
+.data-display-edit {
+  width: 100%;
   padding: 20px;
   max-height: 80vh;
   overflow-y: auto;
 }
 
-.data-overview {
+.filter-form {
   margin-bottom: 24px;
+  padding: 20px;
+  background: #f8f9fa;
+  border-radius: 8px;
+  border: 1px solid #e4e7ed;
 }
 
-.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;
+.filter-form .el-form {
+  margin-bottom: 0;
 }
 
-.overview-trend.positive {
-  color: #67c23a;
+.filter-form .el-form-item {
+  margin-bottom: 16px;
 }
 
-.overview-trend.negative {
-  color: #f56c6c;
+.filter-form .el-form-item:last-child {
+  margin-bottom: 0;
 }
 
-.time-range-selector {
-  margin-bottom: 24px;
-  display: flex;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 16px;
+.chart-section {
+  background: #fff;
+  border-radius: 8px;
+  border: 1px solid #e4e7ed;
 }
 
-.chart-section {
-  margin-bottom: 32px;
+.chart-container {
+  min-height: 500px;
+  padding: 20px;
 }
 
-.chart-header {
+.empty-chart {
+  height: 500px;
   display: flex;
-  justify-content: space-between;
   align-items: center;
-  margin-bottom: 16px;
-}
-
-.chart-header h4 {
-  margin: 0;
-  font-size: 18px;
-  color: #303133;
+  justify-content: center;
 }
 
-.chart-content {
-  background: white;
-  border-radius: 8px;
-  padding: 20px;
-  border: 1px solid #ebeef5;
-}
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .data-display-edit {
+    padding: 12px;
+  }
 
-.chart {
-  width: 100%;
-  height: 400px;
-}
+  .filter-form {
+    padding: 16px;
+  }
 
-.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;
-    }
+  .filter-form .el-form {
+    flex-direction: column;
   }
-  
-  .table-content {
-    background: white;
-    border-radius: 8px;
-    
-    .table-pagination {
-      padding: 16px;
-      display: flex;
-      justify-content: center;
-    }
+
+  .filter-form .el-form-item {
+    width: 100%;
   }
-}
 
-.trend-positive {
-  color: #67c23a;
-  display: flex;
-  align-items: center;
-  gap: 4px;
-}
+  .chart-container {
+    padding: 16px;
+    min-height: 400px;
+  }
 
-.trend-negative {
-  color: #f56c6c;
-  display: flex;
-  align-items: center;
-  gap: 4px;
+  .empty-chart {
+    height: 400px;
+  }
 }
 </style>

+ 9 - 11
src/app/shop/admin/data/report/components/report-chart.vue

@@ -192,18 +192,10 @@ const chartOption = computed(() => {
   const baseOption = {
     color: props.colors,
     tooltip: {
-      trigger: currentType.value === 'pie' ? 'item' : 'axis',
-      axisPointer: {
-        type: currentType.value === 'line' ? 'cross' : 'shadow'
-      },
-      backgroundColor: 'rgba(50, 50, 50, 0.9)',
-      borderColor: '#333',
-      textStyle: {
-        color: '#fff'
-      }
+      show: false // 禁用默认tooltip
     },
     legend: {
-      show: true,
+      show: false,
       top: 'top',
       textStyle: {
         color: 'var(--sa-subtitle)'
@@ -492,7 +484,13 @@ const refreshChart = () => {
 }
 
 const handleChartClick = (params) => {
-  emit('chartClick', params)
+  // 发送点击事件,包含图表数据和点击位置信息
+  emit('chartClick', {
+    ...params,
+    chartData: props.data,
+    chartType: currentType.value,
+    chartTitle: props.title
+  })
 }
 
 const toggleFullscreen = () => {

File diff suppressed because it is too large
+ 236 - 242
src/app/shop/admin/data/report/detail.json


+ 370 - 0
src/app/shop/admin/data/report/dsDetail.json

@@ -0,0 +1,370 @@
+{
+  "code": "200",
+  "data": {
+    "createName": "admin",
+    "createTime": "2020-01-07 13:30:02",
+    "id": "6",
+    "memo": "用户总数",
+    "name": "用户总数",
+    "sections": [
+      {
+        "id": "229",
+        "memo": "用户总数",
+        "name": "悟空360-用户总数",
+        "statistics": [
+          {
+            "cutDay": "2025-08-29",
+            "id": 0,
+            "proportion": 0.0743,
+            "statisticsValue": 493700
+          },
+          {
+            "cutDay": "2025-08-30",
+            "id": 0,
+            "proportion": 0.0743,
+            "statisticsValue": 493897
+          },
+          {
+            "cutDay": "2025-08-31",
+            "id": 0,
+            "proportion": 0.0743,
+            "statisticsValue": 494105
+          },
+          {
+            "cutDay": "2025-09-01",
+            "id": 0,
+            "proportion": 0.0743,
+            "statisticsValue": 494202
+          },
+          {
+            "cutDay": "2025-09-02",
+            "id": 0,
+            "proportion": 0.0743,
+            "statisticsValue": 494307
+          },
+          {
+            "cutDay": "2025-09-03",
+            "id": 0,
+            "proportion": 0.0744,
+            "statisticsValue": 494412
+          },
+          {
+            "cutDay": "2025-09-04",
+            "id": 0,
+            "proportion": 0.0744,
+            "statisticsValue": 494514
+          }
+        ],
+        "total": 3459137
+      },
+      {
+        "id": "268",
+        "memo": "用户总数",
+        "name": "悟空三基同创-用户总数",
+        "statistics": [
+          {
+            "cutDay": "2025-08-29",
+            "id": 0,
+            "proportion": 0.0928,
+            "statisticsValue": 616596
+          },
+          {
+            "cutDay": "2025-08-30",
+            "id": 0,
+            "proportion": 0.0928,
+            "statisticsValue": 616801
+          },
+          {
+            "cutDay": "2025-08-31",
+            "id": 0,
+            "proportion": 0.0928,
+            "statisticsValue": 616975
+          },
+          {
+            "cutDay": "2025-09-01",
+            "id": 0,
+            "proportion": 0.0928,
+            "statisticsValue": 617073
+          },
+          {
+            "cutDay": "2025-09-02",
+            "id": 0,
+            "proportion": 0.0928,
+            "statisticsValue": 617163
+          },
+          {
+            "cutDay": "2025-09-03",
+            "id": 0,
+            "proportion": 0.0928,
+            "statisticsValue": 617264
+          },
+          {
+            "cutDay": "2025-09-04",
+            "id": 0,
+            "proportion": 0.0928,
+            "statisticsValue": 617387
+          }
+        ],
+        "total": 4319259
+      },
+      {
+        "id": "306",
+        "memo": "用户总数",
+        "name": "悟空华为-用户总数",
+        "statistics": [
+          {
+            "cutDay": "2025-08-29",
+            "id": 0,
+            "proportion": 0.0688,
+            "statisticsValue": 457479
+          },
+          {
+            "cutDay": "2025-08-30",
+            "id": 0,
+            "proportion": 0.0688,
+            "statisticsValue": 457492
+          },
+          {
+            "cutDay": "2025-08-31",
+            "id": 0,
+            "proportion": 0.0688,
+            "statisticsValue": 457497
+          },
+          {
+            "cutDay": "2025-09-01",
+            "id": 0,
+            "proportion": 0.0688,
+            "statisticsValue": 457502
+          },
+          {
+            "cutDay": "2025-09-02",
+            "id": 0,
+            "proportion": 0.0688,
+            "statisticsValue": 457503
+          },
+          {
+            "cutDay": "2025-09-03",
+            "id": 0,
+            "proportion": 0.0688,
+            "statisticsValue": 457508
+          },
+          {
+            "cutDay": "2025-09-04",
+            "id": 0,
+            "proportion": 0.0688,
+            "statisticsValue": 457511
+          }
+        ],
+        "total": 3202492
+      },
+      {
+        "id": "305",
+        "memo": "用户总数",
+        "name": "悟空小度-用户总数",
+        "statistics": [
+          {
+            "cutDay": "2025-08-29",
+            "id": 0,
+            "proportion": 0.2913,
+            "statisticsValue": 1935840
+          },
+          {
+            "cutDay": "2025-08-30",
+            "id": 0,
+            "proportion": 0.2912,
+            "statisticsValue": 1935840
+          },
+          {
+            "cutDay": "2025-08-31",
+            "id": 0,
+            "proportion": 0.2912,
+            "statisticsValue": 1935840
+          },
+          {
+            "cutDay": "2025-09-01",
+            "id": 0,
+            "proportion": 0.2912,
+            "statisticsValue": 1935840
+          },
+          {
+            "cutDay": "2025-09-02",
+            "id": 0,
+            "proportion": 0.2912,
+            "statisticsValue": 1935840
+          },
+          {
+            "cutDay": "2025-09-03",
+            "id": 0,
+            "proportion": 0.2911,
+            "statisticsValue": 1935840
+          },
+          {
+            "cutDay": "2025-09-04",
+            "id": 0,
+            "proportion": 0.2911,
+            "statisticsValue": 1935840
+          }
+        ],
+        "total": 13550880
+      },
+      {
+        "id": "267",
+        "memo": "用户总数",
+        "name": "悟空小米-用户总数",
+        "statistics": [
+          {
+            "cutDay": "2025-08-29",
+            "id": 0,
+            "proportion": 0.3732,
+            "statisticsValue": 2480218
+          },
+          {
+            "cutDay": "2025-08-30",
+            "id": 0,
+            "proportion": 0.3731,
+            "statisticsValue": 2480230
+          },
+          {
+            "cutDay": "2025-08-31",
+            "id": 0,
+            "proportion": 0.3731,
+            "statisticsValue": 2480236
+          },
+          {
+            "cutDay": "2025-09-01",
+            "id": 0,
+            "proportion": 0.3731,
+            "statisticsValue": 2480243
+          },
+          {
+            "cutDay": "2025-09-02",
+            "id": 0,
+            "proportion": 0.3731,
+            "statisticsValue": 2480245
+          },
+          {
+            "cutDay": "2025-09-03",
+            "id": 0,
+            "proportion": 0.373,
+            "statisticsValue": 2480250
+          },
+          {
+            "cutDay": "2025-09-04",
+            "id": 0,
+            "proportion": 0.373,
+            "statisticsValue": 2480254
+          }
+        ],
+        "total": 17361676
+      },
+      {
+        "id": "230",
+        "memo": "用户总数",
+        "name": "悟空掌育-用户总数",
+        "statistics": [
+          {
+            "cutDay": "2025-08-29",
+            "id": 0,
+            "proportion": 0.0997,
+            "statisticsValue": 662319
+          },
+          {
+            "cutDay": "2025-08-30",
+            "id": 0,
+            "proportion": 0.0997,
+            "statisticsValue": 662710
+          },
+          {
+            "cutDay": "2025-08-31",
+            "id": 0,
+            "proportion": 0.0997,
+            "statisticsValue": 663013
+          },
+          {
+            "cutDay": "2025-09-01",
+            "id": 0,
+            "proportion": 0.0998,
+            "statisticsValue": 663224
+          },
+          {
+            "cutDay": "2025-09-02",
+            "id": 0,
+            "proportion": 0.0998,
+            "statisticsValue": 663448
+          },
+          {
+            "cutDay": "2025-09-03",
+            "id": 0,
+            "proportion": 0.0998,
+            "statisticsValue": 663682
+          },
+          {
+            "cutDay": "2025-09-04",
+            "id": 0,
+            "proportion": 0.0998,
+            "statisticsValue": 663926
+          }
+        ],
+        "total": 4642322
+      },
+      {
+        "id": 0,
+        "memo": "",
+        "name": "用户总数汇总",
+        "statistics": [
+          {
+            "cutDay": "2025-08-29",
+            "id": 0,
+            "proportion": 1,
+            "statisticsValue": 6646152
+          },
+          {
+            "cutDay": "2025-08-30",
+            "id": 0,
+            "proportion": 1,
+            "statisticsValue": 6646970
+          },
+          {
+            "cutDay": "2025-08-31",
+            "id": 0,
+            "proportion": 1,
+            "statisticsValue": 6647666
+          },
+          {
+            "cutDay": "2025-09-01",
+            "id": 0,
+            "proportion": 1,
+            "statisticsValue": 6648084
+          },
+          {
+            "cutDay": "2025-09-02",
+            "id": 0,
+            "proportion": 1,
+            "statisticsValue": 6648506
+          },
+          {
+            "cutDay": "2025-09-03",
+            "id": 0,
+            "proportion": 1,
+            "statisticsValue": 6648956
+          },
+          {
+            "cutDay": "2025-09-04",
+            "id": 0,
+            "proportion": 1,
+            "statisticsValue": 6649432
+          }
+        ],
+        "total": 46535766
+      }
+    ],
+    "selected": false,
+    "spectacularsDimensionId": 122,
+    "unitType": 2,
+    "updateName": "",
+    "updateTime": "2022-08-09 09:16:37",
+    "viewType": "line"
+  },
+  "message": "SUCCESS",
+  "voice": ""
+}

+ 207 - 67
src/app/shop/admin/data/report/index.vue

@@ -102,35 +102,37 @@
                   </el-icon>
                   删除
                 </el-button>
-                <el-button text size="small" @click="downloadChart(chart)">
+                <el-button text size="small" @click="downloadChart(chart)" :loading="exportLoading"
+                  :disabled="exportLoading">
                   <el-icon>
                     <TrendCharts />
                   </el-icon>
-                  下载图表
+                  {{ exportLoading ? '导出中' : '导出图表' }}
                 </el-button>
               </div>
             </div>
             <div class="chart-content">
               <reportChart :title="chart.name" :data="transformChartData(chart)" :type="chart.viewType || 'line'"
-                :height="350" :show-date-picker="false" :x-axis-key="'date'" />
+                :height="350" :show-date-picker="false" :x-axis-key="'x'" :y-axis-key="'y'" :series-key="'series'"
+                :colors="legendColors" @chart-click="handleChartClick" />
             </div>
             <div class="chart-footer">
               <span class="update-time">更新时间:{{ chart.updateTime || '暂无' }}</span>
             </div>
           </div>
 
-          <!-- 空状态 -->
-          <div v-if="!currentCharts.length" class="empty-charts">
-            <div class="empty-icon">📊</div>
-            <div class="empty-text">{{ selectedDashboard ? '该看板暂无图表数据' : '请选择看板查看图表数据' }}</div>
-            <el-button v-if="selectedDashboard" type="primary" @click="addChart">
-              添加图表
-            </el-button>
-          </div>
         </div>
       </div>
     </div>
   </div>
+
+
+
+
+
+  <!-- 图表提示框组件 -->
+  <chartTipsBox :visible="tipsBoxVisible" :data="tipsBoxData" @close="closeTipsBox" />
+
 </template>
 <script setup>
 import { onMounted, reactive, ref, computed } from 'vue';
@@ -143,6 +145,8 @@ import dataDisplayEdit from './components/dataDisplayEdit.vue';
 import dashboardEdit from './components/dashboardEdit.vue';
 import chartEdit from './components/chartEdit.vue';
 import reportChart from './components/report-chart.vue';
+import chartTipsBox from './components/chart-tips-box.vue';
+import detailData from './detail.json';
 
 // 响应式数据
 const loading = ref(false);
@@ -150,6 +154,19 @@ const dashboardLoading = ref(false);
 const selectedDashboard = ref(null);
 const dashboardList = ref([]);
 const chartData = ref([]);
+// 导出loading状态
+const exportLoading = ref(false);
+// 悬浮弹窗相关代码已移除
+
+// 图例相关数据已移除
+const legendColors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
+
+// 提示框相关数据
+const tipsBoxVisible = ref(false);
+const tipsBoxData = ref({
+  title: '',
+  items: []
+});
 
 // 计算当前选中看板的图表数据
 const currentCharts = computed(() => {
@@ -196,17 +213,25 @@ async function getChartData(dashboardId) {
 
   loading.value = true;
   try {
-    const { code, data } = await api.report.spectaculars.detail({
-      id: dashboardId,
-      startTime: '', // 可以根据实际需要设置时间范围
-      endTime: '',
-      timeInterval: 7
-    });
+    const { code, data } = detailData
+    console.log('📊 图表数据:', {
+      dashboardId,
+      dimensionsCount: data?.dimensions?.length || 0
+    })
+
+    // const { code, data } = await api.report.spectaculars.detail({
+    //   id: dashboardId,
+    //   startTime: '', // 可以根据实际需要设置时间范围
+    //   endTime: '',
+    //   timeInterval: 7
+    // });
     if (code == 200) {
       // 更新当前看板的图表数据
       const newCharts = Array.isArray(data) ? data : [data];
+
       chartData.value = chartData.value.filter(chart => chart.dashboardId !== dashboardId);
       chartData.value.push(...newCharts.map(chart => ({ ...chart, dashboardId })));
+
       // 处理维度数据用于图表显示
       processChartData(data);
     } else {
@@ -237,12 +262,15 @@ async function selectDashboard(dashboard) {
 function processChartData(data) {
   // 处理从detail接口返回的数据,转换为图表可用格式
   if (data && data.dimensions) {
-    console.log('处理图表数据:', data.dimensions);
     // 将dimensions数据转换为图表数据
-    const newCharts = data.dimensions.map(dimension => ({
+    const newCharts = data.dimensions.map((dimension, index) => ({
       ...dimension,
+      id: dimension.id || `chart_${index}`, // 确保有id
+      name: dimension.name || `图表${index + 1}`, // 确保有name
+      viewType: dimension.viewType || 'line', // 确保有viewType,默认为折线图
       dashboardId: selectedDashboard.value?.id,
-      sections: dimension.sections || []
+      sections: dimension.sections || [],
+      updateTime: new Date().toLocaleString() // 添加更新时间
     }));
 
     // 更新图表数据
@@ -254,41 +282,99 @@ function processChartData(data) {
 // 转换图表数据格式
 function transformChartData(chart) {
   if (!chart.sections || chart.sections.length === 0) {
-    // 返回空数据结构,让图表组件显示"暂无数据"状态
     return [];
   }
 
   // 获取所有日期(从第一个section的statistics中提取)
   const firstSection = chart.sections.find(section => section.statistics && section.statistics.length > 0);
+
   if (!firstSection) {
-    // 如果没有有效的statistics数据,返回空数组让图表显示"暂无数据"
     return [];
   }
 
   const dates = firstSection.statistics.map(stat => stat.cutDay);
 
-  // 转换为图表需要的格式
-  const result = dates.map(date => {
-    const dataPoint = { date };
-
-    // 为每个section添加数据
-    chart.sections.forEach(section => {
-      if (section.statistics && section.name !== chart.name + '汇总') {
-        const stat = section.statistics.find(s => s.cutDay === date);
-        if (stat) {
-          dataPoint[section.name] = stat.statisticsValue;
-        }
-      }
-    });
-
-    return dataPoint;
+  // 转换为report-chart组件期望的格式: {x, y, series}
+  const result = [];
+
+  // 为每个section生成数据点
+  chart.sections.forEach(section => {
+    if (section.statistics && section.name !== chart.name + '汇总') {
+      section.statistics.forEach(stat => {
+        result.push({
+          x: stat.cutDay,
+          y: stat.statisticsValue,
+          series: section.name
+        });
+      });
+    }
   });
 
+  console.log('📈', chart.name, '数据点:', result.length);
   return result;
 }
 
 
 
+// 处理图表点击事件 - 显示固定提示框
+function handleChartClick(params) {
+  console.log('图表点击事件:', params);
+
+  // 构建提示框数据
+  const clickData = params.data || params;
+  const chartData = params.chartData || [];
+  const chartType = params.chartType || 'line';
+  const chartTitle = params.chartTitle || '图表';
+
+  // 根据图表类型和点击数据构建提示框内容
+  let title = '';
+  let items = [];
+
+  if (chartType === 'pie') {
+    // 饼图:显示点击的扇形数据
+    title = `${chartTitle}:${clickData.name || ''}`;
+    items = [{
+      name: clickData.name || '数据项',
+      value: clickData.value || 0,
+      percent: clickData.percent ? `${clickData.percent}%` : '0%',
+      color: params.color || legendColors[0]
+    }];
+  } else {
+    // 折线图/柱状图:显示该时间点的所有系列数据
+    const xValue = clickData.name || params.name || '';
+    title = `${chartTitle}:${xValue}`;
+
+    // 获取该时间点的所有系列数据
+    const seriesData = chartData.filter(item => item.x === xValue);
+    const totalValue = seriesData.reduce((sum, item) => sum + (item.y || 0), 0);
+
+    items = seriesData.map((item, index) => ({
+      name: item.series || '系列',
+      value: item.y || 0,
+      percent: totalValue > 0 ? `${((item.y || 0) / totalValue * 100).toFixed(2)}%` : '0%',
+      color: legendColors[index % legendColors.length]
+    }));
+
+    // 添加汇总行
+    if (items.length > 1) {
+      items.push({
+        name: `${chartTitle}汇总`,
+        value: totalValue,
+        percent: '100%',
+        color: '#999'
+      });
+    }
+  }
+
+  tipsBoxData.value = { title, items };
+  tipsBoxVisible.value = true;
+}
+
+// 关闭提示框
+function closeTipsBox() {
+  tipsBoxVisible.value = false;
+}
+
 // 删除看板
 function deleteDashboard(dashboard) {
   ElMessageBox.confirm(`确定要删除看板 "${dashboard.name}" 吗?`, '提示', {
@@ -314,10 +400,6 @@ function deleteDashboard(dashboard) {
   });
 }
 
-// 显示图表详情
-function showChartDetail(chart) {
-  showDataDisplay({ chart });
-}
 
 // 删除图表
 function deleteChart(chart) {
@@ -345,46 +427,35 @@ function deleteChart(chart) {
 }
 
 // 下载图表
-function downloadChart(chart) {
+async function downloadChart(chart) {
+  if (exportLoading.value) return; // 防止重复点击
+
+  exportLoading.value = true;
   try {
-    // 创建图表数据的JSON文件
-    const chartDataForDownload = {
-      name: chart.name,
-      type: chart.viewType || 'line',
-      data: transformChartData(chart),
-      updateTime: chart.updateTime,
-      exportTime: new Date().toLocaleString()
+    // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
+    const exportParams = {
+      id: chart.spectacularsDimensionId,
+      timeInterval: 7
     };
 
-    const dataStr = JSON.stringify(chartDataForDownload, null, 2);
-    const dataBlob = new Blob([dataStr], { type: 'application/json' });
-
-    const link = document.createElement('a');
-    link.href = URL.createObjectURL(dataBlob);
-    link.download = `${chart.name}_图表数据_${new Date().toISOString().slice(0, 10)}.json`;
-    document.body.appendChild(link);
-    link.click();
-    document.body.removeChild(link);
-
-    ElMessage.success('图表数据下载成功');
-  } catch (error) {
-    console.error('下载图表失败:', error);
-    ElMessage.error('下载图表失败,请重试');
+    // 调用导出API,所有下载逻辑都在REPORT函数中处理
+    await api.report.dimensions.report(exportParams, '数据列表');
+  } finally {
+    exportLoading.value = false;
   }
 }
 
 
 
 // 显示数据展示页面
-function showDataDisplay(options) {
+function showChartDetail(options) {
   useModal(
     dataDisplayEdit,
     {
       title: `${options.chart?.name || options.dataType || '数据'} - 数据详情`,
       width: '90%',
       height: '80vh',
-      dataType: options.dataType,
-      chart: options.chart
+      id: options.spectacularsDimensionId,
     },
     {
       confirm: () => {
@@ -702,7 +773,7 @@ onMounted(() => {
   padding: 16px;
   border: 1px solid #e4e7ed;
   transition: all 0.3s;
-  width: 380px;
+  width: 880px;
   height: 430px;
   margin: 10px;
   display: flex;
@@ -858,4 +929,73 @@ onMounted(() => {
     justify-content: flex-end;
   }
 }
+
+/* 图表点击悬浮弹窗样式 */
+.chart-tooltip-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.chart-tooltip {
+  position: absolute;
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  min-width: 280px;
+  max-width: 400px;
+  z-index: 10000;
+  transform: translate(-50%, -100%);
+  margin-top: -10px;
+}
+
+.tooltip-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 16px;
+  border-bottom: 1px solid #ebeef5;
+  background: #f8f9fa;
+  border-radius: 8px 8px 0 0;
+}
+
+.tooltip-title {
+  font-weight: 600;
+  color: #303133;
+  font-size: 14px;
+}
+
+.tooltip-content {
+  padding: 16px;
+}
+
+.tooltip-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.tooltip-item .label {
+  color: #909399;
+  font-size: 13px;
+  margin-right: 12px;
+}
+
+.tooltip-item .value {
+  color: #303133;
+  font-weight: 500;
+  font-size: 13px;
+}
 </style>

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

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

+ 1 - 1
src/app/shop/admin/finance/commission/index.vue

@@ -296,7 +296,7 @@ function viewOrderDetail(row) {
     {
       title: t('modules.commission.orderDetail'),
       type: 'view',
-      id: row.orderNo,
+      orderNo: row.orderNo,
     },
     {
       confirm: () => {

+ 10 - 6
src/app/shop/admin/order/order/detail.vue

@@ -167,7 +167,7 @@
       </div>
 
       <!-- 收货信息 - 在待收货和订单完成状态显示 -->
-      <div class="address-content sa-m-b-16" v-if="[7, 8].includes(state.orderDetail.status)">
+      <div class="address-content sa-m-b-16" v-if="state.orderDetail.realName">
         <div class="sa-title sa-m-b-10">{{ t('modules.order.deliveryInfo') }}</div>
         <el-table class="sa-table" :data="[state.orderDetail.orderAddressVO]" stripe border>
           <el-table-column :label="t('modules.order.recipient')" prop="realName" align="center" />
@@ -211,7 +211,7 @@
           </el-table-column>
           <el-table-column :label="t('modules.order.groupOrderNumber')" min-width="180" align="center">
             <template #default="{ row }">
-              <el-link type="primary" @click="openOrderDetail(row.orderIdKey)">
+              <el-link type="primary" @click="openOrderDetail(row.orderId)">
                 {{ row.orderId || '--' }}
               </el-link>
             </template>
@@ -310,13 +310,17 @@ const memoForm = reactive({
 // 获取订单详情
 async function getOrderDetail() {
   try {
-    loading.value = true;
-    const { code, data } = await api.order.detail(props.modal.params.id);
+    // 支持通过id或orderNo查询订单
+    const params = props.modal.params.orderNo
+      ? { orderNo: props.modal.params.orderNo }
+      : props.modal.params.id;
+
+    const { code, data } = await api.order.detail(params);
     if (code == 200) {
       state.orderDetail = data;
       // 获取拼团信息
 
-      if (data.storePink.id) await getPinkList(data.storePink.id);
+      if (data.storePink?.id) await getPinkList(data.storePink.id);
     }
   } catch (error) {
     console.error('获取订单详情失败:', error);
@@ -489,7 +493,7 @@ async function openOrderDetailByOrderId(orderId) {
       {
         title: t('modules.order.orderDetail'),
         type: 'detail',
-        id: orderId, // 这里可能需要转换为数据库ID
+        orderNo: orderId, // 这里可能需要转换为数据库ID
       },
       {
         success: () => {

+ 0 - 132
src/composables/useModal.js

@@ -1,132 +0,0 @@
-import { ref, reactive } from 'vue'
-import { ElDialog } from 'element-plus'
-import { createVNode, render } from 'vue'
-
-/**
- * useModal - 模态框管理 Hook
- * 用于替代 el-dialog,提供更灵活的模态框管理
- */
-export function useModal() {
-  const visible = ref(false)
-  const loading = ref(false)
-  
-  const open = () => {
-    visible.value = true
-  }
-  
-  const close = () => {
-    visible.value = false
-  }
-  
-  const toggle = () => {
-    visible.value = !visible.value
-  }
-  
-  const setLoading = (state) => {
-    loading.value = state
-  }
-  
-  return {
-    visible,
-    loading,
-    open,
-    close,
-    toggle,
-    setLoading
-  }
-}
-
-/**
- * 创建模态框实例
- * @param {Object} component - Vue 组件
- * @param {Object} props - 组件属性
- * @param {Object} options - 模态框选项
- */
-export function createModal(component, props = {}, options = {}) {
-  const {
-    title = '',
-    width = '50%',
-    destroyOnClose = true,
-    closeOnClickModal = true,
-    closeOnPressEscape = true,
-    showClose = true,
-    center = false,
-    modal = true,
-    lockScroll = true,
-    customClass = '',
-    zIndex = 2000,
-    appendToBody = true
-  } = options
-  
-  const modalState = reactive({
-    visible: false,
-    loading: false
-  })
-  
-  const modalProps = {
-    modelValue: modalState.visible,
-    title,
-    width,
-    destroyOnClose,
-    closeOnClickModal,
-    closeOnPressEscape,
-    showClose,
-    center,
-    modal,
-    lockScroll,
-    customClass,
-    zIndex,
-    appendToBody,
-    'onUpdate:modelValue': (value) => {
-      modalState.visible = value
-    },
-    onClose: () => {
-      modalState.visible = false
-      if (options.onClose) {
-        options.onClose()
-      }
-    }
-  }
-  
-  const componentProps = {
-    ...props,
-    onSave: (data) => {
-      if (options.onSave) {
-        options.onSave(data)
-      }
-      modalState.visible = false
-    },
-    onCancel: () => {
-      if (options.onCancel) {
-        options.onCancel()
-      }
-      modalState.visible = false
-    },
-    onClose: () => {
-      modalState.visible = false
-    }
-  }
-  
-  const open = () => {
-    modalState.visible = true
-  }
-  
-  const close = () => {
-    modalState.visible = false
-  }
-  
-  const setLoading = (state) => {
-    modalState.loading = state
-  }
-  
-  return {
-    modalState,
-    modalProps,
-    componentProps,
-    open,
-    close,
-    setLoading
-  }
-}
-
-export default useModal

+ 197 - 180
src/sheep/layouts/index.vue

@@ -23,7 +23,7 @@
         </el-main>
       </el-container>
     </el-main>
-    <el-footer class="app-layout__footer">
+    <!-- <el-footer class="app-layout__footer">
       <div class="sa-flex sa-flex-1">
         <div
           class="oper-icon sa-m-r-12"
@@ -47,7 +47,7 @@
           size="16"
         ></sa-svg>
       </div>
-    </el-footer>
+    </el-footer> -->
     <sa-file-modal />
     <sa-modal />
     <sa-drawer />
@@ -58,224 +58,241 @@
 </template>
 
 <script setup>
-  import { computed } from 'vue';
-  import { useApp } from '@/sheep/hooks';
-  import AppMenu from './menu/index.vue';
-  import Taskbar from './taskbar/index.vue';
-  import SaModal from '@/sheep/components/sa-modal/sa-modal.vue';
-  import SaFileModal from '@/sheep/components/sa-file/sa-file-modal.vue';
-  import SaDrawer from '@/sheep/components/sa-drawer/sa-drawer.vue';
-  import SaNotice from '@/app/notice/index.vue';
-  import sheep from '@/sheep';
-
-  const appStore = sheep.$store('app');
-  const { appInited, appInfo, appLayout, requestCounter } = useApp();
-  const taskbarHistory = computed(() => appStore.taskbar.history);
-  const rotateSpeed = computed(() => {
-    let speed = 0;
-    if (requestCounter.value == 0) return speed;
-    if (3 - requestCounter.value * 0.6 <= 0) return 0.5;
-    return (3 - requestCounter.value * 0.6).toFixed(2);
-  });
-
-  function onChangeCollapse() {
-    appStore.menuCollapse(!appLayout.value.collapse);
-  }
+import { computed } from 'vue';
+import { useApp } from '@/sheep/hooks';
+import AppMenu from './menu/index.vue';
+import Taskbar from './taskbar/index.vue';
+import SaModal from '@/sheep/components/sa-modal/sa-modal.vue';
+import SaFileModal from '@/sheep/components/sa-file/sa-file-modal.vue';
+import SaDrawer from '@/sheep/components/sa-drawer/sa-drawer.vue';
+import SaNotice from '@/app/notice/index.vue';
+import sheep from '@/sheep';
+
+const appStore = sheep.$store('app');
+const { appInited, appInfo, appLayout, requestCounter } = useApp();
+const taskbarHistory = computed(() => appStore.taskbar.history);
+const rotateSpeed = computed(() => {
+  let speed = 0;
+  if (requestCounter.value == 0) return speed;
+  if (3 - requestCounter.value * 0.6 <= 0) return 0.5;
+  return (3 - requestCounter.value * 0.6).toFixed(2);
+});
+
+function onChangeCollapse() {
+  appStore.menuCollapse(!appLayout.value.collapse);
+}
 </script>
 
 <style lang="scss" scoped>
-  .el-container {
-    height: 100%;
-    position: relative;
-    .sa-float {
-      position: absolute;
-      right: 24px;
-      bottom: 204px;
-      z-index: 100;
-      & > div {
-        margin-bottom: 12px;
-      }
-      & > div:last-of-type {
-        margin-bottom: 0;
-      }
-      :deep() {
-        .sa-float-icon-wrap {
-          width: 40px;
-          height: 40px;
-          background: var(--sa-background-assist);
-          box-shadow: 0px 1px 16px rgba(0, 0, 0, 0.24);
-          border-radius: 50%;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          cursor: pointer;
-          .sa-float-icon {
-            color: var(--el-color-primary);
-          }
-          &:hover {
-            background: var(--sa-table-header-bg);
-          }
+.el-container {
+  height: 100%;
+  position: relative;
+
+  .sa-float {
+    position: absolute;
+    right: 24px;
+    bottom: 204px;
+    z-index: 100;
+
+    &>div {
+      margin-bottom: 12px;
+    }
+
+    &>div:last-of-type {
+      margin-bottom: 0;
+    }
+
+    :deep() {
+      .sa-float-icon-wrap {
+        width: 40px;
+        height: 40px;
+        background: var(--sa-background-assist);
+        box-shadow: 0px 1px 16px rgba(0, 0, 0, 0.24);
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+
+        .sa-float-icon {
+          color: var(--el-color-primary);
+        }
+
+        &:hover {
+          background: var(--sa-table-header-bg);
         }
       }
     }
   }
-  .app-layout {
+}
+
+.app-layout {
+  height: 100%;
+  background: var(--sa-background-assist);
+
+  &__mask {
+    width: 100%;
     height: 100%;
-    background: var(--sa-background-assist);
-
-    &__mask {
-      width: 100%;
-      height: 100%;
-      background: rgba(100, 100, 100, 0.7);
-      backdrop-filter: blur(2px);
-      position: fixed;
-      left: 0;
-      top: 0;
-      z-index: 10000;
-    }
+    background: rgba(100, 100, 100, 0.7);
+    backdrop-filter: blur(2px);
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 10000;
+  }
 
-    &__left {
-      width: unset;
-      max-width: 300px;
-      height: 100%;
-      display: flex;
-      overflow: hidden;
-      z-index: 100;
-      transition: all 0.2s ease-in-out;
-
-      * {
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        -ms-user-select: none;
-        user-select: none;
-      }
-    }
+  &__left {
+    width: unset;
+    max-width: 300px;
+    height: 100%;
+    display: flex;
+    overflow: hidden;
+    z-index: 100;
+    transition: all 0.2s ease-in-out;
 
-    &__right {
-      height: 100%;
-      display: flex;
-      flex-direction: column;
-      position: relative;
+    * {
+      -webkit-user-select: none;
+      -moz-user-select: none;
+      -ms-user-select: none;
+      user-select: none;
     }
+  }
 
-    &__taskbar {
-      // border-bottom: 1px solid var(--sa-border);
-    }
+  &__right {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    position: relative;
+  }
 
-    &__container {
-      width: 100%;
-      box-sizing: border-box;
-      flex: 1;
-      overflow: hidden;
-    }
+  &__taskbar {
+    // border-bottom: 1px solid var(--sa-border);
+  }
+
+  &__container {
+    width: 100%;
+    box-sizing: border-box;
+    flex: 1;
+    overflow: hidden;
+  }
+
+  &__view {
+    height: 100%;
+    width: 100%;
+    padding: 0 16px;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden auto;
+  }
+
+  &__modal {
+    // height: 36px;
+    // border-top: 1px solid var(--sa-space);
+  }
 
-    &__view {
-      height: 100%;
-      width: 100%;
-      padding: 0 16px;
-      display: flex;
-      flex-direction: column;
-      overflow: hidden auto;
+  &__footer {
+    --el-footer-padding: 0 16px;
+    --el-footer-height: 30px;
+    color: var(--sa-footer-color);
+    background: var(--sa-footer-bg);
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 12px;
+    overflow: hidden;
+
+    .app-name {
+      font-weight: 400;
     }
 
-    &__modal {
-      // height: 36px;
-      // border-top: 1px solid var(--sa-space);
+    .el-divider--vertical {
+      --el-border-color: var(--sa-footer-color-1);
+      margin: 0 16px;
     }
 
-    &__footer {
-      --el-footer-padding: 0 16px;
-      --el-footer-height: 30px;
-      color: var(--sa-footer-color);
-      background: var(--sa-footer-bg);
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      font-size: 12px;
-      overflow: hidden;
-      .app-name {
-        font-weight: 400;
+    .oper-icon {
+      cursor: pointer;
+
+      &.fold {
+        transform: rotateZ(180deg);
+        transition: transform 0.25s linear;
       }
-      .el-divider--vertical {
-        --el-border-color: var(--sa-footer-color-1);
-        margin: 0 16px;
+
+      &.open {
+        transform: rotateZ(0deg);
+        transition: transform 0.25s linear;
       }
-      .oper-icon {
-        cursor: pointer;
-        &.fold {
-          transform: rotateZ(180deg);
-          transition: transform 0.25s linear;
-        }
-        &.open {
-          transform: rotateZ(0deg);
-          transition: transform 0.25s linear;
-        }
 
-        &.refresh {
-          -webkit-animation: rotating 3s linear infinite;
-          animation: rotating 3s linear infinite;
-        }
+      &.refresh {
+        -webkit-animation: rotating 3s linear infinite;
+        animation: rotating 3s linear infinite;
       }
+    }
+
+    .modal-oper {
+      position: relative;
+      flex: 1;
+      height: 32px;
+    }
+
+    @media only screen and (max-width: 768px) {
+      --el-footer-height: 36px;
+
       .modal-oper {
-        position: relative;
-        flex: 1;
-        height: 32px;
+        display: none;
       }
+    }
+  }
 
-      @media only screen and (max-width: 768px) {
-        --el-footer-height: 36px;
-        .modal-oper {
-          display: none;
-        }
-      }
+  @media only screen and (max-width: 768px) {
+    .app-layout__left {
+      position: absolute;
+      left: 0;
+      z-index: 10000;
+      transition: transform 0.3s ease-in-out;
     }
 
-    @media only screen and (max-width: 768px) {
+    &.collapse {
       .app-layout__left {
-        position: absolute;
-        left: 0;
-        z-index: 10000;
-        transition: transform 0.3s ease-in-out;
+        transform: translateX(-100%);
       }
-      &.collapse {
-        .app-layout__left {
-          transform: translateX(-100%);
-        }
 
-        .app-layout__mask {
-          display: none;
-        }
-      }
-    }
-    @media only screen and (min-width: 768px) {
       .app-layout__mask {
         display: none;
       }
-      &.collapse {
-        .app-layout__left {
-          max-width: 96px;
-        }
-      }
     }
+  }
 
-    @-webkit-keyframes rotating {
-      0% {
-        transform: rotateZ(0);
-      }
+  @media only screen and (min-width: 768px) {
+    .app-layout__mask {
+      display: none;
+    }
 
-      100% {
-        transform: rotateZ(360deg);
+    &.collapse {
+      .app-layout__left {
+        max-width: 96px;
       }
     }
+  }
 
-    @keyframes rotating {
-      0% {
-        transform: rotateZ(0);
-      }
+  @-webkit-keyframes rotating {
+    0% {
+      transform: rotateZ(0);
+    }
 
-      100% {
-        transform: rotateZ(360deg);
-      }
+    100% {
+      transform: rotateZ(360deg);
+    }
+  }
+
+  @keyframes rotating {
+    0% {
+      transform: rotateZ(0);
+    }
+
+    100% {
+      transform: rotateZ(360deg);
     }
   }
+}
 </style>

+ 31 - 11
src/sheep/request/crud.js

@@ -75,24 +75,44 @@ export const LIST = (url, data, options = {}) => {
 };
 
 // 查看详情
-export const DETAIL = (url, id, options = {}) => {
+export const DETAIL = (url, idOrParams, options = {}) => {
   const config = getCrudConfig(options);
   const method = config.methods.detail;
   const isMall = isMallAPI(url);
 
   if (config.version === CRUD_VERSIONS.V2) {
     // 新版本:使用 RESTful 风格的 URL
-    return request({
-      url: url + config.endpoints.detail + `/${id}`,
-      method,
-    });
+    if (typeof idOrParams === 'object') {
+      // 如果是对象参数(如 {orderNo: 'xxx'}),使用查询参数
+      return request({
+        url: url + config.endpoints.detail,
+        method,
+        params: idOrParams,
+      });
+    } else {
+      // 如果是ID,使用RESTful风格URL
+      return request({
+        url: url + config.endpoints.detail + `/${idOrParams}`,
+        method,
+      });
+    }
   } else {
-    // 旧版本:使用参数传递 ID
-    return request({
-      url: url + config.endpoints.detail,
-      method: isMall ? 'GET' : 'POST', // mall用GET,其他用POST
-      params: { id },
-    });
+    // 旧版本:使用参数传递
+    if (typeof idOrParams === 'object') {
+      // 如果是对象参数(如 {orderNo: 'xxx'}),直接传递
+      return request({
+        url: url + config.endpoints.detail,
+        method: isMall ? 'GET' : 'POST', // mall用GET,其他用POST
+        params: idOrParams,
+      });
+    } else {
+      // 如果是ID,使用id参数
+      return request({
+        url: url + config.endpoints.detail,
+        method: isMall ? 'GET' : 'POST', // mall用GET,其他用POST
+        params: { id: idOrParams },
+      });
+    }
   }
 };
 

File diff suppressed because it is too large
+ 66 - 0
vite.config.js.timestamp-1757043256142-9b52aea5938a2.mjs


Some files were not shown because too many files changed in this diff