<template>
|
<div style="padding: 20px;">
|
<!-- 页面标题和月份筛选 -->
|
<div class="w-full md:w-auto flex items-center gap-3"
|
style="margin-bottom: 20px;">
|
<el-date-picker v-model="dateRange"
|
type="monthrange"
|
format="YYYY-MM"
|
value-format="YYYY-MM"
|
range-separator="至"
|
start-placeholder="开始月份"
|
end-placeholder="结束月份"
|
:disabled-date="disabledDate"
|
@change="handleDateChange"
|
class="w-full md:w-auto"
|
style="margin-right: 30px;" />
|
<el-button type="primary"
|
icon="Refresh"
|
@click="resetDateRange"
|
size="default">
|
重置
|
</el-button>
|
</div>
|
<main class="container mx-auto px-4 pb-10">
|
<!-- 财务指标卡片 -->
|
<div class="stats-cards">
|
<!-- 总营收 -->
|
<div class="stat-card stat-card-blue">
|
<div class="stat-icon">
|
<img src="@/assets/icons/png/walletBlue@2x.png"
|
alt="总营收" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-label">总营收</div>
|
<div class="stat-value">{{ formatMoney(pageInfo.totalIncome || 0) }} 元</div>
|
</div>
|
</div>
|
<!-- 总支出 -->
|
<div class="stat-card stat-card-orange">
|
<div class="stat-icon">
|
<img src="@/assets/icons/png/walletOrange@2x.png"
|
alt="总支出" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-label">总支出</div>
|
<div class="stat-value">{{ formatMoney(pageInfo.totalExpense || 0) }} 元</div>
|
</div>
|
</div>
|
<!-- 总收入笔数 -->
|
<div class="stat-card stat-card-green">
|
<div class="stat-icon">
|
<img src="@/assets/icons/png/walletGreen@2x.png"
|
alt="总收入笔数" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-label">总收入笔数</div>
|
<div class="stat-value">{{ pageInfo.incomeNumber || 0 }} 笔</div>
|
</div>
|
</div>
|
<!-- 总支出笔数 -->
|
<div class="stat-card stat-card-red">
|
<div class="stat-icon">
|
<img src="@/assets/icons/png/walletRed@2x.png"
|
alt="总支出笔数" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-label">总支出笔数</div>
|
<div class="stat-value">{{ pageInfo.expenseNumber || 0 }} 笔</div>
|
</div>
|
</div>
|
<!-- 净收入 -->
|
<div class="stat-card stat-card-yellow">
|
<div class="stat-icon">
|
<img src="@/assets/icons/png/walletYellow@2x.png"
|
alt="净收入" />
|
</div>
|
<div class="stat-content">
|
<div class="stat-label">净收入</div>
|
<div class="stat-value">{{ formatMoney(pageInfo.netRevenue || 0) }} 元</div>
|
</div>
|
</div>
|
</div>
|
<!-- 中间图表区域 -->
|
<div class="charts-row">
|
<!-- 左侧:收入支出分析 -->
|
<el-card class="chart-card">
|
<h2 class="section-title">收入支出分析</h2>
|
<div class="pie-chart-container">
|
<Echarts :legend="pieLegendIncomeExpense"
|
:chartStyle="chartStylePie"
|
:series="pieSeriesIncomeExpense"
|
:tooltip="pieTooltipIncomeExpense"
|
style="height: 320px; width: 100%;">
|
</Echarts>
|
<div class="pie-stats">
|
<div class="bar-stat-item">
|
<span class="bar-stat-label">收入数量</span>
|
<span class="bar-stat-value">{{ pageInfo.incomeNumber || 0 }}</span>
|
</div>
|
<div class="bar-stat-item">
|
<span class="bar-stat-label">支出数量</span>
|
<span class="bar-stat-value">{{ pageInfo.expenseNumber || 0 }}</span>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
<!-- 右侧:行项盈利分析 -->
|
<el-card class="chart-card">
|
<h2 class="section-title">行项盈利分析</h2>
|
<div class="bar-chart-header">
|
<div class="bar-stat-item">
|
<span class="bar-stat-label">当前总个数</span>
|
<span class="bar-stat-value">{{ allBarTypes.value?.length || 0 }}</span>
|
</div>
|
<div class="bar-stat-item">
|
<span class="bar-stat-label">支出金额</span>
|
<span class="bar-stat-value">{{ formatMoney(pageInfo.totalExpense || 0) }}</span>
|
</div>
|
<div class="bar-stat-item">
|
<span class="bar-stat-label">收入金额</span>
|
<span class="bar-stat-value">{{ formatMoney(pageInfo.totalIncome || 0) }}</span>
|
</div>
|
</div>
|
<Echarts ref="barChart"
|
:chartStyle="chartStyle"
|
:grid="barGrid"
|
:legend="barLegend"
|
:series="barSeries"
|
:tooltip="barTooltip"
|
:xAxis="barXAxis"
|
:yAxis="barYAxis"
|
style="height: 300px; width: 100%;">
|
</Echarts>
|
</el-card>
|
</div>
|
<!-- 底部:营收趋势分析 -->
|
<el-card class="trend-chart-card">
|
<h2 class="section-title">营收趋势分析</h2>
|
<Echarts ref="trendChart"
|
:chartStyle="chartStyle"
|
:grid="grid"
|
:legend="trendLegend"
|
:series="trendSeries"
|
:tooltip="tooltip"
|
:xAxis="xAxis0"
|
:yAxis="trendYAxis"
|
style="height: 350px; width: 100%;">
|
</Echarts>
|
</el-card>
|
</main>
|
</div>
|
</template>
|
|
<script setup>
|
import {
|
ref,
|
computed,
|
onMounted,
|
reactive,
|
nextTick,
|
getCurrentInstance,
|
} from "vue";
|
import "element-plus/dist/index.css";
|
import Echarts from "@/components/Echarts/echarts.vue";
|
import {
|
reportForms,
|
reportIncome,
|
reportExpense,
|
} from "@/api/financialManagement/financialStatements";
|
import dayjs from "dayjs";
|
|
// 日期范围
|
const dateRange = ref(null);
|
const { proxy } = getCurrentInstance();
|
const chartStyle = {
|
width: "100%",
|
height: "100%", // 设置图表容器的高度
|
position: "relative",
|
};
|
const grid = {
|
left: "3%",
|
right: "4%",
|
bottom: "3%",
|
containLabel: true,
|
};
|
const lineLegend = {
|
show: false,
|
};
|
// 折线图提示框
|
const tooltip = reactive({
|
trigger: "axis",
|
axisPointer: {
|
type: "line",
|
lineStyle: { color: "#aaa" },
|
},
|
// 自定义内容
|
formatter: function (params) {
|
if (!params || !params.length) return "";
|
const axisLabel = params[0].axisValueLabel || params[0].axisValue || "";
|
const rows = params
|
.map(p => {
|
const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`;
|
return `${colorDot}${p.seriesName}: ${p.value}`;
|
})
|
.join("<br/>");
|
return `<div>${axisLabel}</div><div>${rows}</div>`;
|
},
|
});
|
const lineSeries0 = ref([]);
|
const lineSeries1 = ref([]);
|
|
// 根据月份范围生成 x 轴数据
|
const generateMonthLabels = (startMonth, endMonth) => {
|
const labels = [];
|
let current = dayjs(startMonth);
|
const end = dayjs(endMonth);
|
|
while (current.isBefore(end) || current.isSame(end, "month")) {
|
labels.push(`${current.month() + 1}月`);
|
current = current.add(1, "month");
|
}
|
|
return labels;
|
};
|
|
const xAxis0 = ref([
|
{
|
type: "category",
|
axisTick: { show: true, alignWithLabel: true },
|
data: [],
|
},
|
]);
|
const xAxis1 = ref([
|
{
|
type: "category",
|
axisTick: { show: true, alignWithLabel: true },
|
data: [],
|
},
|
]);
|
const yAxis0 = [
|
{
|
type: "value",
|
name: "收入统计", // 左侧y轴
|
position: "left",
|
min: 0,
|
// 坐标轴名称样式
|
nameTextStyle: {
|
color: "#000",
|
fontSize: 14,
|
},
|
},
|
];
|
|
const yAxis1 = [
|
{
|
type: "value",
|
name: "支出统计", // 左侧y轴
|
position: "left",
|
min: 0,
|
// 坐标轴名称样式
|
nameTextStyle: {
|
color: "#000",
|
fontSize: 14,
|
},
|
},
|
];
|
|
const chartStylePie = {
|
width: "100%",
|
height: "100%", // 设置图表容器的高度
|
};
|
const pieColors = [
|
"#F04864",
|
"#FACC14",
|
"#8543E0",
|
"#1890FF",
|
"#13C2C2",
|
"#2FC25B",
|
]; // 可根据实际调整
|
const pieData0 = ref([]);
|
const pieData1 = ref([]);
|
|
const pieLegend0 = computed(() => ({
|
show: true,
|
top: "center",
|
left: "60%",
|
orient: "vertical",
|
icon: "circle",
|
data: (pieData0.value || [])
|
.filter(item => item && item.name)
|
.map(item => item.name),
|
formatter: function (name) {
|
if (!name) return "";
|
const item = pieData0.value.find(i => i && i.name === name);
|
if (!item) return name;
|
return `${name} | ${item.percent} ${item.amount}`;
|
},
|
textStyle: {
|
color: "#333",
|
fontSize: 14,
|
lineHeight: 26,
|
},
|
}));
|
const pieLegend1 = computed(() => ({
|
show: true,
|
top: "center",
|
left: "60%",
|
orient: "vertical",
|
icon: "circle",
|
data: (pieData1.value || [])
|
.filter(item => item && item.name)
|
.map(item => item.name),
|
formatter: function (name) {
|
if (!name) return "";
|
const item = pieData1.value.find(i => i && i.name === name);
|
if (!item) return name;
|
return `${name} | ${item.percent} ${item.amount}`;
|
},
|
textStyle: {
|
color: "#333",
|
fontSize: 14,
|
lineHeight: 26,
|
},
|
}));
|
|
const materialPieSeries0 = computed(() => [
|
{
|
type: "pie",
|
radius: ["50%", "65%"],
|
center: ["25%", "50%"],
|
avoidLabelOverlap: false,
|
itemStyle: {
|
borderColor: "#fff",
|
borderWidth: 2,
|
},
|
label: {
|
show: false,
|
},
|
data: (pieData0.value || []).filter(item => item && item.name),
|
color: pieColors,
|
},
|
]);
|
const materialPieSeries1 = computed(() => [
|
{
|
type: "pie",
|
radius: ["50%", "65%"],
|
center: ["25%", "50%"],
|
avoidLabelOverlap: false,
|
itemStyle: {
|
borderColor: "#fff",
|
borderWidth: 2,
|
},
|
label: {
|
show: false,
|
},
|
data: (pieData1.value || []).filter(item => item && item.name),
|
color: pieColors,
|
},
|
]);
|
const pieTooltip = reactive({
|
trigger: "item",
|
formatter: function (params) {
|
// 检查数据是否存在
|
if (!params.data) return params.name;
|
// 拼接完整内容
|
return `
|
<div>
|
<div style="color:${params.color};font-size:16px;">●</div>
|
<div>${params.name}</div>
|
<div>占比:${params.data.percent}</div>
|
<div>金额:${params.data.amount}</div>
|
</div>
|
`;
|
},
|
});
|
|
const pageInfo = ref({});
|
|
// 格式化金额
|
const formatMoney = value => {
|
if (!value && value !== 0) return "0";
|
return Number(value).toLocaleString("zh-CN", {
|
minimumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
});
|
};
|
|
// 收入支出分析饼图
|
const pieDataIncomeExpense = computed(() => {
|
const totalIncome = Number(pageInfo.value.totalIncome) || 0;
|
const totalExpense = Number(pageInfo.value.totalExpense) || 0;
|
const total = totalIncome + totalExpense;
|
if (total === 0) {
|
return [
|
{ name: "收入", value: 0, percent: "0%" },
|
{ name: "支出", value: 0, percent: "0%" },
|
];
|
}
|
const incomePercent = ((totalIncome / total) * 100).toFixed(0);
|
const expensePercent = ((totalExpense / total) * 100).toFixed(0);
|
return [
|
{ name: "收入", value: totalIncome, percent: `${incomePercent}%` },
|
{ name: "支出", value: totalExpense, percent: `${expensePercent}%` },
|
];
|
});
|
|
const pieLegendIncomeExpense = computed(() => ({
|
show: false,
|
}));
|
|
const pieTooltipIncomeExpense = reactive({
|
trigger: "item",
|
formatter: function (params) {
|
if (!params.data) return params.name;
|
return `${params.name}占比 ${params.percent}%`;
|
},
|
});
|
|
const pieSeriesIncomeExpense = computed(() => [
|
{
|
type: "pie",
|
radius: ["0%", "70%"],
|
center: ["50%", "50%"],
|
avoidLabelOverlap: true,
|
itemStyle: {
|
borderColor: "#fff",
|
borderWidth: 2,
|
},
|
label: {
|
show: true,
|
position: "outside",
|
formatter: function (params) {
|
return `${params.name}占比 ${params.percent}%`;
|
},
|
fontSize: 14,
|
color: "#333",
|
},
|
labelLine: {
|
show: true,
|
length: 15,
|
length2: 10,
|
lineStyle: {
|
color: "#333",
|
},
|
},
|
emphasis: {
|
label: {
|
show: true,
|
fontSize: 16,
|
fontWeight: "bold",
|
},
|
},
|
data: pieDataIncomeExpense.value,
|
color: ["#1890FF", "#FACC14"],
|
},
|
]);
|
|
// 行项盈利分析柱状图
|
const barXAxis = computed(() => {
|
return [
|
{
|
type: "category",
|
data:
|
allBarTypes.value && allBarTypes.value.length > 0
|
? allBarTypes.value
|
: ["项目1", "项目2", "项目3", "项目4", "项目5", "项目6", "项目7"],
|
axisTick: { show: true, alignWithLabel: true },
|
},
|
];
|
});
|
|
const barYAxis = [
|
{
|
type: "value",
|
name: "单位: 元",
|
position: "left",
|
min: 0,
|
nameTextStyle: {
|
color: "#000",
|
fontSize: 14,
|
},
|
},
|
];
|
|
const barGrid = {
|
left: "3%",
|
right: "4%",
|
bottom: "3%",
|
containLabel: true,
|
};
|
|
const barLegend = {
|
show: true,
|
top: 10,
|
right: 10,
|
};
|
|
// 获取所有类型名称
|
const allBarTypes = computed(() => {
|
const incomeTypes = (lineSeries0.value || [])
|
.map(item => item.name || item.typeName)
|
.filter(Boolean);
|
const expenseTypes = (lineSeries1.value || [])
|
.map(item => item.name || item.typeName)
|
.filter(Boolean);
|
return [...new Set([...incomeTypes, ...expenseTypes])];
|
});
|
|
const barSeries = computed(() => {
|
if (allBarTypes.value.length === 0) {
|
return [
|
{
|
name: "支出",
|
type: "bar",
|
data: [],
|
itemStyle: { color: "#1890FF" },
|
},
|
{
|
name: "收入",
|
type: "bar",
|
data: [],
|
itemStyle: { color: "#13C2C2" },
|
},
|
];
|
}
|
|
// 计算每个项目的总收入(汇总所有月份)
|
const incomeData = allBarTypes.value.map(typeName => {
|
const incomeItem = (lineSeries0.value || []).find(
|
item => (item.name || item.typeName) === typeName
|
);
|
if (incomeItem && incomeItem.data && Array.isArray(incomeItem.data)) {
|
return incomeItem.data.reduce((sum, val) => sum + (Number(val) || 0), 0);
|
}
|
return 0;
|
});
|
|
// 计算每个项目的总支出(汇总所有月份)
|
const expenseData = allBarTypes.value.map(typeName => {
|
const expenseItem = (lineSeries1.value || []).find(
|
item => (item.name || item.typeName) === typeName
|
);
|
if (expenseItem && expenseItem.data && Array.isArray(expenseItem.data)) {
|
return expenseItem.data.reduce((sum, val) => sum + (Number(val) || 0), 0);
|
}
|
return 0;
|
});
|
|
return [
|
{
|
name: "支出",
|
type: "bar",
|
data: expenseData,
|
itemStyle: { color: "#1890FF" },
|
},
|
{
|
name: "收入",
|
type: "bar",
|
data: incomeData,
|
itemStyle: { color: "#13C2C2" },
|
},
|
];
|
});
|
|
const barTooltip = reactive({
|
trigger: "axis",
|
axisPointer: {
|
type: "shadow",
|
},
|
formatter: function (params) {
|
if (!params || !params.length) return "";
|
const axisLabel = params[0].axisValueLabel || params[0].axisValue || "";
|
const rows = params
|
.map(p => {
|
const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`;
|
const value =
|
typeof p.value === "number" ? p.value.toFixed(2) : p.value;
|
return `${colorDot}${p.seriesName} ${value}`;
|
})
|
.join("<br/>");
|
return `<div>${axisLabel}</div><div>${rows}</div>`;
|
},
|
});
|
|
// 营收趋势分析
|
const trendLegend = {
|
show: true,
|
top: 10,
|
right: 10,
|
};
|
|
const trendYAxis = [
|
{
|
type: "value",
|
name: "单位: 元",
|
position: "left",
|
min: 0,
|
nameTextStyle: {
|
color: "#000",
|
fontSize: 14,
|
},
|
},
|
];
|
|
const trendSeries = computed(() => {
|
// 汇总所有支出类型的数据
|
let expenseTrend = [];
|
if (lineSeries1.value.length > 0) {
|
const monthCount = Math.max(
|
...lineSeries1.value.map(item => item.data?.length || 0)
|
);
|
expenseTrend = Array(monthCount).fill(0);
|
lineSeries1.value.forEach(item => {
|
if (item.data && Array.isArray(item.data)) {
|
item.data.forEach((val, index) => {
|
if (index < monthCount) {
|
expenseTrend[index] += Number(val) || 0;
|
}
|
});
|
}
|
});
|
}
|
|
// 汇总所有收入类型的数据
|
let incomeTrend = [];
|
if (lineSeries0.value.length > 0) {
|
const monthCount = Math.max(
|
...lineSeries0.value.map(item => item.data?.length || 0)
|
);
|
incomeTrend = Array(monthCount).fill(0);
|
lineSeries0.value.forEach(item => {
|
if (item.data && Array.isArray(item.data)) {
|
item.data.forEach((val, index) => {
|
if (index < monthCount) {
|
incomeTrend[index] += Number(val) || 0;
|
}
|
});
|
}
|
});
|
}
|
|
return [
|
{
|
name: "支出",
|
type: "line",
|
data: expenseTrend,
|
itemStyle: { color: "#1890FF" },
|
smooth: true,
|
},
|
{
|
name: "收入",
|
type: "line",
|
data: incomeTrend,
|
itemStyle: { color: "#13C2C2" },
|
smooth: true,
|
},
|
];
|
});
|
|
// 获取最近六个月的范围
|
const getLastSixMonths = () => {
|
const endMonth = dayjs().format("YYYY-MM");
|
const startMonth = dayjs().subtract(5, "month").format("YYYY-MM");
|
return [startMonth, endMonth];
|
};
|
|
const getData = async () => {
|
if (
|
!dateRange.value ||
|
!Array.isArray(dateRange.value) ||
|
dateRange.value.length !== 2
|
) {
|
return;
|
}
|
const startDateStr = dateRange.value[0];
|
const endDateStr = dateRange.value[1];
|
if (!startDateStr || !endDateStr) {
|
return;
|
}
|
|
// 验证日期格式并转换为完整日期
|
const startDate = dayjs(startDateStr);
|
const endDate = dayjs(endDateStr);
|
if (!startDate.isValid() || !endDate.isValid()) {
|
console.error("无效的日期格式");
|
return;
|
}
|
|
// 更新 x 轴数据
|
const monthLabels = generateMonthLabels(startDateStr, endDateStr);
|
xAxis0.value[0].data = monthLabels;
|
xAxis1.value[0].data = monthLabels;
|
|
// 开始月份拼接第一天,结束月份拼接最后一天
|
const entryDateStart = startDate.startOf("month").format("YYYY-MM-DD");
|
const entryDateEnd = endDate.endOf("month").format("YYYY-MM-DD");
|
|
try {
|
// const {code,data} = await reportForms({entryDateStart, entryDateEnd});
|
// if(code === 200 && data) {
|
// pageInfo.value = data || {};
|
// // 安全处理数据,过滤掉 null 或 undefined
|
// pieData0.value = (data.incomeType || []).filter(item => item && item.typeName).map(item=>({
|
// name:item.typeName || '',
|
// value:item.account || 0,
|
// percent:`${((item.proportion || 0) * 100).toFixed(2)}%`,
|
// amount:`¥${(item.account || 0).toFixed(2)}`
|
// }))
|
// pieData1.value = (data.expenseType || []).filter(item => item && item.typeName).map(item=>({
|
// name:item.typeName || '',
|
// value:item.account || 0,
|
// percent:`${((item.proportion || 0) * 100).toFixed(2)}%`,
|
// amount:`¥${(item.account || 0).toFixed(2)}`
|
// }))
|
// }
|
} catch (error) {
|
console.error("获取财务指标数据失败:", error);
|
}
|
try {
|
// const { code, data } = await reportIncome({ entryDateStart, entryDateEnd });
|
// if (code == 200 && data && Array.isArray(data)) {
|
// lineSeries0.value = data
|
// .filter(item => item && item.typeName)
|
// .map(item => ({
|
// name: item.typeName || "",
|
// type: "line",
|
// data: (item.account || []).map(val => Number(val) || 0),
|
// }));
|
// }
|
} catch (error) {
|
console.error("获取财务指标数据失败:", error);
|
}
|
try {
|
// const { code, data } = await reportExpense({
|
// entryDateStart,
|
// entryDateEnd,
|
// });
|
// if (code == 200 && data && Array.isArray(data)) {
|
// lineSeries1.value = data
|
// .filter(item => item && item.typeName)
|
// .map(item => ({
|
// name: item.typeName || "",
|
// type: "line",
|
// data: (item.account || []).map(val => Number(val) || 0),
|
// }));
|
// }
|
} catch (error) {
|
console.error("获取财务指标数据失败:", error);
|
}
|
};
|
|
// 初始化
|
onMounted(() => {
|
// 设置默认值为最近六个月
|
const defaultRange = getLastSixMonths();
|
dateRange.value = defaultRange;
|
// 使用 nextTick 确保组件完全渲染后再调用
|
nextTick(() => {
|
getData();
|
});
|
});
|
|
// 限制月份选择范围(最多12个月)
|
const disabledDate = time => {
|
// 如果没有选择开始月份,不禁用任何日期
|
if (
|
!dateRange.value ||
|
!Array.isArray(dateRange.value) ||
|
!dateRange.value[0]
|
) {
|
return false;
|
}
|
|
const startMonth = dayjs(dateRange.value[0]);
|
const currentMonth = dayjs(time);
|
|
// 如果当前月份在开始月份之前,禁用
|
if (currentMonth.isBefore(startMonth, "month")) {
|
return true;
|
}
|
|
// 计算最大允许的月份(开始月份 + 11个月 = 12个月)
|
const maxMonth = startMonth.add(11, "month");
|
|
// 禁用超过12个月的月份
|
return currentMonth.isAfter(maxMonth, "month");
|
};
|
|
// 处理月份范围变化
|
const handleDateChange = newRange => {
|
if (!newRange || !Array.isArray(newRange) || newRange.length !== 2) {
|
return;
|
}
|
|
// 验证月份范围不超过12个月
|
const startDate = dayjs(newRange[0]);
|
const endDate = dayjs(newRange[1]);
|
const monthDiff = endDate.diff(startDate, "month");
|
|
if (monthDiff > 11) {
|
proxy.$modal.msgWarning("最多只能选择12个月份");
|
// 自动调整为12个月
|
const adjustedEnd = startDate.add(11, "month").format("YYYY-MM");
|
dateRange.value = [newRange[0], adjustedEnd];
|
getData();
|
return;
|
}
|
|
dateRange.value = newRange;
|
getData();
|
};
|
|
// 重置月份范围
|
const resetDateRange = () => {
|
// 重置为最近六个月
|
dateRange.value = getLastSixMonths();
|
getData();
|
};
|
</script>
|
|
<style scoped lang="scss">
|
/* 基础样式补充 */
|
:root {
|
--el-color-primary: #4f46e5;
|
}
|
|
/* 统计卡片样式 */
|
.stats-cards {
|
display: grid;
|
grid-template-columns: repeat(5, 1fr);
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
.stat-card {
|
background: #fff;
|
border: 1px solid #e4e7ed;
|
border-radius: 8px;
|
padding: 20px;
|
display: flex;
|
align-items: center;
|
gap: 15px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
transition: all 0.3s;
|
|
&:hover {
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
transform: translateY(-2px);
|
}
|
|
.stat-icon {
|
width: 48px;
|
height: 48px;
|
flex-shrink: 0;
|
|
img {
|
width: 100%;
|
height: 100%;
|
object-fit: contain;
|
}
|
}
|
|
.stat-content {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 8px;
|
}
|
|
.stat-label {
|
font-size: 14px;
|
color: #666;
|
line-height: 1.2;
|
}
|
|
.stat-value {
|
font-size: 24px;
|
font-weight: 600;
|
color: #333;
|
line-height: 1.2;
|
}
|
|
.stat-trend {
|
font-size: 12px;
|
line-height: 1.2;
|
|
&.trend-up {
|
color: #f56c6c;
|
}
|
|
&.trend-down {
|
color: #67c23a;
|
}
|
}
|
}
|
|
/* 图表行布局 */
|
.charts-row {
|
display: grid;
|
grid-template-columns: 1fr 1fr;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
.chart-card {
|
border: 1px solid #e4e7ed;
|
border-radius: 8px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
:deep(.el-card__body) {
|
padding: 20px !important;
|
}
|
}
|
|
.trend-chart-card {
|
border: 1px solid #e4e7ed;
|
border-radius: 8px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
:deep(.el-card__body) {
|
padding: 20px !important;
|
}
|
}
|
|
/* 饼图容器 */
|
.pie-chart-container {
|
position: relative;
|
|
.pie-stats {
|
display: flex;
|
justify-content: space-between;
|
gap: 20px;
|
margin-top: 20px;
|
|
.bar-stat-item {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 8px;
|
padding: 15px;
|
background: #f5f7fa;
|
border-radius: 6px;
|
flex: 1;
|
|
.bar-stat-label {
|
font-size: 14px;
|
color: #666;
|
}
|
|
.bar-stat-value {
|
font-size: 18px;
|
font-weight: 600;
|
color: #333;
|
}
|
}
|
}
|
}
|
|
/* 柱状图头部统计 */
|
.bar-chart-header {
|
display: flex;
|
justify-content: space-between;
|
gap: 20px;
|
margin-bottom: 20px;
|
|
.bar-stat-item {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 8px;
|
padding: 15px;
|
background: #f5f7fa;
|
border-radius: 6px;
|
flex: 1;
|
|
.bar-stat-label {
|
font-size: 14px;
|
color: #666;
|
}
|
|
.bar-stat-value {
|
font-size: 18px;
|
font-weight: 600;
|
color: #333;
|
}
|
}
|
}
|
|
/* 标题样式 */
|
.section-title {
|
position: relative;
|
font-size: 18px;
|
color: #333;
|
padding-left: 12px;
|
margin-bottom: 20px;
|
font-weight: 700;
|
|
&::before {
|
position: absolute;
|
left: 0;
|
top: 2px;
|
content: "";
|
width: 4px;
|
height: 18px;
|
background-color: #002fa7;
|
border-radius: 2px;
|
}
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 1400px) {
|
.stats-cards {
|
grid-template-columns: repeat(3, 1fr);
|
}
|
}
|
|
@media (max-width: 1024px) {
|
.stats-cards {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
|
.charts-row {
|
grid-template-columns: 1fr;
|
}
|
}
|
|
@media (max-width: 640px) {
|
.stats-cards {
|
grid-template-columns: 1fr;
|
}
|
}
|
</style>
|