|
|
@@ -1,10 +1,30 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { markRaw, nextTick, onActivated, onMounted, reactive, ref, watch } from "vue";
|
|
|
-import * as echarts from "echarts";
|
|
|
+import { markRaw, nextTick, onActivated, onMounted, reactive, ref, watch, computed } from "vue";
|
|
|
+import * as echarts from "echarts/core";
|
|
|
+import { PieChart, BarChart, LineChart } from "echarts/charts";
|
|
|
+import {
|
|
|
+ GridComponent,
|
|
|
+ TitleComponent,
|
|
|
+ LegendComponent,
|
|
|
+ TooltipComponent,
|
|
|
+ DataZoomComponent
|
|
|
+} from "echarts/components";
|
|
|
+import { CanvasRenderer } from "echarts/renderers";
|
|
|
+
|
|
|
+echarts.use([
|
|
|
+ PieChart,
|
|
|
+ BarChart,
|
|
|
+ LineChart,
|
|
|
+ GridComponent,
|
|
|
+ TitleComponent,
|
|
|
+ LegendComponent,
|
|
|
+ TooltipComponent,
|
|
|
+ DataZoomComponent,
|
|
|
+ CanvasRenderer
|
|
|
+]);
|
|
|
import { getDashboard, getTrend, getWashDeviceStatus } from "@/api/stat";
|
|
|
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
|
|
-import { storageLocal } from "@pureadmin/utils";
|
|
|
-import { userKey, type DataInfo } from "@/utils/auth";
|
|
|
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
|
|
|
|
|
defineOptions({
|
|
|
name: "Dashboard"
|
|
|
@@ -18,7 +38,6 @@ const end = new Date();
|
|
|
const start = new Date();
|
|
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
|
|
|
|
|
-// 简单的格式化工具
|
|
|
const fmtMoney = (value: number) => {
|
|
|
if (!value) return "0";
|
|
|
return (value / 100).toFixed(2);
|
|
|
@@ -36,7 +55,6 @@ const dateDiff = (start: Date, end: Date) => {
|
|
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
};
|
|
|
|
|
|
-// Session 工具
|
|
|
const Session = {
|
|
|
get: (key: string) => {
|
|
|
const value = sessionStorage.getItem(key);
|
|
|
@@ -47,6 +65,20 @@ const Session = {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 品牌衍生图表色板
|
|
|
+const CHART_BAR = "#4DA89D";
|
|
|
+const CHART_LINE = "#C83A35";
|
|
|
+const CHART_GRID = "#EBEBEB";
|
|
|
+
|
|
|
+const PIE_COLORS = [
|
|
|
+ "#C83A35",
|
|
|
+ "#4DA89D",
|
|
|
+ "#E5A350",
|
|
|
+ "#8B7EC8",
|
|
|
+ "#5BA0D9",
|
|
|
+ "#8E8E8E"
|
|
|
+];
|
|
|
+
|
|
|
const state = reactive({
|
|
|
currentStationId: null as string | null,
|
|
|
dateRange: [formatDate(start), formatDate(end)] as [string, string],
|
|
|
@@ -55,14 +87,14 @@ const state = reactive({
|
|
|
homeChartTwo: null as any,
|
|
|
dispose: [null, "", undefined]
|
|
|
},
|
|
|
- homeOne: [
|
|
|
- { num1: "0", num3: "今日注册会员数(人)" },
|
|
|
- { num1: "0", num3: "今日收益金额(元)" },
|
|
|
- { num1: "0", num3: "今日洗车消费总金额(元)" },
|
|
|
- { num1: "0", num3: "订单平均消费金额(元)" },
|
|
|
- { num1: "0", num3: "今日订单数量(笔)" },
|
|
|
- { num1: "0", num3: "洗车平均时长(分钟)" }
|
|
|
- ],
|
|
|
+ metrics: {
|
|
|
+ registeredMembers: { value: "0", label: "今日注册会员", icon: "ri/user-add-line" },
|
|
|
+ todayIncome: { value: "0", label: "今日收益金额", icon: "ri/money-dollar-circle-line" },
|
|
|
+ consumptionAmount: { value: "0", label: "今日消费总额", icon: "ri/shopping-cart-2-line" },
|
|
|
+ avgOrderPrice: { value: "0", label: "订单均价", icon: "ri/calculator-line" },
|
|
|
+ todayOrders: { value: "0", label: "今日订单数量", icon: "ri/file-list-3-line" },
|
|
|
+ avgDuration: { value: "0", label: "洗车平均时长", icon: "ri/timer-line" }
|
|
|
+ },
|
|
|
myCharts: [] as any[],
|
|
|
charts: {
|
|
|
theme: "",
|
|
|
@@ -105,13 +137,12 @@ const shortcuts = [
|
|
|
}
|
|
|
];
|
|
|
|
|
|
-// 折线图
|
|
|
const initLineChart = (dataList: Array<any>) => {
|
|
|
if (!state.global.dispose.some((b: any) => b === state.global.homeChartOne)) {
|
|
|
state.global.homeChartOne?.dispose();
|
|
|
}
|
|
|
state.global.homeChartOne = markRaw(echarts.init(homeLineRef.value, state.charts.theme));
|
|
|
-
|
|
|
+
|
|
|
dataList.forEach(item => {
|
|
|
item.startTime = item.statTime.slice(0, 3).join("-");
|
|
|
item.seq = Number(item.statTime.join(""));
|
|
|
@@ -119,8 +150,7 @@ const initLineChart = (dataList: Array<any>) => {
|
|
|
|
|
|
state.homeOneExtra.totalIncome = dataList.reduce((k, v) => k + v.totalAmount, 0);
|
|
|
state.homeOneExtra.totalWashOrders = dataList.reduce((k, v) => k + v.totalOrders, 0);
|
|
|
-
|
|
|
- // 排序
|
|
|
+
|
|
|
dataList.sort((a, b) => a.seq - b.seq);
|
|
|
|
|
|
const xAxis = dataList.map(k => k.startTime);
|
|
|
@@ -142,7 +172,7 @@ const initLineChart = (dataList: Array<any>) => {
|
|
|
type: "value",
|
|
|
name: "费用/元 洗车量/次",
|
|
|
position: "left",
|
|
|
- splitLine: { show: true, lineStyle: { type: "dashed", color: "#f5f5f5" } }
|
|
|
+ splitLine: { show: true, lineStyle: { type: "dashed", color: CHART_GRID } }
|
|
|
}
|
|
|
],
|
|
|
series: [
|
|
|
@@ -154,8 +184,8 @@ const initLineChart = (dataList: Array<any>) => {
|
|
|
symbol: "circle",
|
|
|
smooth: true,
|
|
|
data: dataList.map(k => k.totalOrders),
|
|
|
- lineStyle: { color: "#68a7a0" },
|
|
|
- itemStyle: { color: "#68a7a0", borderColor: "#68a7a0", barBorderRadius: 5 }
|
|
|
+ lineStyle: { color: CHART_BAR },
|
|
|
+ itemStyle: { color: CHART_BAR, borderColor: CHART_BAR, barBorderRadius: 5 }
|
|
|
},
|
|
|
{
|
|
|
name: "总金额",
|
|
|
@@ -164,8 +194,8 @@ const initLineChart = (dataList: Array<any>) => {
|
|
|
symbol: "circle",
|
|
|
smooth: true,
|
|
|
data: dataList.map(k => fmtMoney(k.totalAmount)),
|
|
|
- lineStyle: { color: "#3770ff" },
|
|
|
- itemStyle: { color: "#3770ff", borderColor: "#3770ff" }
|
|
|
+ lineStyle: { color: CHART_LINE },
|
|
|
+ itemStyle: { color: CHART_LINE, borderColor: CHART_LINE }
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
@@ -173,13 +203,12 @@ const initLineChart = (dataList: Array<any>) => {
|
|
|
state.myCharts.push(state.global.homeChartOne);
|
|
|
};
|
|
|
|
|
|
-// 饼图
|
|
|
const initPieChart = (dataMap: any) => {
|
|
|
if (!state.global.dispose.some((b: any) => b === state.global.homeChartTwo)) {
|
|
|
state.global.homeChartTwo?.dispose();
|
|
|
}
|
|
|
state.global.homeChartTwo = markRaw(echarts.init(homePieRef.value, state.charts.theme));
|
|
|
-
|
|
|
+
|
|
|
const sessionDicts = Session.get("dicts");
|
|
|
let dicts: any[] = [];
|
|
|
if (sessionDicts) {
|
|
|
@@ -188,9 +217,8 @@ const initPieChart = (dataMap: any) => {
|
|
|
if (!dicts.length) return;
|
|
|
|
|
|
const getname = dicts.map(k => k.name);
|
|
|
- const colorList = ["#6B6F75FF", "#36C78B", "#e9ee8e", "#ffa496", "#E790E8", "#363638FF"];
|
|
|
const data: any[] = [];
|
|
|
-
|
|
|
+
|
|
|
for (let i = 0; i < getname.length; i++) {
|
|
|
const dict = dicts.find(k => k.name === getname[i]);
|
|
|
data.push({ name: getname[i], value: dataMap[`${dict?.value}`] || 0 });
|
|
|
@@ -221,7 +249,7 @@ const initPieChart = (dataMap: any) => {
|
|
|
radius: ["82", dataTheme.value ? "50" : "102"],
|
|
|
center: ["32%", "50%"],
|
|
|
itemStyle: {
|
|
|
- color: (params: any) => colorList[params.dataIndex]
|
|
|
+ color: (params: any) => PIE_COLORS[params.dataIndex % PIE_COLORS.length]
|
|
|
},
|
|
|
label: { show: false },
|
|
|
labelLine: { show: false },
|
|
|
@@ -233,7 +261,6 @@ const initPieChart = (dataMap: any) => {
|
|
|
state.myCharts.push(state.global.homeChartTwo);
|
|
|
};
|
|
|
|
|
|
-// 批量设置 echarts resize
|
|
|
const initEchartsResizeFun = () => {
|
|
|
nextTick(() => {
|
|
|
for (let i = 0; i < state.myCharts.length; i++) {
|
|
|
@@ -258,7 +285,7 @@ const loadStationStat = () => {
|
|
|
const start = state.dateRange[0];
|
|
|
const end = state.dateRange[1];
|
|
|
const size = dateDiff(new Date(start), new Date(end)) + 1;
|
|
|
-
|
|
|
+
|
|
|
getTrend({
|
|
|
startTime: start,
|
|
|
endTime: end,
|
|
|
@@ -274,94 +301,119 @@ const loadStationStatToday = () => {
|
|
|
getDashboard(state.currentStationId || undefined).then((res: any) => {
|
|
|
const data = res?.data || res;
|
|
|
if (data) {
|
|
|
- const {
|
|
|
- todayIncome,
|
|
|
- todayConsumptionAmount,
|
|
|
- todayRegisteredMembers,
|
|
|
- todayWashOrders,
|
|
|
- avgOrderPrice,
|
|
|
- avgOrderDuration
|
|
|
- } = data;
|
|
|
- state.homeOne[0].num1 = String(todayRegisteredMembers || 0);
|
|
|
- state.homeOne[1].num1 = fmtMoney(todayIncome || 0);
|
|
|
- state.homeOne[2].num1 = fmtMoney(todayConsumptionAmount || 0);
|
|
|
- state.homeOne[3].num1 = fmtMoney(avgOrderPrice || 0);
|
|
|
- state.homeOne[4].num1 = String(todayWashOrders || 0);
|
|
|
- state.homeOne[5].num1 = String(avgOrderDuration || 0);
|
|
|
+ state.metrics.registeredMembers.value = String(data.todayRegisteredMembers || 0);
|
|
|
+ state.metrics.todayIncome.value = fmtMoney(data.todayIncome || 0);
|
|
|
+ state.metrics.consumptionAmount.value = fmtMoney(data.todayConsumptionAmount || 0);
|
|
|
+ state.metrics.avgOrderPrice.value = fmtMoney(data.avgOrderPrice || 0);
|
|
|
+ state.metrics.todayOrders.value = String(data.todayWashOrders || 0);
|
|
|
+ state.metrics.avgDuration.value = String(data.avgOrderDuration || 0);
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-// 页面加载时
|
|
|
onMounted(() => {
|
|
|
const currentStationId = Session.get("currentStationId");
|
|
|
if (currentStationId) {
|
|
|
state.currentStationId = currentStationId;
|
|
|
- // 有站点 ID 时才加载数据
|
|
|
initEchartsResize();
|
|
|
loadStationStat();
|
|
|
loadStationStatToday();
|
|
|
loadCurrentEquipmentStatus();
|
|
|
} else {
|
|
|
- // 提示用户选择站点
|
|
|
- console.warn('请先选择站点');
|
|
|
- // 仍然初始化图表和基础数据,只是不加载接口数据
|
|
|
initEchartsResize();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
-// 由于页面缓存原因,keep-alive
|
|
|
onActivated(() => {
|
|
|
initEchartsResizeFun();
|
|
|
});
|
|
|
|
|
|
-// 监听深色主题变化
|
|
|
watch(
|
|
|
() => dataTheme.value,
|
|
|
(isDark) => {
|
|
|
nextTick(() => {
|
|
|
state.charts.theme = isDark ? "dark" : "";
|
|
|
state.charts.bgColor = isDark ? "transparent" : "";
|
|
|
- state.charts.color = isDark ? "#dadada" : "#303133";
|
|
|
+ state.charts.color = isDark ? "#C8C8C8" : "#303133";
|
|
|
setTimeout(() => loadStationStat(), 500);
|
|
|
setTimeout(() => loadCurrentEquipmentStatus(), 700);
|
|
|
});
|
|
|
}
|
|
|
);
|
|
|
+
|
|
|
+const featuredMetrics = computed(() => [
|
|
|
+ state.metrics.todayIncome,
|
|
|
+ state.metrics.todayOrders
|
|
|
+]);
|
|
|
+
|
|
|
+const secondaryMetrics = computed(() => [
|
|
|
+ state.metrics.consumptionAmount,
|
|
|
+ state.metrics.avgOrderPrice,
|
|
|
+ state.metrics.registeredMembers,
|
|
|
+ state.metrics.avgDuration
|
|
|
+]);
|
|
|
+
|
|
|
+const isMoneyMetric = (label: string) => {
|
|
|
+ return label.includes("金额") || label.includes("收益") || label.includes("均价") || label.includes("消费");
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<div class="dashboard-container">
|
|
|
- <!-- 未选择站点提示 -->
|
|
|
+ <!-- 未选择站点提醒 -->
|
|
|
<el-alert
|
|
|
v-if="!state.currentStationId"
|
|
|
- title="请先选择站点"
|
|
|
+ title="未选择站点"
|
|
|
type="warning"
|
|
|
- description="请在右上角选择要查看的站点,选择后将自动加载统计数据"
|
|
|
+ description="请通过右上角站点选择器切换到要查看的门店,选择后将自动加载统计数据"
|
|
|
show-icon
|
|
|
closable
|
|
|
class="mb-4"
|
|
|
/>
|
|
|
-
|
|
|
- <!-- 统计卡片 -->
|
|
|
- <el-row :gutter="15" class="mb-4">
|
|
|
- <el-col
|
|
|
- :xs="24"
|
|
|
- :sm="12"
|
|
|
- :md="12"
|
|
|
- :lg="4"
|
|
|
- :xl="4"
|
|
|
- v-for="(item, index) in state.homeOne"
|
|
|
- :key="index"
|
|
|
+
|
|
|
+ <!-- 核心指标 -->
|
|
|
+ <div class="featured-row">
|
|
|
+ <div
|
|
|
+ v-for="(metric, idx) in featuredMetrics"
|
|
|
+ :key="idx"
|
|
|
+ class="featured-card"
|
|
|
+ :class="idx === 0 ? 'featured-primary' : 'featured-secondary'"
|
|
|
>
|
|
|
- <el-card class="stat-card" shadow="hover">
|
|
|
- <div class="stat-content">
|
|
|
- <span class="stat-value">{{ item.num1 }}</span>
|
|
|
- <div class="stat-label">{{ item.num3 }}</div>
|
|
|
+ <div class="featured-icon">
|
|
|
+ <component :is="useRenderIcon(metric.icon)" />
|
|
|
+ </div>
|
|
|
+ <div class="featured-body">
|
|
|
+ <div class="featured-value">
|
|
|
+ {{ metric.value }}
|
|
|
+ <span v-if="isMoneyMetric(metric.label)" class="featured-unit">元</span>
|
|
|
+ <span v-else class="featured-unit">笔</span>
|
|
|
</div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ <div class="featured-label">{{ metric.label }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 次要指标 -->
|
|
|
+ <div class="secondary-row">
|
|
|
+ <div
|
|
|
+ v-for="(metric, idx) in secondaryMetrics"
|
|
|
+ :key="idx"
|
|
|
+ class="secondary-card"
|
|
|
+ >
|
|
|
+ <div class="secondary-icon">
|
|
|
+ <component :is="useRenderIcon(metric.icon)" />
|
|
|
+ </div>
|
|
|
+ <div class="secondary-body">
|
|
|
+ <div class="secondary-value">
|
|
|
+ {{ metric.value }}
|
|
|
+ <span v-if="isMoneyMetric(metric.label)" class="secondary-unit">元</span>
|
|
|
+ <span v-else-if="metric.label.includes('时长')" class="secondary-unit">分钟</span>
|
|
|
+ <span v-else class="secondary-unit">人</span>
|
|
|
+ </div>
|
|
|
+ <div class="secondary-label">{{ metric.label }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 图表区域 -->
|
|
|
<el-row :gutter="15">
|
|
|
@@ -381,19 +433,19 @@ watch(
|
|
|
:shortcuts="shortcuts"
|
|
|
/>
|
|
|
<div class="chart-summary">
|
|
|
- 总收益金额:
|
|
|
- <el-tag type="success">{{ fmtMoney(state.homeOneExtra.totalIncome) }}元</el-tag>
|
|
|
- 总订单数量:
|
|
|
- <el-tag type="danger">{{ state.homeOneExtra.totalWashOrders }}笔</el-tag>
|
|
|
+ 总收益:
|
|
|
+ <el-tag type="success" size="small">{{ fmtMoney(state.homeOneExtra.totalIncome) }}元</el-tag>
|
|
|
+ 总订单:
|
|
|
+ <el-tag type="danger" size="small">{{ state.homeOneExtra.totalWashOrders }}笔</el-tag>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <div class="chart-wrapper" ref="homeLineRef"></div>
|
|
|
+ <div class="chart-wrapper" ref="homeLineRef" />
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :xs="24" :sm="10" :md="10" :lg="8" :xl="8">
|
|
|
<el-card class="chart-card">
|
|
|
- <div class="chart-wrapper" ref="homePieRef"></div>
|
|
|
+ <div class="chart-wrapper" ref="homePieRef" />
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
@@ -405,47 +457,144 @@ watch(
|
|
|
padding: 15px;
|
|
|
}
|
|
|
|
|
|
-.stat-card {
|
|
|
+// 核心指标行
|
|
|
+.featured-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 3fr 2fr;
|
|
|
+ gap: 15px;
|
|
|
margin-bottom: 15px;
|
|
|
-
|
|
|
- .stat-content {
|
|
|
- text-align: center;
|
|
|
- padding: 10px 0;
|
|
|
-
|
|
|
- .stat-value {
|
|
|
- font-size: 28px;
|
|
|
- font-weight: 600;
|
|
|
+
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.featured-card {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+ padding: 24px 28px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: var(--el-bg-color);
|
|
|
+
|
|
|
+ &.featured-primary {
|
|
|
+ box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
|
|
|
+ border-top: 3px solid var(--el-color-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.featured-secondary {
|
|
|
+ box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
|
|
|
+ }
|
|
|
+
|
|
|
+ .featured-icon {
|
|
|
+ font-size: 40px;
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ opacity: 0.85;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .featured-body {
|
|
|
+ .featured-value {
|
|
|
+ font-size: 36px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.2;
|
|
|
color: var(--el-text-color-primary);
|
|
|
+ letter-spacing: -0.5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .featured-unit {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ margin-left: 4px;
|
|
|
}
|
|
|
-
|
|
|
- .stat-label {
|
|
|
- margin-top: 8px;
|
|
|
+
|
|
|
+ .featured-label {
|
|
|
+ margin-top: 6px;
|
|
|
font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
color: var(--el-text-color-secondary);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 次要指标行
|
|
|
+.secondary-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 15px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+
|
|
|
+ @media (max-width: 992px) {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 640px) {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.secondary-card {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 14px;
|
|
|
+ padding: 18px 20px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: var(--el-bg-color);
|
|
|
+ box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
|
|
|
+
|
|
|
+ .secondary-icon {
|
|
|
+ font-size: 28px;
|
|
|
+ color: var(--el-text-color-placeholder);
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .secondary-body {
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ .secondary-value {
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.3;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ .secondary-unit {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ margin-left: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .secondary-label {
|
|
|
+ margin-top: 2px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 图表
|
|
|
.chart-card {
|
|
|
margin-bottom: 15px;
|
|
|
-
|
|
|
+
|
|
|
.chart-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
flex-wrap: wrap;
|
|
|
gap: 10px;
|
|
|
-
|
|
|
+
|
|
|
.chart-summary {
|
|
|
font-size: 14px;
|
|
|
color: var(--el-text-color-secondary);
|
|
|
-
|
|
|
+
|
|
|
.el-tag {
|
|
|
- margin: 0 5px;
|
|
|
+ margin: 0 4px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
.chart-wrapper {
|
|
|
height: 350px;
|
|
|
width: 100%;
|
|
|
@@ -457,5 +606,13 @@ watch(
|
|
|
flex-direction: column;
|
|
|
align-items: flex-start;
|
|
|
}
|
|
|
+
|
|
|
+ .featured-card {
|
|
|
+ padding: 18px 20px;
|
|
|
+
|
|
|
+ .featured-value {
|
|
|
+ font-size: 28px;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|