From 132c4132360f9dc103e4c9f5bbc8cc36d1429e43 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期一, 23 三月 2026 11:17:48 +0800
Subject: [PATCH] 生产成本核算
---
src/views/costAccounting/productionCostAccounting/index.vue | 767 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 767 insertions(+), 0 deletions(-)
diff --git a/src/views/costAccounting/productionCostAccounting/index.vue b/src/views/costAccounting/productionCostAccounting/index.vue
new file mode 100644
index 0000000..78ca6c5
--- /dev/null
+++ b/src/views/costAccounting/productionCostAccounting/index.vue
@@ -0,0 +1,767 @@
+<template>
+ <div class="production-cost-page">
+ <el-card class="filter-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>
+ <div class="card-head-right">
+ <el-radio-group
+ v-model="statisticsType"
+ size="small"
+ @change="handleTypeChange"
+ >
+ <el-radio-button label="day">鎸夋棩</el-radio-button>
+ <el-radio-button label="month">鎸夋湀</el-radio-button>
+ </el-radio-group>
+ </div>
+ </div>
+ </template>
+
+ <div class="filter-layout">
+ <el-form :model="searchForm" :inline="true" class="filter-form">
+ <el-form-item label="鏃堕棿鑼冨洿">
+ <el-date-picker
+ v-if="statisticsType === 'day'"
+ v-model="searchForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ class="w-260"
+ @change="handleQuery"
+ />
+ <el-date-picker
+ v-else
+ v-model="searchForm.monthRange"
+ type="monthrange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫湀浠�"
+ end-placeholder="缁撴潫鏈堜唤"
+ value-format="YYYY-MM"
+ class="w-260"
+ @change="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="浜у搧绫诲埆">
+ <el-select
+ v-model="searchForm.category"
+ clearable
+ filterable
+ placeholder="鍏ㄩ儴绫诲埆"
+ class="w-180"
+ @change="handleQuery"
+ >
+ <el-option
+ v-for="item in categoryOptions"
+ :key="item"
+ :label="item"
+ :value="item"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐢熶骇璁㈠崟">
+ <el-select
+ v-model="searchForm.orderNo"
+ clearable
+ filterable
+ placeholder="鍏ㄩ儴璁㈠崟"
+ class="w-180"
+ @change="handleQuery"
+ >
+ <el-option
+ v-for="item in orderOptions"
+ :key="item"
+ :label="item"
+ :value="item"
+ />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <div class="filter-actions">
+ <el-button class="lux-btn" type="primary" @click="handleQuery">
+ 鍒锋柊
+ </el-button>
+ <el-button class="lux-btn" @click="handleReset">閲嶇疆</el-button>
+ <el-button class="lux-btn" type="success" plain @click="handleExport">
+ 瀵煎嚭
+ </el-button>
+ </div>
+ </div>
+ </el-card>
+
+ <el-card class="panel-card" shadow="never">
+ <div class="kpi-strip">
+ <div class="kpi-item kpi-total">
+ <div class="kpi-label">鎬荤敓浜ф垚鏈�</div>
+ <div class="kpi-value">楼{{ formatMoney(overview.totalCost) }}</div>
+ </div>
+ <div class="kpi-item kpi-raw">
+ <div class="kpi-label">鍘熸枡鎴愭湰</div>
+ <div class="kpi-value">楼{{ formatMoney(overview.rawCost) }}</div>
+ </div>
+ <div class="kpi-item kpi-aux">
+ <div class="kpi-label">杈呮枡鎴愭湰</div>
+ <div class="kpi-value">楼{{ formatMoney(overview.auxCost) }}</div>
+ </div>
+ <div class="kpi-item kpi-order">
+ <div class="kpi-label">璁㈠崟鏁伴噺</div>
+ <div class="kpi-value">{{ overview.orderCount }}</div>
+ </div>
+ </div>
+ </el-card>
+
+ <el-row :gutter="14" class="summary-row">
+ <el-col :span="12">
+ <el-card class="table-card" shadow="never">
+ <template #header>
+ <div class="panel-head">
+ <span class="card-title">鎸変骇鍝佺被鍒眹鎬�</span>
+ </div>
+ </template>
+ <el-table :data="categorySummary" stripe class="lux-table" height="260">
+ <el-table-column prop="category" label="浜у搧绫诲埆" min-width="140" />
+ <el-table-column prop="rawCost" label="鍘熸枡鎴愭湰(鍏�)" align="right">
+ <template #default="scope">
+ <span class="price-value">{{ formatMoney(scope.row.rawCost) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="auxCost" label="杈呮枡鎴愭湰(鍏�)" align="right">
+ <template #default="scope">
+ <span class="price-value">{{ formatMoney(scope.row.auxCost) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="totalCost" label="鎬绘垚鏈�(鍏�)" align="right">
+ <template #default="scope">
+ <span class="cost-value">楼{{ formatMoney(scope.row.totalCost) }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-col>
+ <el-col :span="12">
+ <el-card class="table-card" shadow="never">
+ <template #header>
+ <div class="panel-head">
+ <span class="card-title">鎸夌敓浜ц鍗曟眹鎬�</span>
+ </div>
+ </template>
+ <el-table :data="orderSummary" stripe class="lux-table" height="260">
+ <el-table-column prop="orderNo" label="鐢熶骇璁㈠崟" min-width="150" />
+ <el-table-column prop="category" label="浜у搧绫诲埆" min-width="120" />
+ <el-table-column prop="totalCost" label="鎬绘垚鏈�(鍏�)" align="right">
+ <template #default="scope">
+ <span class="cost-value">楼{{ formatMoney(scope.row.totalCost) }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card class="table-card" shadow="never">
+ <template #header>
+ <div class="panel-head">
+ <span class="card-title">澶氱淮搴︽眹鎬绘槑缁�</span>
+ <span class="subtle">{{ timeColumnLabel }} + 浜у搧绫诲埆 + 鐢熶骇璁㈠崟</span>
+ </div>
+ </template>
+
+ <el-table :data="pagedTableData" stripe class="lux-table">
+ <el-table-column prop="timeLabel" :label="timeColumnLabel" min-width="110" />
+ <el-table-column prop="category" label="浜у搧绫诲埆" min-width="120" />
+ <el-table-column prop="orderNo" label="鐢熶骇璁㈠崟" min-width="150" />
+ <el-table-column prop="rawCost" label="鍘熸枡鎴愭湰(鍏�)" align="right">
+ <template #default="scope">
+ <span class="price-value">{{ formatMoney(scope.row.rawCost) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="auxCost" label="杈呮枡鎴愭湰(鍏�)" align="right">
+ <template #default="scope">
+ <span class="price-value">{{ formatMoney(scope.row.auxCost) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="totalCost" label="鎬绘垚鏈�(鍏�)" align="right">
+ <template #default="scope">
+ <span class="cost-value">楼{{ formatMoney(scope.row.totalCost) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎷嗗垎鏄庣粏" width="92" fixed="right">
+ <template #default="scope">
+ <el-button link type="primary" @click="openDetail(scope.row)">鏌ョ湅</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div class="pagination-container">
+ <el-pagination
+ v-model:current-page="page.current"
+ v-model:page-size="page.size"
+ :page-sizes="[10, 20, 50, 100]"
+ :total="tableData.length"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </el-card>
+
+ <el-drawer
+ v-model="detailVisible"
+ title="鐢熶骇鎴愭湰鎷嗗垎鏄庣粏"
+ size="560px"
+ destroy-on-close
+ >
+ <div v-if="detailRow" class="drawer-head">
+ <div><b>{{ timeColumnLabel }}锛�</b>{{ detailRow.timeLabel }}</div>
+ <div><b>浜у搧绫诲埆锛�</b>{{ detailRow.category }}</div>
+ <div><b>鐢熶骇璁㈠崟锛�</b>{{ detailRow.orderNo }}</div>
+ </div>
+ <el-table :data="detailMaterials" class="lux-table" stripe>
+ <el-table-column prop="materialName" label="鐗╂枡鍚嶇О" min-width="120" />
+ <el-table-column prop="materialType" label="绫诲瀷" width="80" />
+ <el-table-column prop="quantity" label="鎶曞叆閲�" align="right">
+ <template #default="scope">
+ {{ formatNumber(scope.row.quantity, 2) }} {{ scope.row.unit }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitPrice" label="鍗曚环(鍏�)" align="right">
+ <template #default="scope">
+ {{ formatNumber(scope.row.unitPrice, 2) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="cost" label="鎴愭湰(鍏�)" align="right">
+ <template #default="scope">
+ <span class="cost-value">楼{{ formatMoney(scope.row.cost) }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div class="drawer-foot">
+ <span>鍘熸枡锛毬{ formatMoney(detailRawCost) }}</span>
+ <span>杈呮枡锛毬{ formatMoney(detailAuxCost) }}</span>
+ <span class="strong">鍚堣锛毬{ formatMoney(detailTotalCost) }}</span>
+ </div>
+ </el-drawer>
+ </div>
+</template>
+
+<script setup>
+import { computed, reactive, ref, watch } from "vue";
+import { DataLine } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+
+const statisticsType = ref("day");
+
+const getDefaultDateRange = () => {
+ const end = new Date();
+ const start = new Date();
+ start.setDate(start.getDate() - 6);
+ return [start.toISOString().slice(0, 10), end.toISOString().slice(0, 10)];
+};
+
+const getDefaultMonthRange = () => {
+ const end = new Date();
+ const start = new Date();
+ start.setMonth(start.getMonth() - 2);
+ return [start.toISOString().slice(0, 7), end.toISOString().slice(0, 7)];
+};
+
+const searchForm = reactive({
+ dateRange: getDefaultDateRange(),
+ monthRange: getDefaultMonthRange(),
+ category: "",
+ orderNo: "",
+});
+
+const sourceRecords = ref([
+ { date: "2026-03-17", category: "鐡风爾", orderNo: "PO-260317-01", materialName: "闄剁摲绮�", materialType: "鍘熸枡", quantity: 1200, unit: "kg", unitPrice: 2.8 },
+ { date: "2026-03-17", category: "鐡风爾", orderNo: "PO-260317-01", materialName: "閲夋枡", materialType: "杈呮枡", quantity: 180, unit: "kg", unitPrice: 8.6 },
+ { date: "2026-03-17", category: "姘存偿", orderNo: "PO-260317-02", materialName: "鐔熸枡", materialType: "鍘熸枡", quantity: 2200, unit: "kg", unitPrice: 1.36 },
+ { date: "2026-03-17", category: "姘存偿", orderNo: "PO-260317-02", materialName: "鐭宠啅", materialType: "杈呮枡", quantity: 260, unit: "kg", unitPrice: 0.92 },
+ { date: "2026-03-18", category: "鐮傛祮", orderNo: "PO-260318-01", materialName: "鏈哄埗鐮�", materialType: "鍘熸枡", quantity: 1600, unit: "kg", unitPrice: 0.58 },
+ { date: "2026-03-18", category: "鐮傛祮", orderNo: "PO-260318-01", materialName: "淇濇按鍓�", materialType: "杈呮枡", quantity: 65, unit: "kg", unitPrice: 11.4 },
+ { date: "2026-03-19", category: "鐡风爾", orderNo: "PO-260319-01", materialName: "闄剁摲绮�", materialType: "鍘熸枡", quantity: 980, unit: "kg", unitPrice: 2.9 },
+ { date: "2026-03-19", category: "鐡风爾", orderNo: "PO-260319-01", materialName: "鑹叉枡", materialType: "杈呮枡", quantity: 42, unit: "kg", unitPrice: 15.8 },
+ { date: "2026-03-19", category: "鐮傛祮", orderNo: "PO-260319-03", materialName: "鏈哄埗鐮�", materialType: "鍘熸枡", quantity: 1400, unit: "kg", unitPrice: 0.56 },
+ { date: "2026-03-19", category: "鐮傛祮", orderNo: "PO-260319-03", materialName: "鍑忔按鍓�", materialType: "杈呮枡", quantity: 74, unit: "kg", unitPrice: 7.2 },
+ { date: "2026-03-20", category: "姘存偿", orderNo: "PO-260320-02", materialName: "鐔熸枡", materialType: "鍘熸枡", quantity: 2400, unit: "kg", unitPrice: 1.33 },
+ { date: "2026-03-20", category: "姘存偿", orderNo: "PO-260320-02", materialName: "鐭跨矇", materialType: "杈呮枡", quantity: 380, unit: "kg", unitPrice: 1.08 },
+]);
+
+const normalizedRecords = computed(() =>
+ sourceRecords.value.map((item) => {
+ const month = item.date.slice(0, 7);
+ const cost = Number(item.quantity) * Number(item.unitPrice);
+ return { ...item, month, cost };
+ })
+);
+
+const categoryOptions = computed(() =>
+ Array.from(new Set(normalizedRecords.value.map((item) => item.category)))
+);
+
+const orderOptions = computed(() =>
+ Array.from(new Set(normalizedRecords.value.map((item) => item.orderNo)))
+);
+
+const inRange = (value, range) => {
+ if (!Array.isArray(range) || range.length !== 2 || !range[0] || !range[1]) return true;
+ return value >= range[0] && value <= range[1];
+};
+
+const getMonthRangeDays = (monthRange) => {
+ if (!Array.isArray(monthRange) || monthRange.length !== 2 || !monthRange[0] || !monthRange[1]) {
+ return 0;
+ }
+ const [startMonth, endMonth] = monthRange;
+ const startDate = new Date(`${startMonth}-01T00:00:00`);
+ const endDate = new Date(`${endMonth}-01T00:00:00`);
+ if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()) || startDate > endDate) {
+ return 0;
+ }
+ const endMonthLastDay = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0);
+ const diffMs = endMonthLastDay.getTime() - startDate.getTime();
+ return Math.floor(diffMs / (24 * 60 * 60 * 1000)) + 1;
+};
+
+const buildQueryParams = () => {
+ const isDay = statisticsType.value === "day";
+ const params = {
+ statisticsType: statisticsType.value,
+ category: searchForm.category || undefined,
+ orderNo: searchForm.orderNo || undefined,
+ };
+
+ if (isDay) {
+ const [startDate, endDate] = searchForm.dateRange || [];
+ params.startDate = startDate;
+ params.endDate = endDate;
+ } else {
+ const [startMonth, endMonth] = searchForm.monthRange || [];
+ params.startMonth = startMonth;
+ params.endMonth = endMonth;
+ params.days = getMonthRangeDays(searchForm.monthRange);
+ }
+
+ return params;
+};
+
+const filteredRecords = computed(() =>
+ normalizedRecords.value.filter((item) => {
+ const hitTime =
+ statisticsType.value === "day"
+ ? inRange(item.date, searchForm.dateRange)
+ : inRange(item.month, searchForm.monthRange);
+ const hitCategory = !searchForm.category || item.category === searchForm.category;
+ const hitOrder = !searchForm.orderNo || item.orderNo === searchForm.orderNo;
+ return hitTime && hitCategory && hitOrder;
+ })
+);
+
+const timeColumnLabel = computed(() => (statisticsType.value === "day" ? "鏃ユ湡" : "鏈堜唤"));
+
+const aggregateBy = (list, keyFn) => {
+ const map = new Map();
+ for (const item of list) {
+ const key = keyFn(item);
+ if (!map.has(key)) {
+ map.set(key, {
+ rawCost: 0,
+ auxCost: 0,
+ totalCost: 0,
+ materials: [],
+ });
+ }
+ const bucket = map.get(key);
+ if (item.materialType === "鍘熸枡") bucket.rawCost += item.cost;
+ if (item.materialType === "杈呮枡") bucket.auxCost += item.cost;
+ bucket.totalCost += item.cost;
+ bucket.materials.push(item);
+ }
+ return map;
+};
+
+const groupedMap = computed(() =>
+ aggregateBy(filteredRecords.value, (item) => {
+ const timeKey = statisticsType.value === "day" ? item.date : item.month;
+ return `${timeKey}__${item.category}__${item.orderNo}`;
+ })
+);
+
+const tableData = computed(() => {
+ const rows = [];
+ for (const [key, val] of groupedMap.value) {
+ const [timeLabel, category, orderNo] = key.split("__");
+ rows.push({
+ key,
+ timeLabel,
+ category,
+ orderNo,
+ rawCost: val.rawCost,
+ auxCost: val.auxCost,
+ totalCost: val.totalCost,
+ materials: val.materials,
+ });
+ }
+ return rows.sort((a, b) => (a.timeLabel > b.timeLabel ? -1 : 1));
+});
+
+const page = reactive({
+ current: 1,
+ size: 10,
+});
+
+const pagedTableData = computed(() => {
+ const start = (page.current - 1) * page.size;
+ return tableData.value.slice(start, start + page.size);
+});
+
+const categorySummary = computed(() => {
+ const map = aggregateBy(filteredRecords.value, (item) => item.category);
+ const rows = [];
+ for (const [category, val] of map) {
+ rows.push({
+ category,
+ rawCost: val.rawCost,
+ auxCost: val.auxCost,
+ totalCost: val.totalCost,
+ });
+ }
+ return rows.sort((a, b) => b.totalCost - a.totalCost);
+});
+
+const orderSummary = computed(() => {
+ const map = aggregateBy(filteredRecords.value, (item) => item.orderNo);
+ const rows = [];
+ for (const [orderNo, val] of map) {
+ rows.push({
+ orderNo,
+ category: val.materials[0]?.category || "-",
+ totalCost: val.totalCost,
+ });
+ }
+ return rows.sort((a, b) => b.totalCost - a.totalCost);
+});
+
+const overview = computed(() => {
+ const rawCost = filteredRecords.value
+ .filter((item) => item.materialType === "鍘熸枡")
+ .reduce((sum, item) => sum + item.cost, 0);
+ const auxCost = filteredRecords.value
+ .filter((item) => item.materialType === "杈呮枡")
+ .reduce((sum, item) => sum + item.cost, 0);
+ const orderCount = new Set(filteredRecords.value.map((item) => item.orderNo)).size;
+ return {
+ rawCost,
+ auxCost,
+ totalCost: rawCost + auxCost,
+ orderCount,
+ };
+});
+
+const detailVisible = ref(false);
+const detailRow = ref(null);
+
+const detailMaterials = computed(() => detailRow.value?.materials || []);
+
+const detailRawCost = computed(() =>
+ detailMaterials.value
+ .filter((item) => item.materialType === "鍘熸枡")
+ .reduce((sum, item) => sum + item.cost, 0)
+);
+const detailAuxCost = computed(() =>
+ detailMaterials.value
+ .filter((item) => item.materialType === "杈呮枡")
+ .reduce((sum, item) => sum + item.cost, 0)
+);
+const detailTotalCost = computed(() =>
+ detailMaterials.value.reduce((sum, item) => sum + item.cost, 0)
+);
+
+const openDetail = (row) => {
+ detailRow.value = row;
+ detailVisible.value = true;
+};
+
+const handleTypeChange = () => {
+ handleQuery();
+};
+
+const handleQuery = () => {
+ page.current = 1;
+ const queryParams = buildQueryParams();
+ console.log("[productionCostAccounting] query params:", queryParams);
+ ElMessage.success("宸叉寜鏉′欢瀹屾垚姹囨��");
+};
+
+const handleReset = () => {
+ searchForm.dateRange = getDefaultDateRange();
+ searchForm.monthRange = getDefaultMonthRange();
+ searchForm.category = "";
+ searchForm.orderNo = "";
+ handleQuery();
+};
+
+const handleSizeChange = (val) => {
+ page.size = val;
+ page.current = 1;
+};
+
+const handleCurrentChange = (val) => {
+ page.current = val;
+};
+
+const handleExport = () => {
+ const headers = [timeColumnLabel.value, "浜у搧绫诲埆", "鐢熶骇璁㈠崟", "鍘熸枡鎴愭湰", "杈呮枡鎴愭湰", "鎬绘垚鏈�"];
+ const lines = tableData.value.map((row) =>
+ [
+ row.timeLabel,
+ row.category,
+ row.orderNo,
+ row.rawCost.toFixed(2),
+ row.auxCost.toFixed(2),
+ row.totalCost.toFixed(2),
+ ].join(",")
+ );
+ const csv = [headers.join(","), ...lines].join("\n");
+ const blob = new Blob(["\uFEFF" + csv], { type: "text/csv;charset=utf-8;" });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = `鐢熶骇鎴愭湰姹囨�籣${statisticsType.value}_${Date.now()}.csv`;
+ link.click();
+ URL.revokeObjectURL(url);
+ ElMessage.success("瀵煎嚭鎴愬姛");
+};
+
+const formatMoney = (v) => {
+ const n = Number.parseFloat(v);
+ const value = Number.isFinite(n) ? n : 0;
+ return value.toLocaleString("zh-CN", {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+};
+
+const formatNumber = (v, digits = 2) => {
+ const n = Number.parseFloat(v);
+ if (!Number.isFinite(n)) return "--";
+ return n.toLocaleString("zh-CN", {
+ minimumFractionDigits: digits,
+ maximumFractionDigits: digits,
+ });
+};
+
+watch(tableData, () => {
+ const maxPage = Math.max(1, Math.ceil(tableData.value.length / page.size));
+ if (page.current > maxPage) page.current = maxPage;
+});
+</script>
+
+<style scoped lang="scss">
+.production-cost-page {
+ --lux-bg: #f6f7fb;
+ --lux-card: rgba(255, 255, 255, 0.86);
+ --lux-border: rgba(15, 23, 42, 0.08);
+ --lux-text: rgba(15, 23, 42, 0.92);
+ --lux-subtle: rgba(15, 23, 42, 0.58);
+ --lux-muted: rgba(15, 23, 42, 0.38);
+ --lux-primary: #2f6fed;
+ --lux-success: #16a34a;
+ --lux-warning: #f59e0b;
+ --lux-danger: #ef4444;
+ --lux-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
+ --lux-shadow-soft: 0 10px 28px rgba(15, 23, 42, 0.06);
+ --lux-radius: 14px;
+
+ 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%);
+}
+
+.filter-card,
+.panel-card,
+.table-card {
+ border-radius: var(--lux-radius);
+ border-color: var(--lux-border);
+ background: var(--lux-card);
+ box-shadow: var(--lux-shadow-soft);
+}
+
+.filter-card {
+ margin-bottom: 16px;
+}
+
+.panel-card,
+.summary-row {
+ margin-bottom: 14px;
+}
+
+.filter-layout {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 14px;
+}
+
+.filter-form {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px 14px;
+}
+
+.filter-form :deep(.el-form-item) {
+ margin: 0;
+}
+
+.filter-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.card-head,
+.panel-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.card-head-left {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.card-head-right {
+ display: flex;
+ align-items: center;
+}
+
+.card-icon {
+ color: var(--lux-primary);
+}
+
+.card-title {
+ font-weight: 760;
+ color: var(--lux-text);
+}
+
+.subtle {
+ color: var(--lux-subtle);
+ font-size: 12px;
+}
+
+.kpi-strip {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.kpi-item {
+ padding: 12px 14px;
+ border-radius: 12px;
+ border: 1px solid rgba(15, 23, 42, 0.08);
+}
+
+.kpi-total {
+ background: linear-gradient(135deg, rgba(47, 111, 237, 0.1), rgba(255, 255, 255, 0.86));
+}
+
+.kpi-raw {
+ background: linear-gradient(135deg, rgba(22, 163, 74, 0.1), rgba(255, 255, 255, 0.86));
+}
+
+.kpi-aux {
+ background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), rgba(255, 255, 255, 0.86));
+}
+
+.kpi-order {
+ background: linear-gradient(135deg, rgba(100, 116, 139, 0.1), rgba(255, 255, 255, 0.86));
+}
+
+.kpi-label {
+ font-size: 12px;
+ color: var(--lux-subtle);
+}
+
+.kpi-value {
+ font-size: 22px;
+ margin-top: 6px;
+ font-weight: 780;
+ color: var(--lux-text);
+}
+
+.price-value {
+ font-weight: 700;
+ color: var(--lux-success);
+}
+
+.cost-value {
+ font-weight: 700;
+ color: var(--lux-danger);
+}
+
+.drawer-head {
+ display: grid;
+ gap: 8px;
+ margin-bottom: 12px;
+ color: var(--lux-text);
+}
+
+.drawer-foot {
+ display: flex;
+ justify-content: flex-end;
+ gap: 18px;
+ margin-top: 12px;
+ color: var(--lux-text);
+}
+
+.pagination-container {
+ display: flex;
+ justify-content: flex-end;
+ padding-top: 12px;
+}
+
+.strong {
+ font-weight: 800;
+}
+
+.w-260 {
+ width: 260px;
+}
+
+.w-180 {
+ width: 180px;
+}
+
+::deep(.lux-table) {
+ border-radius: 12px;
+ overflow: hidden;
+}
+
+::deep(.lux-table th.el-table__cell) {
+ background: rgba(15, 23, 42, 0.03);
+}
+
+::deep(.lux-table .el-table__row:hover > td.el-table__cell) {
+ background-color: rgba(47, 111, 237, 0.06) !important;
+}
+
+@media (max-width: 1100px) {
+ .kpi-strip {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .filter-layout {
+ flex-direction: column;
+ }
+}
+</style>
--
Gitblit v1.9.3