From 80adf052e0b58abd634ca9b67f8569ccc468c430 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期一, 23 三月 2026 15:11:36 +0800
Subject: [PATCH] Merge branch 'dev_银川_中盛建材' of http://114.132.189.42:9002/r/product-inventory-management into dev_银川_中盛建材
---
src/views/costAccounting/stdVsActCostAnalysis/index.vue | 817 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 704 insertions(+), 113 deletions(-)
diff --git a/src/views/costAccounting/stdVsActCostAnalysis/index.vue b/src/views/costAccounting/stdVsActCostAnalysis/index.vue
index 45b0e25..8fa9f16 100644
--- a/src/views/costAccounting/stdVsActCostAnalysis/index.vue
+++ b/src/views/costAccounting/stdVsActCostAnalysis/index.vue
@@ -1,12 +1,28 @@
<template>
<div class="std-cost-page">
- <el-card class="filter-card" shadow="never">
+ <div class="page-bg" aria-hidden="true">
+ <div class="bg-mesh" />
+ <div class="bg-orb bg-orb--a" />
+ <div class="bg-orb bg-orb--b" />
+ <div class="bg-orb bg-orb--c" />
+ <div class="bg-grid" />
+ </div>
+
+ <div class="page-inner">
+ <el-card class="filter-card glass-card" shadow="never">
<template #header>
<div class="card-head">
<div class="card-head-left">
- <el-icon class="card-icon ui-icon"><DataLine /></el-icon>
- <span class="card-title">鏍囧噯/瀹為檯鎴愭湰瀵规瘮鍒嗘瀽</span>
- <span class="subtle">宸紓 = 瀹為檯鎴愭湰 - 鏍囧噯鎴愭湰</span>
+ <div class="title-badge">
+ <el-icon class="card-icon ui-icon"><DataLine /></el-icon>
+ </div>
+ <div class="title-block">
+ <div class="title-row">
+ <span class="card-title shimmer-text">鏍囧噯/瀹為檯鎴愭湰瀵规瘮鍒嗘瀽</span>
+ <span class="live-pill">瀹炴椂鍒嗘瀽</span>
+ </div>
+ <span class="subtle">宸紓 = 瀹為檯鎴愭湰 鈭� 鏍囧噯鎴愭湰</span>
+ </div>
</div>
</div>
</template>
@@ -87,40 +103,68 @@
</div>
</el-card>
- <el-card class="panel-card" shadow="never">
+ <el-card class="panel-card glass-card kpi-card" shadow="never">
<div class="kpi-strip">
<div class="kpi-item kpi-std">
- <div class="kpi-label">鏍囧噯鎴愭湰鍚堣</div>
+ <div class="kpi-top">
+ <el-icon class="kpi-ico"><Histogram /></el-icon>
+ <div class="kpi-label">鏍囧噯鎴愭湰鍚堣</div>
+ </div>
<div class="kpi-value">楼{{ formatMoney(overview.standardCost) }}</div>
+ <div class="kpi-glow" />
</div>
<div class="kpi-item kpi-act">
- <div class="kpi-label">瀹為檯鎴愭湰鍚堣</div>
+ <div class="kpi-top">
+ <el-icon class="kpi-ico"><TrendCharts /></el-icon>
+ <div class="kpi-label">瀹為檯鎴愭湰鍚堣</div>
+ </div>
<div class="kpi-value">楼{{ formatMoney(overview.actualCost) }}</div>
+ <div class="kpi-glow" />
</div>
<div class="kpi-item kpi-diff">
- <div class="kpi-label">宸紓鍚堣</div>
+ <div class="kpi-top">
+ <el-icon class="kpi-ico"><Switch /></el-icon>
+ <div class="kpi-label">宸紓鍚堣</div>
+ </div>
<div class="kpi-value" :class="overview.diff >= 0 ? 'cost-value' : 'ok-value'">
楼{{ formatMoney(overview.diff) }}
</div>
+ <div class="kpi-glow" />
</div>
<div class="kpi-item kpi-rate">
- <div class="kpi-label">宸紓鐜�</div>
+ <div class="kpi-top">
+ <el-icon class="kpi-ico"><PieChart /></el-icon>
+ <div class="kpi-label">宸紓鐜�</div>
+ </div>
<div class="kpi-value">{{ formatPercent(overview.diffRate) }}</div>
+ <div class="kpi-glow" />
</div>
</div>
</el-card>
- <el-card class="table-card" shadow="never">
+ <el-card class="table-card glass-card chart-section" shadow="never">
<template #header>
<div class="panel-head">
- <span class="card-title">鏍囧噯/瀹為檯鎴愭湰鍙鍖栵紙鏌辩姸 + 鎶樼嚎锛�</span>
+ <div class="panel-head-main">
+ <span class="panel-accent" />
+ <div>
+ <span class="card-title">鏍囧噯/瀹為檯鎴愭湰鍙鍖�</span>
+ <span class="chart-tag">鏌辩姸 路 鎶樼嚎</span>
+ </div>
+ </div>
<span class="subtle">鏀寔鎸夋湀浠姐�佷骇鍝佺被鍒�佹垚鏈被鍨嬬瓫閫�</span>
</div>
</template>
<div class="chart-wrap">
<div class="chart-tools chart-tools-inline" @click.stop>
- <button class="chart-tool" type="button" @click="openLargeChart">鏌ョ湅澶у浘</button>
- <button class="chart-tool" type="button" @click="downloadChartImage">涓嬭浇鍥捐〃</button>
+ <button class="chart-tool chart-tool--primary" type="button" @click="openLargeChart">
+ <el-icon><ZoomIn /></el-icon>
+ 鏌ョ湅澶у浘
+ </button>
+ <button class="chart-tool" type="button" @click="downloadChartImage">
+ <el-icon><Download /></el-icon>
+ 涓嬭浇鍥捐〃
+ </button>
</div>
<div ref="chartRef" class="chart-content"></div>
</div>
@@ -131,6 +175,7 @@
title="鏍囧噯/瀹為檯鎴愭湰瀵规瘮澶у浘"
width="88%"
top="6vh"
+ append-to-body
destroy-on-close
@opened="initLargeChart"
@closed="disposeLargeChart"
@@ -138,31 +183,34 @@
<div ref="largeChartRef" class="large-chart-content"></div>
</el-dialog>
- <el-card class="table-card" shadow="never">
+ <el-card class="table-card glass-card" shadow="never">
<template #header>
<div class="panel-head">
- <span class="card-title">瀵规瘮鏄庣粏</span>
- <span class="subtle">鍏� {{ tableData.length }} 鏉�</span>
+ <div class="panel-head-main">
+ <span class="panel-accent panel-accent--emerald" />
+ <span class="card-title">瀵规瘮鏄庣粏</span>
+ </div>
+ <span class="count-chip">鍏� {{ tableData.length }} 鏉�</span>
</div>
</template>
- <el-table :data="pagedTableData" stripe class="lux-table">
+ <el-table :data="pagedTableData" stripe class="lux-table" @sort-change="handleSortChange">
<el-table-column prop="month" label="鏈堜唤" width="110" />
<el-table-column prop="category" label="浜у搧绫诲埆" min-width="140" />
<el-table-column prop="costType" label="鎴愭湰绫诲瀷" min-width="120" />
- <el-table-column prop="standardCost" label="鏍囧噯鎴愭湰(鍏�)" align="right">
+ <el-table-column prop="standardCost" label="鏍囧噯鎴愭湰(鍏�)" sortable="custom" align="right">
<template #default="scope">楼{{ formatMoney(scope.row.standardCost) }}</template>
</el-table-column>
- <el-table-column prop="actualCost" label="瀹為檯鎴愭湰(鍏�)" align="right">
+ <el-table-column prop="actualCost" label="瀹為檯鎴愭湰(鍏�)" sortable="custom" align="right">
<template #default="scope">楼{{ formatMoney(scope.row.actualCost) }}</template>
</el-table-column>
- <el-table-column prop="diff" label="宸紓(鍏�)" align="right">
+ <el-table-column prop="diff" label="宸紓(鍏�)" sortable="custom" align="right">
<template #default="scope">
<span :class="scope.row.diff >= 0 ? 'cost-value' : 'ok-value'">
{{ formatSignedMoney(scope.row.diff) }}
</span>
</template>
</el-table-column>
- <el-table-column prop="diffRate" label="宸紓鐜�" align="right">
+ <el-table-column prop="diffRate" label="宸紓鐜�" sortable="custom" align="right">
<template #default="scope">
<span :class="scope.row.diffRate >= 0 ? 'cost-value' : 'ok-value'">
{{ formatPercent(scope.row.diffRate) }}
@@ -182,12 +230,22 @@
/>
</div>
</el-card>
+ </div>
</div>
</template>
<script setup>
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
-import { ArrowDown, DataLine } from "@element-plus/icons-vue";
+import {
+ ArrowDown,
+ DataLine,
+ Download,
+ Histogram,
+ PieChart,
+ Switch,
+ TrendCharts,
+ ZoomIn,
+} from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import * as echarts from "echarts";
import * as XLSX from "xlsx";
@@ -213,35 +271,103 @@
const largeChartVisible = ref(false);
const currentChartOption = ref(null);
-const actualCostSource = ref([
- { month: "2026-01", category: "鐡风爾", costType: "鑳借�楁垚鏈�", actualCost: 182000 },
- { month: "2026-01", category: "鐡风爾", costType: "鐢熶骇鎴愭湰", actualCost: 465000 },
- { month: "2026-01", category: "姘存偿", costType: "鑳借�楁垚鏈�", actualCost: 138500 },
- { month: "2026-01", category: "姘存偿", costType: "鐢熶骇鎴愭湰", actualCost: 398000 },
- { month: "2026-02", category: "鐡风爾", costType: "鑳借�楁垚鏈�", actualCost: 191500 },
- { month: "2026-02", category: "鐡风爾", costType: "鐢熶骇鎴愭湰", actualCost: 472500 },
- { month: "2026-02", category: "姘存偿", costType: "鑳借�楁垚鏈�", actualCost: 142300 },
- { month: "2026-02", category: "姘存偿", costType: "鐢熶骇鎴愭湰", actualCost: 407000 },
- { month: "2026-03", category: "鐮傛祮", costType: "鑳借�楁垚鏈�", actualCost: 95800 },
- { month: "2026-03", category: "鐮傛祮", costType: "鐢熶骇鎴愭湰", actualCost: 265400 },
- { month: "2026-03", category: "鐡风爾", costType: "鑳借�楁垚鏈�", actualCost: 189800 },
- { month: "2026-03", category: "鐡风爾", costType: "鐢熶骇鎴愭湰", actualCost: 469900 },
-]);
+// ------------------------------
+// 鍋囨暟鎹細鐢ㄤ簬鍏堣仈璋冮〉闈㈡覆鏌�
+// ------------------------------
+const actualCostSource = ref([]);
+const standardCostSource = ref([]);
-const standardCostSource = ref([
- { month: "2026-01", category: "鐡风爾", costType: "鑳借�楁垚鏈�", standardCost: 176000 },
- { month: "2026-01", category: "鐡风爾", costType: "鐢熶骇鎴愭湰", standardCost: 452000 },
- { month: "2026-01", category: "姘存偿", costType: "鑳借�楁垚鏈�", standardCost: 136000 },
- { month: "2026-01", category: "姘存偿", costType: "鐢熶骇鎴愭湰", standardCost: 392000 },
- { month: "2026-02", category: "鐡风爾", costType: "鑳借�楁垚鏈�", standardCost: 186000 },
- { month: "2026-02", category: "鐡风爾", costType: "鐢熶骇鎴愭湰", standardCost: 458000 },
- { month: "2026-02", category: "姘存偿", costType: "鑳借�楁垚鏈�", standardCost: 139000 },
- { month: "2026-02", category: "姘存偿", costType: "鐢熶骇鎴愭湰", standardCost: 401000 },
- { month: "2026-03", category: "鐮傛祮", costType: "鑳借�楁垚鏈�", standardCost: 93000 },
- { month: "2026-03", category: "鐮傛祮", costType: "鐢熶骇鎴愭湰", standardCost: 259000 },
- { month: "2026-03", category: "鐡风爾", costType: "鑳借�楁垚鏈�", standardCost: 185000 },
- { month: "2026-03", category: "鐡风爾", costType: "鐢熶骇鎴愭湰", standardCost: 461000 },
-]);
+const fakeMonths = ["2026-01", "2026-02", "2026-03"];
+const fakeCategories = [
+ "绮夌叅鐏�",
+ "鐭崇伆",
+ "姘存偿",
+ "閾濈矇鑶�",
+ "鑴辨ā鍓�",
+ "鐭宠啅",
+ "鎵撳寘甯�",
+ "闃茶厫鍓傦紙鏉挎潗鐢級",
+ "姘у寲闀侊紙鏉挎潗鐢級",
+ "鍐锋尋涓濓紙鏉挎潗鐢級",
+ "鍗℃墸锛堟澘鏉愮敤锛�",
+ "鏉愭枡灏忚",
+ "姘�",
+ "鐢�",
+ "钂告苯",
+];
+
+const fakeCostType = (category) => (["姘�", "鐢�", "钂告苯"].includes(category) ? "鑳借�楁垚鏈�" : "鐢熶骇鎴愭湰");
+
+// 姣忎釜绫诲埆鐨勬爣鍑嗘垚鏈熀鍑嗗�硷紙浠呯敤浜庡亣鏁版嵁锛�
+const baseStandardCostByCategory = {
+ 绮夌叅鐏�: 98000,
+ 鐭崇伆: 52000,
+ 姘存偿: 175000,
+ 閾濈矇鑶�: 32000,
+ 鑴辨ā鍓�: 21000,
+ 鐭宠啅: 41000,
+ 鎵撳寘甯�: 14500,
+ "闃茶厫鍓傦紙鏉挎潗鐢級": 12500,
+ "姘у寲闀侊紙鏉挎潗鐢級": 22000,
+ "鍐锋尋涓濓紙鏉挎潗鐢級": 9800,
+ "鍗℃墸锛堟澘鏉愮敤锛�": 8600,
+ 鏉愭枡灏忚: 420000,
+ 姘�: 6800,
+ 鐢�: 26000,
+ 钂告苯: 52000,
+};
+
+// 鏈堜唤娉㈠姩绯绘暟锛堣鍥捐〃鐪嬭捣鏉ユ洿鈥滅湡瀹炩�濅竴浜涳級
+const monthFactorByMonth = {
+ "2026-01": 1.0,
+ "2026-02": 1.06,
+ "2026-03": 0.97,
+};
+
+// 瀹為檯鎴愭湰鐩稿鏍囧噯鎴愭湰鐨勫亸绉绘瘮渚嬶紙鐢ㄤ簬娴嬭瘯姝h礋宸紓灞曠ず锛�
+const diffRatioByCategory = {
+ 绮夌叅鐏�: 0.05,
+ 鐭崇伆: -0.01,
+ 姘存偿: 0.03,
+ 閾濈矇鑶�: 0.0,
+ 鑴辨ā鍓�: -0.04,
+ 鐭宠啅: 0.02,
+ 鎵撳寘甯�: -0.03,
+ "闃茶厫鍓傦紙鏉挎潗鐢級": 0.06,
+ "姘у寲闀侊紙鏉挎潗鐢級": -0.02,
+ "鍐锋尋涓濓紙鏉挎潗鐢級": 0.01,
+ "鍗℃墸锛堟澘鏉愮敤锛�": -0.05,
+ 鏉愭枡灏忚: 0.02,
+ 姘�: -0.01,
+ 鐢�: 0.04,
+ 钂告苯: -0.03,
+};
+
+const buildFakeSources = () => {
+ const stdRows = [];
+ const actRows = [];
+
+ for (const month of fakeMonths) {
+ const monthFactor = monthFactorByMonth[month] ?? 1;
+ const monthAdj = month === "2026-02" ? 0.005 : month === "2026-03" ? -0.006 : 0;
+
+ for (const category of fakeCategories) {
+ const costType = fakeCostType(category);
+ const base = baseStandardCostByCategory[category] ?? 0;
+ const standardCost = Math.round(base * monthFactor);
+ const diffRatio = (diffRatioByCategory[category] ?? 0) + monthAdj;
+ const actualCost = Math.round(standardCost * (1 + diffRatio));
+
+ stdRows.push({ month, category, costType, standardCost });
+ actRows.push({ month, category, costType, actualCost });
+ }
+ }
+
+ standardCostSource.value = stdRows;
+ actualCostSource.value = actRows;
+};
+
+buildFakeSources();
const categoryOptions = computed(() => {
const all = [...actualCostSource.value, ...standardCostSource.value];
@@ -295,9 +421,37 @@
size: 10,
});
+/** sortable="custom" 闇�鍦� sort-change 閲岃嚜琛屾帓搴忥紝鍐嶅垎椤� */
+const tableSort = reactive({
+ prop: "",
+ order: "",
+});
+
+const handleSortChange = ({ prop, order }) => {
+ tableSort.prop = prop || "";
+ tableSort.order = order || "";
+ page.current = 1;
+};
+
+const sortedTableData = computed(() => {
+ const rows = [...tableData.value];
+ if (!tableSort.prop || !tableSort.order) return rows;
+ const dir = tableSort.order === "ascending" ? 1 : -1;
+ const key = tableSort.prop;
+ rows.sort((a, b) => {
+ const na = Number(a[key]);
+ const nb = Number(b[key]);
+ const va = Number.isFinite(na) ? na : 0;
+ const vb = Number.isFinite(nb) ? nb : 0;
+ if (va === vb) return 0;
+ return va < vb ? -dir : dir;
+ });
+ return rows;
+});
+
const pagedTableData = computed(() => {
const start = (page.current - 1) * page.size;
- return tableData.value.slice(start, start + page.size);
+ return sortedTableData.value.slice(start, start + page.size);
});
const overview = computed(() => {
@@ -321,9 +475,23 @@
const buildChartOption = () => {
const { xAxis, standard, actual, diffRate } = getChartData();
return {
+ animation: true,
+ animationDuration: 920,
+ animationEasing: "cubicOut",
+ textStyle: { fontFamily: "inherit" },
tooltip: {
trigger: "axis",
- axisPointer: { type: "shadow" },
+ axisPointer: {
+ type: "cross",
+ crossStyle: { color: "rgba(47, 111, 237, 0.35)" },
+ lineStyle: { type: "dashed" },
+ },
+ backgroundColor: "rgba(255, 255, 255, 0.94)",
+ borderColor: "rgba(47, 111, 237, 0.22)",
+ borderWidth: 1,
+ padding: [12, 14],
+ textStyle: { color: "rgba(15, 23, 42, 0.88)" },
+ extraCssText: "box-shadow: 0 12px 40px rgba(15, 23, 42, 0.12); border-radius: 12px;",
formatter: (params) => {
const row = tableData.value[params[0]?.dataIndex] || {};
return [
@@ -335,25 +503,33 @@
].join("<br/>");
},
},
- legend: { data: ["鏍囧噯鎴愭湰", "瀹為檯鎴愭湰", "宸紓鐜�"] },
- grid: { left: "4%", right: "4%", top: "16%", bottom: "16%", containLabel: true },
+ legend: {
+ data: ["鏍囧噯鎴愭湰", "瀹為檯鎴愭湰", "宸紓鐜�"],
+ top: 6,
+ itemGap: 18,
+ textStyle: { color: "rgba(15, 23, 42, 0.72)" },
+ },
+ grid: { left: "3%", right: "3%", top: "18%", bottom: "14%", containLabel: true },
xAxis: {
type: "category",
data: xAxis,
- axisLabel: { color: "rgba(15, 23, 42, 0.62)" },
- axisLine: { lineStyle: { color: "rgba(15, 23, 42, 0.08)" } },
+ axisLabel: { color: "rgba(15, 23, 42, 0.62)", fontSize: 11 },
+ axisLine: { lineStyle: { color: "rgba(15, 23, 42, 0.1)" } },
+ axisTick: { show: false },
},
yAxis: [
{
type: "value",
name: "鎴愭湰(鍏�)",
- axisLabel: { color: "rgba(15, 23, 42, 0.58)" },
- splitLine: { lineStyle: { color: "rgba(15, 23, 42, 0.06)" } },
+ nameTextStyle: { color: "rgba(15, 23, 42, 0.5)", fontSize: 11 },
+ axisLabel: { color: "rgba(15, 23, 42, 0.55)" },
+ splitLine: { lineStyle: { color: "rgba(15, 23, 42, 0.06)", type: "dashed" } },
},
{
type: "value",
name: "宸紓鐜�(%)",
- axisLabel: { color: "rgba(15, 23, 42, 0.58)" },
+ nameTextStyle: { color: "rgba(15, 23, 42, 0.5)", fontSize: 11 },
+ axisLabel: { color: "rgba(15, 23, 42, 0.55)" },
splitLine: { show: false },
},
],
@@ -361,25 +537,62 @@
{
name: "鏍囧噯鎴愭湰",
type: "bar",
- barMaxWidth: 24,
+ barMaxWidth: 26,
data: standard,
- itemStyle: { color: "#5b8cff", borderRadius: [4, 4, 0, 0] },
+ itemStyle: {
+ borderRadius: [6, 6, 0, 0],
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: "#8eb4ff" },
+ { offset: 1, color: "#3d74f5" },
+ ]),
+ },
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 12,
+ shadowColor: "rgba(61, 116, 245, 0.45)",
+ },
+ },
},
{
name: "瀹為檯鎴愭湰",
type: "bar",
- barMaxWidth: 24,
+ barMaxWidth: 26,
data: actual,
- itemStyle: { color: "#f59e0b", borderRadius: [4, 4, 0, 0] },
+ itemStyle: {
+ borderRadius: [6, 6, 0, 0],
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: "#fcd34d" },
+ { offset: 1, color: "#ea580c" },
+ ]),
+ },
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 12,
+ shadowColor: "rgba(234, 88, 12, 0.4)",
+ },
+ },
},
{
name: "宸紓鐜�",
type: "line",
yAxisIndex: 1,
smooth: true,
+ symbol: "circle",
+ symbolSize: 7,
+ showSymbol: true,
data: diffRate,
- itemStyle: { color: "#ef4444" },
- lineStyle: { width: 2 },
+ lineStyle: { width: 3, shadowBlur: 8, shadowColor: "rgba(239, 68, 68, 0.35)" },
+ itemStyle: {
+ color: "#ef4444",
+ borderColor: "#fff",
+ borderWidth: 2,
+ },
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: "rgba(239, 68, 68, 0.28)" },
+ { offset: 1, color: "rgba(239, 68, 68, 0.02)" },
+ ]),
+ },
},
],
};
@@ -469,10 +682,11 @@
const downloadTemplate = () => {
const sample = [
- { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "鐡风爾", 鎴愭湰绫诲瀷: "鏍囧噯鑳借�楁垚鏈�", 鏍囧噯鎴愭湰: 185000 },
- { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "鐡风爾", 鎴愭湰绫诲瀷: "鏍囧噯鐢熶骇鎴愭湰", 鏍囧噯鎴愭湰: 461000 },
- { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "姘存偿", 鎴愭湰绫诲瀷: "鏍囧噯鑳借�楁垚鏈�", 鏍囧噯鎴愭湰: 140000 },
- { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "姘存偿", 鎴愭湰绫诲瀷: "鏍囧噯鐢熶骇鎴愭湰", 鏍囧噯鎴愭湰: 405000 },
+ { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "绮夌叅鐏�", 鎴愭湰绫诲瀷: "鏍囧噯鐢熶骇鎴愭湰", 鏍囧噯鎴愭湰: 98000 },
+ { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "姘存偿", 鎴愭湰绫诲瀷: "鏍囧噯鐢熶骇鎴愭湰", 鏍囧噯鎴愭湰: 175000 },
+ { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "鐢�", 鎴愭湰绫诲瀷: "鏍囧噯鑳借�楁垚鏈�", 鏍囧噯鎴愭湰: 26000 },
+ { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "钂告苯", 鎴愭湰绫诲瀷: "鏍囧噯鑳借�楁垚鏈�", 鏍囧噯鎴愭湰: 52000 },
+ { 鏈堜唤: "2026-03", 浜у搧绫诲埆: "姘�", 鎴愭湰绫诲瀷: "鏍囧噯鑳借�楁垚鏈�", 鏍囧噯鎴愭湰: 6800 },
];
const ws = XLSX.utils.json_to_sheet(sample);
const wb = XLSX.utils.book_new();
@@ -489,6 +703,8 @@
searchForm.monthRange = getDefaultMonthRange();
searchForm.category = "";
searchForm.costType = "";
+ tableSort.prop = "";
+ tableSort.order = "";
page.current = 1;
handleQuery();
};
@@ -549,6 +765,8 @@
} else {
updateChart();
}
+ // 寮圭獥鍑虹幇鍚庡鍣ㄥ昂瀵镐細鍙樺寲锛屽己鍒� resize 闃叉 canvas 婧㈠嚭閬尅琛ㄥご/鍏抽棴鎸夐挳
+ largeChartInstance?.resize?.();
});
};
@@ -602,26 +820,138 @@
</script>
<style scoped lang="scss">
+@keyframes mesh-shift {
+ 0%,
+ 100% {
+ opacity: 1;
+ transform: scale(1) translate(0, 0);
+ }
+ 50% {
+ opacity: 0.85;
+ transform: scale(1.02) translate(-1%, 1%);
+ }
+}
+
+@keyframes orb-float {
+ 0%,
+ 100% {
+ transform: translate(0, 0) scale(1);
+ }
+ 33% {
+ transform: translate(12px, -18px) scale(1.05);
+ }
+ 66% {
+ transform: translate(-8px, 10px) scale(0.98);
+ }
+}
+
+@keyframes shimmer {
+ 0% {
+ background-position: 200% center;
+ }
+ 100% {
+ background-position: -200% center;
+ }
+}
+
.std-cost-page {
- --lux-bg: #f6f7fb;
- --lux-card: rgba(255, 255, 255, 0.86);
- --lux-border: rgba(15, 23, 42, 0.08);
+ --lux-bg: #eef1f8;
+ --lux-card: rgba(255, 255, 255, 0.72);
+ --lux-border: rgba(15, 23, 42, 0.1);
--lux-text: rgba(15, 23, 42, 0.92);
--lux-subtle: rgba(15, 23, 42, 0.58);
--lux-primary: #2f6fed;
--lux-success: #16a34a;
--lux-warning: #f59e0b;
--lux-danger: #ef4444;
- --lux-shadow-soft: 0 10px 28px rgba(15, 23, 42, 0.06);
- --lux-radius: 14px;
+ --lux-shadow-soft: 0 12px 40px rgba(15, 23, 42, 0.08);
+ --lux-shadow-lift: 0 20px 50px rgba(47, 111, 237, 0.12);
+ --lux-radius: 16px;
- padding: 18px 22px 24px;
- background: radial-gradient(
- 1200px 420px at 20% 0%,
- rgba(47, 111, 237, 0.1),
- transparent 55%
- ),
- linear-gradient(180deg, var(--lux-bg) 0%, #ffffff 58%);
+ position: relative;
+ min-height: 100%;
+ padding: 20px 22px 28px;
+ overflow: hidden;
+ background: linear-gradient(165deg, #e8ecf7 0%, #f4f6fb 42%, #fafbfd 100%);
+}
+
+.page-bg {
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+}
+
+.bg-mesh {
+ position: absolute;
+ inset: -20%;
+ background:
+ radial-gradient(ellipse 80% 50% at 15% 0%, rgba(47, 111, 237, 0.18), transparent 50%),
+ radial-gradient(ellipse 60% 45% at 85% 15%, rgba(245, 158, 11, 0.12), transparent 45%),
+ radial-gradient(ellipse 50% 40% at 50% 100%, rgba(22, 163, 74, 0.08), transparent 50%);
+ animation: mesh-shift 14s ease-in-out infinite;
+}
+
+.bg-orb {
+ position: absolute;
+ border-radius: 50%;
+ filter: blur(60px);
+ opacity: 0.55;
+ animation: orb-float 18s ease-in-out infinite;
+}
+
+.bg-orb--a {
+ width: 320px;
+ height: 320px;
+ top: -80px;
+ right: 5%;
+ background: rgba(47, 111, 237, 0.35);
+}
+
+.bg-orb--b {
+ width: 260px;
+ height: 260px;
+ bottom: 10%;
+ left: -40px;
+ background: rgba(99, 102, 241, 0.28);
+ animation-delay: -6s;
+}
+
+.bg-orb--c {
+ width: 200px;
+ height: 200px;
+ top: 40%;
+ right: 25%;
+ background: rgba(245, 158, 11, 0.22);
+ animation-delay: -12s;
+}
+
+.bg-grid {
+ position: absolute;
+ inset: 0;
+ opacity: 0.35;
+ background-image:
+ linear-gradient(rgba(15, 23, 42, 0.04) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(15, 23, 42, 0.04) 1px, transparent 1px);
+ background-size: 48px 48px;
+ mask-image: radial-gradient(ellipse 90% 70% at 50% 30%, black 20%, transparent 75%);
+}
+
+.page-inner {
+ position: relative;
+ z-index: 1;
+}
+
+.glass-card {
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+ border: 1px solid rgba(255, 255, 255, 0.65);
+ box-shadow: var(--lux-shadow-soft), inset 0 1px 0 rgba(255, 255, 255, 0.85);
+ transition: box-shadow 0.35s ease, transform 0.35s ease;
+}
+
+.glass-card:hover {
+ box-shadow: var(--lux-shadow-lift), inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.filter-card,
@@ -630,13 +960,69 @@
border-radius: var(--lux-radius);
border-color: var(--lux-border);
background: var(--lux-card);
- box-shadow: var(--lux-shadow-soft);
}
.filter-card,
.panel-card,
.table-card {
- margin-bottom: 14px;
+ margin-bottom: 16px;
+}
+
+.title-badge {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 44px;
+ height: 44px;
+ border-radius: 14px;
+ background: linear-gradient(145deg, rgba(47, 111, 237, 0.2), rgba(47, 111, 237, 0.06));
+ box-shadow: 0 8px 24px rgba(47, 111, 237, 0.15);
+}
+
+.card-icon {
+ font-size: 22px;
+ color: var(--lux-primary);
+}
+
+.title-block {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.title-row {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.shimmer-text {
+ background: linear-gradient(
+ 90deg,
+ var(--lux-text) 0%,
+ var(--lux-text) 40%,
+ rgba(47, 111, 237, 0.95) 50%,
+ var(--lux-text) 60%,
+ var(--lux-text) 100%
+ );
+ background-size: 200% auto;
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ animation: shimmer 5s linear infinite;
+}
+
+.live-pill {
+ font-size: 11px;
+ font-weight: 650;
+ letter-spacing: 0.04em;
+ padding: 4px 10px;
+ border-radius: 999px;
+ color: #0d47a1;
+ background: linear-gradient(135deg, rgba(47, 111, 237, 0.18), rgba(47, 111, 237, 0.06));
+ border: 1px solid rgba(47, 111, 237, 0.22);
+ box-shadow: 0 2px 10px rgba(47, 111, 237, 0.12);
}
.filter-layout {
@@ -656,14 +1042,40 @@
margin: 0;
}
+.filter-form :deep(.el-input__wrapper),
+.filter-form :deep(.el-select .el-input__wrapper) {
+ border-radius: 10px;
+ box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
+ transition: box-shadow 0.2s ease;
+}
+
+.filter-form :deep(.el-input__wrapper:hover) {
+ box-shadow: 0 4px 14px rgba(47, 111, 237, 0.1);
+}
+
.filter-actions {
display: flex;
justify-content: flex-end;
align-items: center;
flex-wrap: wrap;
gap: 10px 14px;
- padding-top: 10px;
- border-top: 1px dashed rgba(15, 23, 42, 0.1);
+ padding-top: 12px;
+ border-top: 1px dashed rgba(15, 23, 42, 0.12);
+}
+
+.filter-actions :deep(.lux-btn) {
+ border-radius: 10px;
+ font-weight: 600;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.filter-actions :deep(.lux-btn:hover) {
+ transform: translateY(-1px);
+ box-shadow: 0 8px 20px rgba(47, 111, 237, 0.18);
+}
+
+.filter-actions :deep(.el-button--success.is-plain) {
+ border-color: rgba(22, 163, 74, 0.35);
}
.action-group {
@@ -688,22 +1100,59 @@
display: flex;
align-items: center;
justify-content: space-between;
- gap: 10px;
+ gap: 12px;
+ flex-wrap: wrap;
}
.card-head-left {
display: flex;
align-items: center;
- gap: 8px;
+ gap: 14px;
}
-.card-icon {
+.panel-head-main {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.panel-accent {
+ width: 4px;
+ height: 22px;
+ border-radius: 4px;
+ background: linear-gradient(180deg, #5b8cff, #2f6fed);
+ box-shadow: 0 2px 8px rgba(47, 111, 237, 0.35);
+}
+
+.panel-accent--emerald {
+ background: linear-gradient(180deg, #34d399, #059669);
+ box-shadow: 0 2px 8px rgba(5, 150, 105, 0.35);
+}
+
+.chart-tag {
+ margin-left: 10px;
+ font-size: 11px;
+ font-weight: 650;
+ color: rgba(15, 23, 42, 0.55);
+ padding: 2px 8px;
+ border-radius: 6px;
+ background: rgba(15, 23, 42, 0.05);
+}
+
+.count-chip {
+ font-size: 12px;
+ font-weight: 650;
color: var(--lux-primary);
+ padding: 6px 12px;
+ border-radius: 999px;
+ background: rgba(47, 111, 237, 0.1);
+ border: 1px solid rgba(47, 111, 237, 0.15);
}
.card-title {
font-weight: 760;
color: var(--lux-text);
+ letter-spacing: -0.02em;
}
.subtle {
@@ -711,44 +1160,115 @@
font-size: 12px;
}
+.kpi-card {
+ padding: 2px;
+}
+
.kpi-strip {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 12px;
+ gap: 14px;
}
.kpi-item {
- padding: 12px 14px;
- border-radius: 12px;
- border: 1px solid rgba(15, 23, 42, 0.08);
+ position: relative;
+ padding: 16px 16px 14px;
+ border-radius: 14px;
+ border: 1px solid rgba(255, 255, 255, 0.7);
+ overflow: hidden;
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease;
+}
+
+.kpi-item:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 16px 36px rgba(15, 23, 42, 0.1);
+}
+
+.kpi-glow {
+ position: absolute;
+ right: -20%;
+ bottom: -40%;
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ filter: blur(40px);
+ opacity: 0.45;
+ pointer-events: none;
+}
+
+.kpi-std .kpi-glow {
+ background: rgba(47, 111, 237, 0.55);
+}
+
+.kpi-act .kpi-glow {
+ background: rgba(245, 158, 11, 0.5);
+}
+
+.kpi-diff .kpi-glow {
+ background: rgba(239, 68, 68, 0.45);
+}
+
+.kpi-rate .kpi-glow {
+ background: rgba(22, 163, 74, 0.5);
+}
+
+.kpi-top {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+}
+
+.kpi-ico {
+ font-size: 18px;
+ opacity: 0.88;
}
.kpi-std {
- background: linear-gradient(135deg, rgba(47, 111, 237, 0.1), rgba(255, 255, 255, 0.86));
+ background: linear-gradient(145deg, rgba(47, 111, 237, 0.14), rgba(255, 255, 255, 0.92));
+}
+
+.kpi-std .kpi-ico {
+ color: #2563eb;
}
.kpi-act {
- background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), rgba(255, 255, 255, 0.86));
+ background: linear-gradient(145deg, rgba(245, 158, 11, 0.16), rgba(255, 255, 255, 0.92));
+}
+
+.kpi-act .kpi-ico {
+ color: #d97706;
}
.kpi-diff {
- background: linear-gradient(135deg, rgba(239, 68, 68, 0.08), rgba(255, 255, 255, 0.86));
+ background: linear-gradient(145deg, rgba(239, 68, 68, 0.12), rgba(255, 255, 255, 0.92));
+}
+
+.kpi-diff .kpi-ico {
+ color: #dc2626;
}
.kpi-rate {
- background: linear-gradient(135deg, rgba(22, 163, 74, 0.1), rgba(255, 255, 255, 0.86));
+ background: linear-gradient(145deg, rgba(22, 163, 74, 0.12), rgba(255, 255, 255, 0.92));
+}
+
+.kpi-rate .kpi-ico {
+ color: #059669;
}
.kpi-label {
font-size: 12px;
color: var(--lux-subtle);
+ font-weight: 600;
}
.kpi-value {
- margin-top: 6px;
+ position: relative;
+ z-index: 1;
font-size: 22px;
font-weight: 780;
color: var(--lux-text);
+ font-variant-numeric: tabular-nums;
}
.cost-value {
@@ -761,15 +1281,21 @@
font-weight: 700;
}
+.chart-section :deep(.el-card__header) {
+ border-bottom: 1px solid rgba(15, 23, 42, 0.06);
+}
+
.chart-wrap {
position: relative;
- padding-top: 34px;
- border-radius: 12px;
+ padding-top: 40px;
+ border-radius: 14px;
overflow: hidden;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.5) 0%, rgba(248, 250, 252, 0.95) 100%);
+ border: 1px solid rgba(15, 23, 42, 0.06);
}
.chart-content {
- height: 360px;
+ height: 380px;
}
.chart-tools {
@@ -780,39 +1306,70 @@
.chart-tools-inline {
position: absolute;
- top: 4px;
- right: 6px;
+ top: 6px;
+ right: 8px;
z-index: 2;
}
.chart-tool {
- font-size: 11px;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 12px;
font-weight: 650;
line-height: 1;
- padding: 6px 10px;
+ padding: 8px 12px;
border-radius: 10px;
border: 1px solid rgba(15, 23, 42, 0.1);
- background: rgba(255, 255, 255, 0.78);
- color: rgba(15, 23, 42, 0.72);
+ background: rgba(255, 255, 255, 0.88);
+ color: rgba(15, 23, 42, 0.75);
cursor: pointer;
- transition: background-color 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
+ transition:
+ background-color 0.2s ease,
+ border-color 0.2s ease,
+ transform 0.2s ease,
+ box-shadow 0.2s ease;
+}
+
+.chart-tool--primary {
+ border-color: rgba(47, 111, 237, 0.28);
+ background: linear-gradient(135deg, rgba(47, 111, 237, 0.12), rgba(255, 255, 255, 0.95));
+ color: #1e40af;
}
.chart-tool:hover {
- background: rgba(47, 111, 237, 0.08);
- border-color: rgba(47, 111, 237, 0.22);
- transform: translateY(-1px);
+ background: rgba(47, 111, 237, 0.1);
+ border-color: rgba(47, 111, 237, 0.28);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(47, 111, 237, 0.12);
}
.large-chart-content {
height: 70vh;
min-height: 520px;
+ width: 100%;
+ overflow: hidden; // 闃叉 ECharts 鐢诲竷婧㈠嚭閬尅寮圭獥鏍囬鏍�
+}
+
+.std-cost-page :deep(.el-dialog__header) {
+ position: relative;
+ z-index: 3;
+}
+
+.std-cost-page :deep(.el-dialog__headerbtn) {
+ position: relative;
+ z-index: 4;
+}
+
+.std-cost-page :deep(.el-dialog__body) {
+ position: relative;
+ z-index: 1;
}
.pagination-container {
display: flex;
justify-content: flex-end;
- padding-top: 12px;
+ padding-top: 14px;
}
.w-260 {
@@ -826,14 +1383,21 @@
::deep(.lux-table) {
border-radius: 12px;
overflow: hidden;
+ --el-table-border-color: rgba(15, 23, 42, 0.06);
}
::deep(.lux-table th.el-table__cell) {
- background: rgba(15, 23, 42, 0.03);
+ background: linear-gradient(180deg, rgba(15, 23, 42, 0.04), rgba(15, 23, 42, 0.02));
+ font-weight: 700;
+ color: rgba(15, 23, 42, 0.75);
+}
+
+::deep(.lux-table .el-table__row) {
+ transition: background-color 0.2s ease;
}
::deep(.lux-table .el-table__row:hover > td.el-table__cell) {
- background-color: rgba(47, 111, 237, 0.06) !important;
+ background-color: rgba(47, 111, 237, 0.07) !important;
}
@media (max-width: 1100px) {
@@ -845,5 +1409,32 @@
justify-content: flex-start;
padding-top: 8px;
}
+
+ .shimmer-text {
+ animation: none;
+ color: var(--lux-text);
+ background: none;
+ -webkit-background-clip: unset;
+ background-clip: unset;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .bg-mesh,
+ .bg-orb,
+ .shimmer-text {
+ animation: none;
+ }
+
+ .shimmer-text {
+ color: var(--lux-text);
+ background: none;
+ -webkit-background-clip: unset;
+ background-clip: unset;
+ }
+
+ .kpi-item:hover {
+ transform: none;
+ }
}
</style>
\ No newline at end of file
--
Gitblit v1.9.3