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