From 7b60f24e07f54634503e5b37e6ca5f85d4ad743f Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 07 八月 2026 15:41:39 +0800
Subject: [PATCH] 天津_建诚恒商贸 1.备件管理-新增独立备件出库功能入口,支持直接完成备件领用出库操作。 2.设备保养-支持按日期分组折叠展示,分组条目可展开查看明细;单条明细行末尾增加【保养完成】操作按钮,可单独办结单条保养任务;折叠汇总状态下,新增【一键保养完成】批量操作按钮,快速办结当前分组全部保养项。 3.设备巡检-按日期分组折叠展示,分组支持展开查看明细;每条巡检明细后增设【巡检完成】按钮,支持单独办结单条巡检记录;分组折叠未展开状态下,提供【一键巡检完成】批量操作,统一办结该分组下所有巡检任务。 4.增值税对比-优化数据展示逻辑,列表展示各订单增值税明细;页面右侧新增柱状可视化图表,直观对比进销项增值税数据。 5.管理驾驶舱-指标修改:将顶部「销售产品数」调整为「销售订单数」;时间筛选:页面左上角新增日期范围下拉筛选控件,所有统计指标标注当前筛选时段;

---
 src/views/equipmentManagement/upkeep/index.vue                           |  214 +++++--
 src/views/equipmentManagement/spareParts/index.vue                       |  147 +++++
 src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue         |    4 
 src/api/procurementManagement/taxComparison.js                           |   20 
 src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue    |    4 
 src/api/equipmentManagement/upkeep.js                                    |   17 
 src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue |    4 
 src/views/equipmentManagement/inspectionManagement/index.vue             |  481 +++++++----------
 src/views/reportAnalysis/PSIDataAnalysis/index.vue                       |   66 ++
 src/api/equipmentManagement/sparePartsUsage.js                           |    9 
 src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue      |    4 
 src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue    |    5 
 src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue       |    7 
 src/api/inspectionManagement/index.js                                    |   17 
 src/views/reportAnalysis/taxComparison/index.vue                         |  483 ++++++++++++++---
 src/api/viewIndex.js                                                     |   20 
 src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue    |    4 
 17 files changed, 1,037 insertions(+), 469 deletions(-)

diff --git a/src/api/equipmentManagement/sparePartsUsage.js b/src/api/equipmentManagement/sparePartsUsage.js
index e9384aa..9befc6c 100644
--- a/src/api/equipmentManagement/sparePartsUsage.js
+++ b/src/api/equipmentManagement/sparePartsUsage.js
@@ -34,3 +34,12 @@
   });
 };
 
+// 澶囦欢鐩存帴鍑哄簱
+export function outboundSpareParts(data) {
+  return request({
+    url: '/sparePartsRequisitionRecord/outbound',
+    method: 'post',
+    data
+  })
+}
+
diff --git a/src/api/equipmentManagement/upkeep.js b/src/api/equipmentManagement/upkeep.js
index 234d2a5..5fa070a 100644
--- a/src/api/equipmentManagement/upkeep.js
+++ b/src/api/equipmentManagement/upkeep.js
@@ -94,6 +94,23 @@
     params: params,
   });
 };
+// 鍗曟潯淇濆吇瀹屾垚
+export function completeMaintenance(id) {
+  return request({
+    url: '/device/maintenance/complete/' + id,
+    method: 'put'
+  })
+}
+
+// 涓�閿繚鍏诲畬鎴�
+export function batchCompleteMaintenance(dateStr) {
+  return request({
+    url: '/device/maintenance/batchComplete',
+    method: 'put',
+    params: { dateStr }
+  })
+}
+
 // 璁惧淇濆吇瀹氭椂浠诲姟鍒楄〃
 export const deviceMaintenanceTaskDel = (params) => {
   return request({
diff --git a/src/api/inspectionManagement/index.js b/src/api/inspectionManagement/index.js
index d0c444a..13d31f9 100644
--- a/src/api/inspectionManagement/index.js
+++ b/src/api/inspectionManagement/index.js
@@ -58,4 +58,21 @@
         method: 'post',
         data: query
     })
+}
+
+// 鍗曟潯宸℃瀹屾垚
+export function completeInspection(id) {
+  return request({
+    url: '/inspectionTask/complete/' + id,
+    method: 'put'
+  })
+}
+
+// 涓�閿贰妫�瀹屾垚
+export function batchCompleteInspection(dateStr) {
+  return request({
+    url: '/inspectionTask/batchComplete',
+    method: 'put',
+    params: { dateStr }
+  })
 }
\ No newline at end of file
diff --git a/src/api/procurementManagement/taxComparison.js b/src/api/procurementManagement/taxComparison.js
index 726a27f..750bccb 100644
--- a/src/api/procurementManagement/taxComparison.js
+++ b/src/api/procurementManagement/taxComparison.js
@@ -1,6 +1,6 @@
 import request from "@/utils/request";
 
-// 鍒嗛〉鏌ヨ
+// 鍒嗛〉鏌ヨ锛堟柊澧� type 鍙傛暟锛�
 export function getTaxList(query) {
   return request({
     url: "/purchase/report/listVat",
@@ -8,3 +8,21 @@
     params: query,
   });
 }
+
+// 澧炲�肩◣杩涢攢椤瑰浘琛ㄦ暟鎹�
+export function getVatChart(year) {
+  return request({
+    url: '/purchase/report/vatChart',
+    method: 'get',
+    params: { year }
+  })
+}
+
+// 澧炲�肩◣鏄庣粏
+export function getVatDetail(query) {
+  return request({
+    url: '/purchase/report/listVatDetail',
+    method: 'get',
+    params: query
+  })
+}
diff --git a/src/api/viewIndex.js b/src/api/viewIndex.js
index 4d2923c..2bba3ad 100644
--- a/src/api/viewIndex.js
+++ b/src/api/viewIndex.js
@@ -202,20 +202,20 @@
 };
 
 // 渚涘簲鍟嗛噰璐帓鍚�
-export const supplierPurchaseRanking = (query) => {
+export const supplierPurchaseRanking = (params) => {
   return request({
     url: "/home/supplierPurchaseRanking",
     method: "get",
-    params: query,
+    params,
   });
 };
 
 // 瀹㈡埛閲戦璐$尞鎺掑悕
-export const customerContributionRanking = (query) => {
+export const customerContributionRanking = (params) => {
   return request({
     url: "/home/customerContributionRanking",
     method: "get",
-    params: query,
+    params,
   });
 };
 
@@ -228,10 +228,11 @@
 };
 
 // 浜у搧閿�鍞噾棰濆垎鏋�
-export const productSalesAnalysis = () => {
+export const productSalesAnalysis = (params) => {
   return request({
     url: "/home/productSalesAnalysis",
     method: "get",
+    params,
   });
 };
 
@@ -245,10 +246,11 @@
 };
 
 // 鍘熸潗鏂欓噰璐噾棰濆崰姣�
-export const rawMaterialPurchaseAmountRatio = () => {
+export const rawMaterialPurchaseAmountRatio = (params) => {
   return request({
     url: "/home/rawMaterialPurchaseAmountRatio",
     method: "get",
+    params,
   });
 };
 
@@ -262,10 +264,11 @@
 };
 
 // 閿�鍞�/閲囪喘/鍌ㄥ瓨浜у搧鏁�
-export const salesPurchaseStorageProductCount = () => {
+export const salesPurchaseStorageProductCount = (startDate, endDate) => {
   return request({
     url: "/home/salesPurchaseStorageProductCount",
     method: "get",
+    params: { startDate, endDate },
   });
 };
 
@@ -288,10 +291,11 @@
 };
 
 // 浜у搧鍛ㄨ浆澶╂暟
-export const productTurnoverDays = () => {
+export const productTurnoverDays = (params) => {
   return request({
     url: "/home/productTurnoverDays",
     method: "get",
+    params,
   });
 };
 
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index 8c0d52c..da8ac99 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -24,7 +24,6 @@
                            :label="tab.label"
                            :value="tab.name" />
         </el-radio-group>
-        <!-- 鎿嶄綔鎸夐挳鍖� -->
         <el-space v-if="activeRadio !== 'task'">
           <el-button type="primary"
                      :icon="Plus"
@@ -38,7 +37,9 @@
           <el-button @click="handleOut">瀵煎嚭</el-button>
         </el-space>
       </div>
-      <div>
+
+      <!-- 宸℃浠诲姟 -->
+      <div v-show="activeRadio === 'taskManage'">
         <PIMTable :table-loading="tableLoading"
                   :table-data="tableData"
                   :column="tableColumns"
@@ -56,8 +57,6 @@
                   :table-style="{ width: '100%' }">
           <template #inspector="{ row }">
             <div class="person-tags">
-              <!-- 璋冭瘯淇℃伅锛屼笂绾挎椂鍒犻櫎 -->
-              <!-- {{ console.log('inspector data:', row.inspector) }} -->
               <template v-if="row.inspector && row.inspector.length > 0">
                 <el-tag v-for="(person, index) in row.inspector"
                         :key="index"
@@ -67,8 +66,7 @@
                   {{ person }}
                 </el-tag>
               </template>
-              <span v-else
-                    class="no-data">--</span>
+              <span v-else class="no-data">--</span>
             </div>
           </template>
           <template #isEnabled="{ row }">
@@ -79,6 +77,80 @@
           </template>
         </PIMTable>
       </div>
+
+      <!-- 宸℃璁板綍 - 鏃ユ湡鍒嗙粍鎶樺彔 -->
+      <div v-show="activeRadio === 'task'">
+        <el-table
+          :data="groupedInspectionList"
+          border
+          v-loading="tableLoading"
+          :expand-row-keys="inspectionExpandedRowKeys"
+          :row-key="(row) => row.date"
+          @expand-change="onInspectionExpandChange"
+          height="calc(100vh - 23em)"
+        >
+          <el-table-column type="expand">
+            <template #default="props">
+              <el-table :data="props.row.children" border row-key="id">
+                <el-table-column label="宸℃浠诲姟鍚嶇О" prop="taskName" align="center" min-width="180" />
+                <el-table-column label="宸℃椤圭洰" prop="inspectionProject" align="center" min-width="160" />
+                <el-table-column label="宸℃浜�" align="center" min-width="150">
+                  <template #default="{ row }">
+                    <template v-if="row.inspector && row.inspector.length > 0">
+                      <el-tag v-for="(p, i) in row.inspector" :key="i" size="small" type="primary" class="person-tag">{{ p }}</el-tag>
+                    </template>
+                    <span v-else>--</span>
+                  </template>
+                </el-table-column>
+                <el-table-column label="宸℃鐘舵��" align="center" width="100">
+                  <template #default="{ row }">
+                    <el-tag :type="row.status === 1 ? 'success' : 'warning'" size="small">
+                      {{ row.status === 1 ? '宸插畬鎴�' : '寰呭贰妫�' }}
+                    </el-tag>
+                  </template>
+                </el-table-column>
+                <el-table-column label="宸℃缁撴灉" prop="inspectionResult" align="center" min-width="120" />
+                <el-table-column label="澶囨敞" prop="remarks" align="center" min-width="150" />
+                <el-table-column label="寮傚父鎻忚堪" prop="abnormalDescription" align="center" min-width="150" />
+                <el-table-column label="鎿嶄綔" align="center" width="200">
+                  <template #default="{ row }">
+                    <el-button type="primary" link @click="openUploadDialog(row)">涓婁紶</el-button>
+                    <el-button type="success" link @click="viewFile(row)">鏌ョ湅闄勪欢</el-button>
+                    <el-button type="warning" link v-if="row.status === 0" @click="handleSingleComplete(row)">宸℃瀹屾垚</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </template>
+          </el-table-column>
+          <el-table-column label="宸℃鏃ユ湡" prop="date" align="center" />
+          <el-table-column label="宸℃璁板綍鏁�" prop="count" align="center" />
+          <el-table-column label="寰呭贰妫�鏁�" align="center" width="120">
+            <template #default="{ row }">
+              {{ row.children.filter(c => c.status === 0).length }}
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" align="center" width="200">
+            <template #default="{ row }">
+              <el-button
+                type="primary"
+                size="small"
+                :disabled="!row.children.some(c => c.status === 0)"
+                @click="handleBatchInspection(row.date)"
+              >
+                涓�閿贰妫�瀹屾垚
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          v-show="total > 0"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          :page="pageNum"
+          :limit="pageSize"
+          @pagination="handlePagination"
+        />
+      </div>
     </el-card>
     <form-dia ref="formDia"
               @closeDia="handleQuery"></form-dia>
@@ -86,47 +158,43 @@
     <upload-files ref="uploadFiles"
                   @success="handleQuery"
                   @closeDia="handleQuery"></upload-files>
+
   </div>
 </template>
 
 <script setup>
   import { Delete, Plus } from "@element-plus/icons-vue";
-  import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue";
-  import { ElMessageBox } from "element-plus";
+  import { onMounted, ref, reactive, getCurrentInstance, nextTick, computed } from "vue";
+  import { ElMessage, ElMessageBox } from "element-plus";
   import dayjs from "dayjs";
 
-  // 缁勪欢寮曞叆
   import PIMTable from "@/components/PIMTable/PIMTable.vue";
   import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
   import UploadFiles from "@/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue";
   import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
 
-  // 鎺ュ彛寮曞叆
   import {
     delTimingTask,
     inspectionTaskList,
     timingTaskList,
+    completeInspection,
+    batchCompleteInspection,
   } from "@/api/inspectionManagement/index.js";
+  import Pagination from "@/components/PIMTable/Pagination.vue";
 
-  // 鍏ㄥ眬鍙橀噺
   const { proxy } = getCurrentInstance();
   const formDia = ref();
   const viewFiles = ref();
   const uploadFiles = ref();
 
-  // 鏌ヨ鍙傛暟
-  const queryParams = reactive({
-    taskName: "",
-  });
+  const queryParams = reactive({ taskName: "" });
 
-  // 鍗曢�夋閰嶇疆
   const activeRadio = ref("taskManage");
   const radios = reactive([
     { name: "taskManage", label: "宸℃浠诲姟" },
     { name: "task", label: "宸℃璁板綍" },
   ]);
 
-  // 琛ㄦ牸鏁版嵁
   const selectedRows = ref([]);
   const tableData = ref([]);
   const operationsArr = ref([]);
@@ -134,316 +202,165 @@
   const tableLoading = ref(false);
   const total = ref(0);
   const pageNum = ref(1);
-  const pageSize = ref(10);
+  const pageSize = ref(100);
 
-  // 鍒楅厤缃�
   const columns = ref([
     { prop: "taskName", label: "宸℃浠诲姟鍚嶇О", minWidth: 200 },
     { prop: "inspectionProject", label: "宸℃椤圭洰", minWidth: 180 },
     { prop: "remarks", label: "澶囨敞", minWidth: 180 },
     { prop: "inspector", label: "鎵ц宸℃浜�", minWidth: 180, slot: "inspector" },
+    { prop: "isEnabled", label: "鏄惁鍚敤", minWidth: 100, dataType: "slot", slot: "isEnabled" },
     {
-      prop: "isEnabled",
-      label: "鏄惁鍚敤",
-      minWidth: 100,
-      dataType: "slot",
-      slot: "isEnabled",
+      prop: "frequencyType", label: "棰戞", minWidth: 120,
+      formatData: params => ({ DAILY: "姣忔棩", WEEKLY: "姣忓懆", MONTHLY: "姣忔湀", QUARTERLY: "瀛e害" }[params] || ""),
     },
     {
-      prop: "frequencyType",
-      label: "棰戞",
-      minWidth: 120,
-      formatData: params => {
-        return params === "DAILY"
-          ? "姣忔棩"
-          : params === "WEEKLY"
-          ? "姣忓懆"
-          : params === "MONTHLY"
-          ? "姣忔湀"
-          : params === "QUARTERLY"
-          ? "瀛e害"
-          : "";
-      },
-    },
-    {
-      prop: "frequencyDetail",
-      label: "寮�濮嬫棩鏈熶笌鏃堕棿",
-      minWidth: 200,
+      prop: "frequencyDetail", label: "寮�濮嬫棩鏈熶笌鏃堕棿", minWidth: 200,
       formatter: (row, column, cellValue) => {
-        // 鍏堝垽鏂槸鍚︽槸瀛楃涓�
         if (typeof cellValue !== "string") return "";
-        let val = cellValue;
-        const replacements = {
-          MON: "鍛ㄤ竴",
-          TUE: "鍛ㄤ簩",
-          WED: "鍛ㄤ笁",
-          THU: "鍛ㄥ洓",
-          FRI: "鍛ㄤ簲",
-          SAT: "鍛ㄥ叚",
-          SUN: "鍛ㄦ棩",
-        };
-        // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
-        return val.replace(
-          /MON|TUE|WED|THU|FRI|SAT|SUN/g,
-          match => replacements[match]
-        );
+        return cellValue.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => ({ MON: "鍛ㄤ竴", TUE: "鍛ㄤ簩", WED: "鍛ㄤ笁", THU: "鍛ㄥ洓", FRI: "鍛ㄤ簲", SAT: "鍛ㄥ叚", SUN: "鍛ㄦ棩" }[match]));
       },
     },
     { prop: "registrant", label: "鐧昏浜�", minWidth: 120 },
     {
-      prop: "createTime",
-      label: "鐧昏鏃ユ湡",
-      minWidth: 180,
-      formatData: cell => {
-        if (!cell) return "-";
-        try {
-          return dayjs(cell).format("YYYY-MM-DD HH:mm:ss");
-        } catch {
-          return cell;
-        }
-      },
+      prop: "createTime", label: "鐧昏鏃ユ湡", minWidth: 180,
+      formatData: cell => { if (!cell) return "-"; try { return dayjs(cell).format("YYYY-MM-DD HH:mm:ss"); } catch { return cell; } },
     },
-    // {
-    //   prop: "inspectionResult",
-    //   label: "宸℃缁撴灉",
-    //   minWidth: 100,
-    //   dataType: "tag",
-    //   formatData: val => {
-    //     return val == 1 ? "姝e父" : "寮傚父";
-    //   },
-    //   formatType: val => {
-    //     return val == 1 ? "success" : "danger";
-    //   },
-    // },
     { prop: "abnormalDescription", label: "寮傚父鎻忚堪", minWidth: 150 },
   ]);
 
-  // 鎿嶄綔鍒楅厤缃�
   const getOperationColumn = operations => {
     if (!operations || operations.length === 0) return null;
-
-    const operationConfig = {
-      label: "鎿嶄綔",
-      width: operations.length > 1 ? 180 : 130,
-      fixed: "right",
-      align: "center",
-      dataType: "action",
-      operation: operations
-        .map(op => {
-          switch (op) {
-            case "edit":
-              return {
-                name: "缂栬緫",
-                clickFun: handleAdd,
-                color: "#409EFF",
-              };
-            case "upload":
-              return {
-                name: "涓婁紶",
-                clickFun: openUploadDialog,
-                color: "#409EFF",
-              };
-            case "viewFile":
-              return {
-                name: "鏌ョ湅闄勪欢",
-                clickFun: viewFile,
-                color: "#67C23A",
-              };
-            default:
-              return null;
-          }
-        })
-        .filter(Boolean),
+    return {
+      label: "鎿嶄綔", width: operations.length > 1 ? 180 : 130, fixed: "right", align: "center", dataType: "action",
+      operation: operations.map(op => {
+        switch (op) {
+          case "edit": return { name: "缂栬緫", clickFun: handleAdd, color: "#409EFF" };
+          case "upload": return { name: "涓婁紶", clickFun: openUploadDialog, color: "#409EFF" };
+          case "viewFile": return { name: "鏌ョ湅闄勪欢", clickFun: viewFile, color: "#67C23A" };
+          default: return null;
+        }
+      }).filter(Boolean),
     };
-
-    return operationConfig;
   };
 
-  onMounted(() => {
-    radioChange("taskManage");
-  });
+  // 宸℃璁板綍 - 鏃ユ湡鍒嗙粍
+  const inspectionExpandedRowKeys = ref([]);
 
-  // 鍗曢�夊彉鍖�
+  const groupInspectionByDate = (list) => {
+    const map = {};
+    list.forEach(item => {
+      const date = item.dateStr || (item.createTime ? dayjs(item.createTime).format('YYYY-MM-DD') : '鏈煡鏃ユ湡');
+      if (!map[date]) map[date] = [];
+      map[date].push(item);
+    });
+    return Object.entries(map).map(([date, children]) => ({ date, children, count: children.length }));
+  };
+
+  const groupedInspectionList = computed(() => groupInspectionByDate(tableData.value));
+
+  const onInspectionExpandChange = (row, expandedRows) => {
+    inspectionExpandedRowKeys.value = expandedRows.map(r => r.date);
+  };
+
+  // 鍗曟潯宸℃瀹屾垚
+  const handleSingleComplete = async (row) => {
+    try {
+      await ElMessageBox.confirm('纭灏嗚宸℃璁板綍鏍囪涓哄畬鎴愶紵', '鎻愮ず', { type: 'warning' });
+      const res = await completeInspection(row.id);
+      if (res.code === 200) {
+        ElMessage.success('宸℃瀹屾垚');
+        getList();
+      } else {
+        ElMessage.error(res.msg || '鎿嶄綔澶辫触');
+      }
+    } catch { /* 鍙栨秷 */ }
+  };
+
+  // 涓�閿贰妫�瀹屾垚
+  const handleBatchInspection = async (dateStr) => {
+    try {
+      await ElMessageBox.confirm(`纭灏� ${dateStr} 鐨勬墍鏈夊緟宸℃璁板綍鏍囪涓哄畬鎴愶紵`, '鎻愮ず', { type: 'warning' });
+      const res = await batchCompleteInspection(dateStr);
+      if (res.code === 200) {
+        ElMessage.success(res.msg || '鎵归噺瀹屾垚鎴愬姛');
+        getList();
+      } else {
+        ElMessage.error(res.msg || '鎿嶄綔澶辫触');
+      }
+    } catch { /* 鍙栨秷 */ }
+  };
+
+  onMounted(() => { radioChange("taskManage"); });
+
   const radioChange = value => {
+    inspectionExpandedRowKeys.value = [];
     if (value === "taskManage") {
       const operationColumn = getOperationColumn(["edit"]);
-      tableColumns.value = [
-        ...columns.value,
-        ...(operationColumn ? [operationColumn] : []),
-      ];
+      tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
       operationsArr.value = ["edit"];
     } else if (value === "task") {
       const operationColumn = getOperationColumn(["upload", "viewFile"]);
-      // 宸℃璁板綍涓嶅睍绀�"鏄惁鍚敤"鍒�
       const taskColumns = columns.value.filter(col => col.prop !== "isEnabled");
-      tableColumns.value = [
-        ...taskColumns,
-        ...(operationColumn ? [operationColumn] : []),
-      ];
+      tableColumns.value = [...taskColumns, ...(operationColumn ? [operationColumn] : [])];
       operationsArr.value = ["upload", "viewFile"];
     }
     pageNum.value = 1;
-    pageSize.value = 10;
     getList();
   };
 
-  // 鏌ヨ鎿嶄綔
-  const handleQuery = () => {
-    pageNum.value = 1;
-    pageSize.value = 10;
-    getList();
-  };
-  // 鍒嗛〉澶勭悊
-  const handlePagination = val => {
-    pageNum.value = val.page;
-    pageSize.value = val.limit;
-    getList();
-  };
-  // 鑾峰彇鍒楄〃鏁版嵁
+  const handleQuery = () => { pageNum.value = 1; getList(); };
+
+  const handlePagination = val => { pageNum.value = val.page; pageSize.value = val.limit; getList(); };
+
   const getList = () => {
     tableLoading.value = true;
-
-    const params = {
-      ...queryParams,
-      size: pageSize.value,
-      current: pageNum.value,
-    };
-
-    let apiCall;
-    if (activeRadio.value === "task") {
-      apiCall = inspectionTaskList(params);
-    } else {
-      apiCall = timingTaskList(params);
-    }
-
-    apiCall
-      .then(res => {
-        const rawData = res.data.records || [];
-        // 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
-        tableData.value = rawData.map(item => {
-          const processedItem = { ...item };
-          processedItem.__raw = { ...item };
-
-          // 澶勭悊 inspector 瀛楁
-          if (processedItem.inspector) {
-            if (typeof processedItem.inspector === "string") {
-              // 瀛楃涓叉寜閫楀彿鍒嗗壊
-              processedItem.inspector = processedItem.inspector
-                .split(",")
-                .map(s => s.trim())
-                .filter(s => s);
-            } else if (!Array.isArray(processedItem.inspector)) {
-              // 闈炴暟缁勮浆涓烘暟缁�
-              processedItem.inspector = [processedItem.inspector];
-            }
-          } else {
-            // 绌哄�艰涓虹┖鏁扮粍
-            processedItem.inspector = [];
+    const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
+    const apiCall = activeRadio.value === "task" ? inspectionTaskList(params) : timingTaskList(params);
+    apiCall.then(res => {
+      const rawData = res.data.records || [];
+      tableData.value = rawData.map(item => {
+        const processedItem = { ...item };
+        if (processedItem.inspector) {
+          if (typeof processedItem.inspector === "string") {
+            processedItem.inspector = processedItem.inspector.split(",").map(s => s.trim()).filter(s => s);
+          } else if (!Array.isArray(processedItem.inspector)) {
+            processedItem.inspector = [processedItem.inspector];
           }
-
-          return processedItem;
-        });
-        total.value = res.data.total || 0;
-      })
-      .finally(() => {
-        tableLoading.value = false;
-      });
-  };
-
-  // 閲嶇疆鏌ヨ
-  const resetQuery = () => {
-    for (const key in queryParams) {
-      if (!["pageNum", "pageSize"].includes(key)) {
-        queryParams[key] = "";
-      }
-    }
-    handleQuery();
-  };
-
-  // 鏂板 / 缂栬緫
-  const handleAdd = row => {
-    const type = row ? "edit" : "add";
-    nextTick(() => {
-      formDia.value?.openDialog(type, row);
-    });
-  };
-
-  // 鏌ョ湅闄勪欢
-  const viewFile = row => {
-    nextTick(() => {
-      viewFiles.value?.openDialog(row);
-    });
-  };
-
-  const openUploadDialog = row => {
-    nextTick(() => {
-      uploadFiles.value?.openDialog(row);
-    });
-  };
-
-  // 鍒犻櫎鎿嶄綔
-  const handleDelete = () => {
-    if (!selectedRows.value.length) {
-      proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
-      return;
-    }
-
-    const deleteIds = selectedRows.value.map(item => item.id);
-
-    proxy.$modal
-      .confirm("鏄惁纭鍒犻櫎鎵�閫夋暟鎹」锛�")
-      .then(() => {
-        return delTimingTask(deleteIds);
-      })
-      .then(() => {
-        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
-        handleQuery();
-      })
-      .catch(() => {});
-  };
-
-  // 澶氶�夊彉鏇�
-  const handleSelectionChange = selection => {
-    selectedRows.value = selection;
-  };
-
-  // 瀵煎嚭
-  const handleOut = () => {
-    ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
-      confirmButtonText: "纭",
-      cancelButtonText: "鍙栨秷",
-      type: "warning",
-    })
-      .then(() => {
-        // 鏍规嵁褰撳墠閫変腑鐨勬爣绛鹃〉璋冪敤涓嶅悓鐨勫鍑烘帴鍙�
-        if (activeRadio.value === "taskManage") {
-          // 宸℃浠诲姟
-          proxy.download("/timingTask/export", {}, "宸℃浠诲姟.xlsx");
-        } else if (activeRadio.value === "task") {
-          // 宸℃璁板綍
-          proxy.download("/inspectionTask/export", {}, "宸℃璁板綍.xlsx");
+        } else {
+          processedItem.inspector = [];
         }
-      })
-      .catch(() => {
-        proxy.$modal.msg("宸插彇娑�");
+        return processedItem;
       });
+      total.value = res.data.total || 0;
+    }).finally(() => { tableLoading.value = false; });
+  };
+
+  const resetQuery = () => { queryParams.taskName = ""; handleQuery(); };
+
+  const handleAdd = row => { nextTick(() => { formDia.value?.openDialog(row ? "edit" : "add", row); }); };
+  const viewFile = row => { nextTick(() => { viewFiles.value?.openDialog(row); }); };
+  const openUploadDialog = row => { nextTick(() => { uploadFiles.value?.openDialog(row); }); };
+
+  const handleDelete = () => {
+    if (!selectedRows.value.length) { proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁"); return; }
+    proxy.$modal.confirm("鏄惁纭鍒犻櫎鎵�閫夋暟鎹」锛�").then(() => delTimingTask(selectedRows.value.map(item => item.id)))
+      .then(() => { proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛"); handleQuery(); }).catch(() => {});
+  };
+
+  const handleSelectionChange = selection => { selectedRows.value = selection; };
+
+  const handleOut = () => {
+    ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", { confirmButtonText: "纭", cancelButtonText: "鍙栨秷", type: "warning" })
+      .then(() => {
+        if (activeRadio.value === "taskManage") proxy.download("/timingTask/export", {}, "宸℃浠诲姟.xlsx");
+        else proxy.download("/inspectionTask/export", {}, "宸℃璁板綍.xlsx");
+      }).catch(() => { proxy.$modal.msg("宸插彇娑�"); });
   };
 </script>
 
 <style scoped>
-  .person-tags {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 4px;
-  }
-
-  .person-tag {
-    margin-right: 4px;
-    margin-bottom: 2px;
-  }
-
-  .no-data {
-    color: #909399;
-    font-size: 14px;
-  }
+  .person-tags { display: flex; flex-wrap: wrap; gap: 4px; }
+  .person-tag { margin-right: 4px; margin-bottom: 2px; }
+  .no-data { color: #909399; font-size: 14px; }
 </style>
diff --git a/src/views/equipmentManagement/spareParts/index.vue b/src/views/equipmentManagement/spareParts/index.vue
index b8cb086..131e1d5 100644
--- a/src/views/equipmentManagement/spareParts/index.vue
+++ b/src/views/equipmentManagement/spareParts/index.vue
@@ -105,6 +105,7 @@
               <el-select v-model="usageQuery.sourceType" placeholder="璇烽�夋嫨" clearable style="width: 200px">
                 <el-option label="缁翠慨" :value="0" />
                 <el-option label="淇濆吇" :value="1" />
+                <el-option label="鐩存帴鍑哄簱" :value="2" />
               </el-select>
             </el-form-item>
             <el-form-item>
@@ -112,6 +113,9 @@
               <el-button @click="resetUsageQuery">閲嶇疆</el-button>
             </el-form-item>
           </el-form>
+          <div>
+            <el-button type="primary" @click="openRequisitionDialog">鐩存帴鍑哄簱</el-button>
+          </div>
         </div>
 				<div class="table_list">
 					<PIMTable
@@ -126,6 +130,60 @@
 				</div>
       </el-tab-pane>
     </el-tabs>
+
+    <!-- 鐩存帴鍑哄簱寮圭獥 -->
+    <el-dialog title="鐩存帴鍑哄簱" v-model="requisitionDialogVisible" width="500px">
+      <el-form :model="requisitionForm" :rules="requisitionRules" ref="requisitionFormRef" label-width="100px">
+        <el-form-item label="璁惧閫夋嫨" prop="deviceLedgerId">
+          <el-select
+            v-model="requisitionForm.deviceLedgerId"
+            placeholder="璇烽�夋嫨璁惧"
+            filterable
+            clearable
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in requisitionDeviceOptions"
+              :key="item.id"
+              :label="item.deviceName"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="澶囦欢閫夋嫨" prop="sparePartsId">
+          <el-select
+            v-model="requisitionForm.sparePartsId"
+            placeholder="璇烽�夋嫨澶囦欢"
+            filterable
+            clearable
+            style="width: 100%"
+            @change="onRequisitionSparePartChange"
+          >
+            <el-option
+              v-for="item in requisitionSparePartsOptions"
+              :key="item.id"
+              :label="`${item.name}锛堝簱瀛�: ${item.quantity}锛塦"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="棰嗙敤鏁伴噺" prop="quantity">
+          <el-input-number
+            v-model="requisitionForm.quantity"
+            :min="1"
+            :max="selectedSparePartMaxQty"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="requisitionForm.remark" placeholder="璇疯緭鍏ュ娉�" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitRequisition" :loading="requisitionLoading">纭畾</el-button>
+        <el-button @click="requisitionDialogVisible = false">鍙栨秷</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -135,7 +193,7 @@
 import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts";
 import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
 import PIMTable from "@/components/PIMTable/PIMTable.vue";
-import { getSparePartsUsagePage } from "@/api/equipmentManagement/sparePartsUsage";
+import { getSparePartsUsagePage, outboundSpareParts } from "@/api/equipmentManagement/sparePartsUsage";
 
 // 鍔犺浇鐘舵��
 const loading = ref(false);
@@ -511,6 +569,93 @@
   }
 };
 
+// 鐩存帴鍑哄簱
+const requisitionDialogVisible = ref(false);
+const requisitionLoading = ref(false);
+const requisitionFormRef = ref(null);
+const requisitionDeviceOptions = ref([]);
+const requisitionSparePartsOptions = ref([]);
+const selectedSparePartMaxQty = ref(1);
+const requisitionForm = reactive({
+  deviceLedgerId: null,
+  sparePartsId: null,
+  quantity: 1,
+  remark: '',
+});
+const requisitionRules = {
+  sparePartsId: [{ required: true, message: '璇烽�夋嫨澶囦欢', trigger: 'change' }],
+  quantity: [{ required: true, message: '璇疯緭鍏ラ鐢ㄦ暟閲�', trigger: 'blur' }],
+};
+
+const loadRequisitionOptions = async () => {
+  try {
+    const [deviceRes, spareRes] = await Promise.all([
+      getDeviceLedger(),
+      getSparePartsList({ current: 1, size: 9999 }),
+    ]);
+    requisitionDeviceOptions.value = deviceRes.data || [];
+    requisitionSparePartsOptions.value = spareRes.data?.records || [];
+  } catch (e) {
+    ElMessage.error('鍔犺浇閫夐」澶辫触');
+  }
+};
+
+const openRequisitionDialog = async () => {
+  requisitionForm.deviceLedgerId = null;
+  requisitionForm.sparePartsId = null;
+  requisitionForm.quantity = 1;
+  requisitionForm.remark = '';
+  selectedSparePartMaxQty.value = 1;
+  await loadRequisitionOptions();
+  requisitionDialogVisible.value = true;
+};
+
+const onRequisitionSparePartChange = (val) => {
+  const selected = requisitionSparePartsOptions.value.find(item => item.id === val);
+  selectedSparePartMaxQty.value = selected ? selected.quantity : 1;
+  if (requisitionForm.quantity > selectedSparePartMaxQty.value) {
+    requisitionForm.quantity = selectedSparePartMaxQty.value;
+  }
+};
+
+const submitRequisition = async () => {
+  if (!requisitionFormRef.value) return;
+  try {
+    await requisitionFormRef.value.validate();
+  } catch {
+    return;
+  }
+  if (requisitionForm.quantity <= 0) {
+    ElMessage.warning('棰嗙敤鏁伴噺蹇呴』澶т簬0');
+    return;
+  }
+  const selected = requisitionSparePartsOptions.value.find(item => item.id === requisitionForm.sparePartsId);
+  if (selected && requisitionForm.quantity > selected.quantity) {
+    ElMessage.warning(`搴撳瓨涓嶈冻锛屽綋鍓嶅簱瀛樹负 ${selected.quantity}`);
+    return;
+  }
+  requisitionLoading.value = true;
+  try {
+    const res = await outboundSpareParts({
+      deviceLedgerId: requisitionForm.deviceLedgerId || undefined,
+      sparePartsId: requisitionForm.sparePartsId,
+      quantity: requisitionForm.quantity,
+      remark: requisitionForm.remark || undefined,
+    });
+    if (res.code === 200) {
+      ElMessage.success('棰嗙敤鎴愬姛');
+      requisitionDialogVisible.value = false;
+      fetchUsageData();
+    } else {
+      ElMessage.error(res.msg || '棰嗙敤澶辫触');
+    }
+  } catch {
+    ElMessage.error('棰嗙敤澶辫触');
+  } finally {
+    requisitionLoading.value = false;
+  }
+};
+
 // 缁勪欢鎸傝浇鏃惰幏鍙栧垪琛ㄦ暟鎹�
 onMounted(() => {
   fetchListData();
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 245a162..43474d7 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -142,68 +142,91 @@
               </el-button>
               <el-button type="danger"
                          icon="Delete"
-                         :disabled="multipleList.length <= 0 || hasFinishedStatus"
+                         :disabled="multipleList.length <= 0"
                          @click="delRepairByIds(multipleList.map((item) => item.id))">
                 鎵归噺鍒犻櫎
               </el-button>
             </div>
           </div>
-          <PIMTable rowKey="id"
-                    isSelection
-                    :column="columns"
-                    :tableData="dataList"
-                    :page="{
-          current: pagination.currentPage,
-          size: pagination.pageSize,
-          total: pagination.total,
-        }"
-                    @selection-change="handleSelectionChange"
-                    @pagination="changePage">
-            <template #maintenanceResultRef="{ row }">
-              <div>{{ row.maintenanceResult || '-' }}</div>
-            </template>
-            <template #statusRef="{ row }">
-              <el-tag v-if="row.status === 2"
-                      type="danger">澶辫触</el-tag>
-              <el-tag v-if="row.status === 1"
-                      type="success">瀹岀粨</el-tag>
-              <el-tag v-if="row.status === 0"
-                      type="warning">寰呬繚鍏�</el-tag>
-            </template>
-            <template #operation="{ row }">
-              <!-- 杩欎釜鍔熻兘璺熸柊澧炰繚鍏诲姛鑳戒竴妯′竴鏍凤紝鏈夊暐鎰忎箟锛� -->
-              <!-- <el-button
-              type="primary"
-              text
-              @click="addMaintain(row)"
+          <el-table
+            :data="groupedDataList"
+            border
+            v-loading="tableLoading"
+            @selection-change="handleSelectionChange"
+            :expand-row-keys="expandedRowKeys"
+            :row-key="(row) => row.date"
+            @expand-change="onExpandChange"
           >
-            鏂板淇濆吇
-          </el-button> -->
-              <el-button type="primary"
-                         link
-                         :disabled="row.status === 1"
-                         @click="editPlan(row.id)">
-                缂栬緫
-              </el-button>
-              <el-button type="success"
-                         link
-                         :disabled="row.status === 1"
-                         @click="addMaintain(row)">
-                淇濆吇
-              </el-button>
-              <el-button type="danger"
-                         link
-                         :disabled="row.status === 1"
-                         @click="delRepairByIds(row.id)">
-                鍒犻櫎
-              </el-button>
-              <el-button type="primary"
-                         link
-                         @click="openFileDialog(row)">
-                闄勪欢
-              </el-button>
-            </template>
-          </PIMTable>
+            <el-table-column type="selection" width="55" />
+            <el-table-column type="expand">
+              <template #default="props">
+                <el-table :data="props.row.children" border row-key="id">
+                  <el-table-column label="璁惧鍚嶇О" prop="deviceName" align="center" />
+                  <el-table-column label="瑙勬牸鍨嬪彿" prop="deviceModel" align="center" />
+                  <el-table-column label="褰曞叆浜�" prop="createUserName" align="center" />
+                  <el-table-column label="淇濆吇椤圭洰" prop="machineryCategory" align="center">
+                    <template #default="{ row }">
+                      {{ row.machineryCategory || '--' }}
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="瀹為檯淇濆吇浜�" prop="maintenanceActuallyName" align="center" />
+                  <el-table-column label="瀹為檯淇濆吇鏃ユ湡" align="center">
+                    <template #default="{ row }">
+                      {{ row.maintenanceActuallyTime ? dayjs(row.maintenanceActuallyTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="淇濆吇缁撴灉" align="center">
+                    <template #default="{ row }">
+                      {{ row.maintenanceResult || '-' }}
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="鐘舵��" align="center">
+                    <template #default="{ row }">
+                      <el-tag v-if="row.status === 2" type="danger">澶辫触</el-tag>
+                      <el-tag v-if="row.status === 1" type="success">瀹岀粨</el-tag>
+                      <el-tag v-if="row.status === 0" type="warning">寰呬繚鍏�</el-tag>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" align="center" width="300">
+                    <template #default="{ row }">
+                      <el-button type="primary" link :disabled="row.status === 1" @click="editPlan(row.id)">缂栬緫</el-button>
+                      <el-button type="success" link :disabled="row.status !== 0" @click="handleSingleComplete(row.id)">淇濆吇瀹屾垚</el-button>
+                      <el-button type="success" link :disabled="row.status === 1" @click="addMaintain(row)">淇濆吇</el-button>
+                      <el-button type="danger" link :disabled="row.status === 1" @click="delRepairByIds(row.id)">鍒犻櫎</el-button>
+                      <el-button type="primary" link @click="openFileDialog(row)">闄勪欢</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </template>
+            </el-table-column>
+            <el-table-column label="璁″垝淇濆吇鏃ユ湡" prop="date" align="center" />
+            <el-table-column label="淇濆吇璁板綍鏁�" prop="count" align="center" />
+            <el-table-column label="寰呬繚鍏绘暟" align="center" width="120">
+              <template #default="{ row }">
+                {{ row.children.filter(c => c.status === 0).length }}
+              </template>
+            </el-table-column>
+            <el-table-column label="鎿嶄綔" align="center" width="200">
+              <template #default="{ row }">
+                <el-button
+                  type="primary"
+                  size="small"
+                  :disabled="!row.children.some(c => c.status === 0)"
+                  @click="handleBatchComplete(row.date)"
+                >
+                  涓�閿繚鍏诲畬鎴�
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <Pagination
+            v-show="pagination.total > 0"
+            :total="pagination.total"
+            layout="total, sizes, prev, pager, next, jumper"
+            :page="pagination.currentPage"
+            :limit="pagination.pageSize"
+            @pagination="changePage"
+          />
         </div>
       </el-tab-pane>
     </el-tabs>
@@ -217,6 +240,7 @@
               v-model:visible="fileDialogVisible"
               :record-type="'device_maintenance'"
               :record-id="currentMaintenanceTaskId" />
+
   </div>
 </template>
 
@@ -240,8 +264,11 @@
     delUpkeep,
     deviceMaintenanceTaskList,
     deviceMaintenanceTaskDel,
+    batchCompleteMaintenance,
+    completeMaintenance,
   } from "@/api/equipmentManagement/upkeep";
   import dayjs from "dayjs";
+  import Pagination from "@/components/PIMTable/Pagination.vue";
 
   const { proxy } = getCurrentInstance();
   const FileList = defineAsyncComponent(() =>
@@ -273,10 +300,56 @@
   const dataList = ref([]);
   const pagination = ref({
     currentPage: 1,
-    pageSize: 10,
+    pageSize: 100,
     total: 0,
   });
   const multipleList = ref([]);
+  const tableLoading = ref(false);
+  const expandedRowKeys = ref([]);
+
+  const groupByDate = (list) => {
+    const map = {};
+    list.forEach(item => {
+      const date = item.dateStr || (item.maintenancePlanTime ? dayjs(item.maintenancePlanTime).format('YYYY-MM-DD') : (item.createTime ? dayjs(item.createTime).format('YYYY-MM-DD') : '鏈煡鏃ユ湡'));
+      if (!map[date]) map[date] = [];
+      map[date].push(item);
+    });
+    return Object.entries(map).map(([date, children]) => ({ date, children, count: children.length }));
+  };
+
+  const groupedDataList = computed(() => groupByDate(dataList.value));
+
+  // 鍗曟潯淇濆吇瀹屾垚
+  const handleSingleComplete = async (id) => {
+    try {
+      await ElMessageBox.confirm('纭灏嗚淇濆吇璁板綍鏍囪涓哄畬鎴愶紵', '鎻愮ず', { type: 'warning' });
+      const res = await completeMaintenance(id);
+      if (res.code === 200) {
+        ElMessage.success('淇濆吇瀹屾垚');
+        getTableData();
+      } else {
+        ElMessage.error(res.msg || '鎿嶄綔澶辫触');
+      }
+    } catch { /* 鍙栨秷 */ }
+  };
+
+  // 涓�閿繚鍏诲畬鎴�
+  const handleBatchComplete = async (dateStr) => {
+    try {
+      await ElMessageBox.confirm(`纭灏� ${dateStr} 鐨勬墍鏈夊緟淇濆吇璁板綍鏍囪涓哄畬鎴愶紵`, '鎻愮ず', { type: 'warning' });
+      const res = await batchCompleteMaintenance(dateStr);
+      if (res.code === 200) {
+        ElMessage.success(res.msg || '鎵归噺淇濆吇瀹屾垚鎴愬姛');
+        getTableData();
+      } else {
+        ElMessage.error(res.msg || '鎵归噺淇濆吇瀹屾垚澶辫触');
+      }
+    } catch { /* 鍙栨秷 */ }
+  };
+
+  const onExpandChange = (row, expandedRows) => {
+    expandedRowKeys.value = expandedRows.map(r => r.date);
+  };
 
   // 淇濆吇浠诲姟tab鐩稿叧鍙橀噺
   const scheduledFilters = reactive({
@@ -513,6 +586,7 @@
 
   // 淇濆吇璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
   const getTableData = async () => {
+    tableLoading.value = true;
     try {
       const params = {
         current: pagination.value.currentPage,
@@ -534,6 +608,8 @@
       }
     } catch (error) {
       console.log(error);
+    } finally {
+      tableLoading.value = false;
     }
   };
 
@@ -546,13 +622,15 @@
   };
 
   const handleSelectionChange = selection => {
-    multipleList.value = selection;
+    // 浠庨�変腑鐨勫垎缁勮涓敹闆嗘墍鏈夊瓙璁板綍ID
+    const ids = [];
+    selection.forEach(group => {
+      if (group.children) {
+        group.children.forEach(child => ids.push(child.id));
+      }
+    });
+    multipleList.value = ids;
   };
-
-  // 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
-  const hasFinishedStatus = computed(() => {
-    return multipleList.value.some(item => item.status === 1);
-  });
 
   const changePage = page => {
     pagination.value.currentPage = page.page;
@@ -573,12 +651,8 @@
   };
 
   const delRepairByIds = async ids => {
-    // 妫�鏌ユ槸鍚︽湁瀹岀粨鐘舵�佺殑璁板綍
-    const hasFinished = multipleList.value.some(item => item.status === 1);
-    if (hasFinished) {
-      ElMessage.warning("涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�");
-      return;
-    }
+    const idList = Array.isArray(ids) ? ids : [ids];
+    if (idList.length === 0) return;
 
     try {
       await ElMessageBox.confirm("纭鍒犻櫎淇濆吇鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
@@ -587,7 +661,7 @@
         type: "warning",
       });
 
-      const { code } = await delUpkeep(ids);
+      const { code } = await delUpkeep(idList);
       if (code === 200) {
         ElMessage.success("鍒犻櫎鎴愬姛");
         getTableData();
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
index f4e49b6..3a06632 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
@@ -132,8 +132,11 @@
   },
 }
 
+const dateRange = inject('psiDateRange', null)
+
 const fetchData = () => {
-  productInOutAnalysis({ type: productType.value })
+  const params = { type: productType.value, ...(dateRange?.value || {}) }
+  productInOutAnalysis(params)
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         const list = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
index 6e39eb1..916c089 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
@@ -31,6 +31,8 @@
 import Echarts from '@/components/Echarts/echarts.vue'
 import { productTurnoverDays } from '@/api/viewIndex.js'
 
+const dateRange = inject('psiDateRange', null)
+
 const chartStyle = { width: '100%', height: '100%' }
 const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true }
 const barLegend = { show: false, textStyle: { color: '#B8C8E0' }, data: ['鍛ㄨ浆澶╂暟'] }
@@ -69,7 +71,7 @@
 const yAxis1 = [{ type: 'value', axisLabel: { color: '#B8C8E0' } }]
 
 const fetchData = () => {
-  productTurnoverDays()
+  productTurnoverDays(dateRange?.value || undefined)
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         const list = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
index 055fb66..d461f4f 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
@@ -36,12 +36,15 @@
 
 const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down')
 
+const dateRange = inject('psiDateRange', null)
+
 const fetchData = () => {
-  salesPurchaseStorageProductCount()
+  const params = dateRange?.value || {}
+  salesPurchaseStorageProductCount(params.startDate, params.endDate)
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         statItems.value = res.data.map((item) => ({
-          name: item.name,
+          name: item.name === '閿�鍞骇鍝佹暟' ? '閿�鍞鍗曟暟' : item.name,
           value: item.value,
           rate: item.rate,
         }))
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
index 65a72fe..b48f96a 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
@@ -29,6 +29,8 @@
 import { rawMaterialPurchaseAmountRatio } from '@/api/viewIndex.js'
 import { useChartBackground } from '@/hooks/useChartBackground.js'
 
+const dateRange = inject('psiDateRange', null)
+
 const pieWrapperRef = ref(null)
 const pieBackgroundRef = ref(null)
 
@@ -181,7 +183,7 @@
 })
 
 const fetchData = () => {
-  rawMaterialPurchaseAmountRatio()
+  rawMaterialPurchaseAmountRatio(dateRange?.value || undefined)
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         const items = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
index f5dac31..4fa1062 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
@@ -24,6 +24,8 @@
 import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue'
 import { productSalesAnalysis } from '@/api/viewIndex.js'
 import PanelHeader from './PanelHeader.vue'
+
+const dateRange = inject('psiDateRange', null)
 import CarouselCards from './CarouselCards.vue'
 import Echarts from '@/components/Echarts/echarts.vue'
 import { useChartBackground } from '@/hooks/useChartBackground.js'
@@ -153,7 +155,7 @@
 })
 
 const fetchData = () => {
-  productSalesAnalysis()
+  productSalesAnalysis(dateRange?.value || undefined)
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         const items = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/index.vue b/src/views/reportAnalysis/PSIDataAnalysis/index.vue
index f81e482..33a8b92 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/index.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/index.vue
@@ -13,7 +13,22 @@
 
     <!-- 椤堕儴鏍囬鏍� -->
     <div class="dashboard-header">
+      <div class="header-left"></div>
       <div class="factory-name">PSI 鏁版嵁鍒嗘瀽</div>
+      <div class="header-right">
+        <div class="date-picker-wrapper">
+          <el-date-picker
+            v-model="psiDateRange"
+            type="daterange"
+            range-separator="鑷�"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            value-format="YYYY-MM-DD"
+            :shortcuts="dateShortcuts"
+            @change="onDateRangeChange"
+          />
+        </div>
+      </div>
     </div>
 
     <!-- 涓昏鍐呭鍖哄煙 -->
@@ -43,8 +58,9 @@
 </template>
 
 <script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue'
+import { ref, onMounted, onBeforeUnmount, nextTick, provide, reactive } from 'vue'
 import autofit from 'autofit.js'
+import dayjs from 'dayjs'
 import LeftBottom from './components/left-bottom.vue'
 import CenterCenter from './components/center-center.vue'
 import RightTop from '../dataDashboard/components/basic/right-top.vue'
@@ -65,6 +81,27 @@
 
 // 鐢ㄦ埛store
 const userStore = useUserStore()
+
+// 鏃ユ湡鑼冨洿绛涢��
+const psiDateRange = ref([])
+const psiDateRangeParams = ref({})
+const dateShortcuts = [
+  { text: '鏈湀', value: () => { const start = dayjs().startOf('month').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+  { text: '杩�7澶�', value: () => { const start = dayjs().subtract(6, 'day').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+  { text: '杩�30澶�', value: () => { const start = dayjs().subtract(29, 'day').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+  { text: '杩�90澶�', value: () => { const start = dayjs().subtract(89, 'day').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+]
+
+const onDateRangeChange = (val) => {
+  if (val && val.length === 2) {
+    psiDateRangeParams.value = { startDate: val[0], endDate: val[1] }
+  } else {
+    psiDateRangeParams.value = {}
+  }
+  dataDashboardRefreshTick.value++
+}
+
+provide('psiDateRange', psiDateRangeParams)
 
 /** 涓� dataDashboard 鍏辩敤娉ㄥ叆鍚嶏紝瀛愮粍浠讹紙鍚鐢ㄧ殑 right-top/right-bottom锛夋瘡鍒嗛挓鍒锋柊 */
 const DASHBOARD_REFRESH_MS = 60 * 1000
@@ -237,15 +274,36 @@
 background-repeat: no-repeat;
 display: flex;
 align-items: center;
-justify-content: center;
+justify-content: space-between;
+}
+
+.header-left {
+width: 300px;
+padding-left: 20px;
+display: flex;
+align-items: center;
+}
+
+.header-right {
+width: 300px;
+display: flex;
+align-items: center;
+justify-content: flex-end;
+padding-right: 20px;
+}
+
+.date-picker-wrapper {
+/* 鏃ユ湡閫夋嫨鍣ㄥ湪澶у睆涓婄殑鏍峰紡 */
+--el-fill-color-blank: rgba(0, 20, 60, 0.8);
+--el-border-color: rgba(0, 212, 255, 0.3);
+--el-text-color-regular: #00d4ff;
+--el-color-primary: #00d4ff;
 }
 
 .factory-name {
 font-weight: 600;
 font-size: 52px;
 color: #FFFFFF;
-top: 16px;
-position: absolute;
 }
 
 .fullscreen-btn {
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue b/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
index 6fcd80f..7786c9a 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
@@ -28,6 +28,8 @@
 import DateTypeSwitch from '../DateTypeSwitch.vue'
 import { customerContributionRanking } from '@/api/viewIndex.js'
 
+const dateRange = inject('psiDateRange', null)
+
 const chartStyle = {
   width: '100%',
   height: '100%',
@@ -287,7 +289,7 @@
 }
 
 const fetchCustomerRanking = () => {
-  customerContributionRanking({ type: dateType.value })
+  customerContributionRanking({ type: dateType.value, ...(dateRange?.value || {}) })
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         dataArr.value = res.data.map(item => ({
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue b/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
index 96e4548..bfc0dea 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
@@ -27,6 +27,8 @@
 import DateTypeSwitch from '../DateTypeSwitch.vue'
 import { supplierPurchaseRanking } from '@/api/viewIndex.js'
 
+const dateRange = inject('psiDateRange', null)
+
 const chartStyle = {
   width: '100%',
   height: '100%',
@@ -295,7 +297,7 @@
 
 // 渚涘簲鍟嗛噰璐帓鍚�
 const fetchSupplierRanking = () => {
-  supplierPurchaseRanking({ type: radio1.value })
+  supplierPurchaseRanking({ type: radio1.value, ...(dateRange?.value || {}) })
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         dataArr.value = res.data.map(item => ({
diff --git a/src/views/reportAnalysis/taxComparison/index.vue b/src/views/reportAnalysis/taxComparison/index.vue
index d27ab07..c28d2f6 100644
--- a/src/views/reportAnalysis/taxComparison/index.vue
+++ b/src/views/reportAnalysis/taxComparison/index.vue
@@ -1,118 +1,411 @@
 <template>
-  <div class="app-container">
-    <el-form :model="filters" :inline="true">
-      <el-form-item label="鏃ユ湡">
-        <el-date-picker
-          style="width: 240px"
-          v-model="filters.month"
-          value-format="YYYY-MM"
-          format="YYYY-MM"
-          type="month"
-          placeholder="閫夋嫨鏈堜唤"
-          clearable
-          @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="getTableData"> 鎼滅储 </el-button>
-        <el-button @click="resetFilters"> 閲嶇疆 </el-button>
-        <el-button @click="handleOut"> 瀵煎嚭 </el-button>
-      </el-form-item>
-    </el-form>
-    <div class="table_list">
-      <PIMTable
-        rowKey="id"
-        :column="columns"
-        :tableData="dataList"
-        :page="{
-          current: pagination.currentPage,
-          size: pagination.pageSize,
-          total: pagination.total,
-        }"
-        @pagination="changePage"
-      />
-    </div>
+  <div class="app-container vat-page">
+    <el-row :gutter="20" class="vat-row">
+      <!-- 宸︿晶锛氬鍊肩◣鏄庣粏鍒楄〃 -->
+      <el-col :span="15" class="vat-col">
+        <el-card shadow="never" class="vat-card">
+          <template #header>
+            <div class="card-header">
+              <span class="card-title">澧炲�肩◣鏄庣粏</span>
+              <el-select v-model="queryMonth" placeholder="閫夋嫨鏈堜唤" clearable style="width: 150px;" @change="handleMonthChange">
+                <el-option v-for="m in monthOptions" :key="m" :label="m" :value="m" />
+              </el-select>
+            </div>
+          </template>
+          <template v-if="!loading && vatDetailList.length === 0">
+            <div class="empty-full">
+              <el-empty description="鏆傛棤澧炲�肩◣鏄庣粏鏁版嵁" :image-size="160" />
+            </div>
+          </template>
+          <template v-else>
+            <div class="table-wrapper">
+              <el-table :data="vatDetailList" border v-loading="loading" stripe height="100%">
+                <el-table-column prop="orderType" label="绫诲瀷" width="65" align="center">
+                  <template #default="scope">
+                    <el-tag :type="scope.row.orderType === '杩涢」' ? '' : 'warning'" size="small" effect="plain">
+                      {{ scope.row.orderType }}
+                    </el-tag>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="invoiceNo" label="鍙戠エ鍙�" min-width="140" show-overflow-tooltip />
+                <el-table-column prop="salesContractNo" label="鍚堝悓鍙�" min-width="140" show-overflow-tooltip />
+                <el-table-column prop="supplierName" label="渚涘簲鍟�" min-width="100" show-overflow-tooltip />
+                <el-table-column prop="customerName" label="瀹㈡埛" min-width="100" show-overflow-tooltip />
+                <el-table-column prop="invoiceDate" label="寮�绁ㄦ棩鏈�" width="110" align="center" />
+                <el-table-column prop="taxRate" label="绋庣巼" width="75" align="center">
+                  <template #default="scope">
+                    {{ scope.row.taxRate }}%
+                  </template>
+                </el-table-column>
+                <el-table-column prop="taxAmount" label="绋庨" width="110" align="right">
+                  <template #default="scope">
+                    <span class="tax-amount" :class="scope.row.orderType === '杩涢」' ? 'input-tax' : 'output-tax'">
+                      楼{{ formatNumber(scope.row.taxAmount) }}
+                    </span>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </div>
+            <el-pagination
+              v-if="page.total > 0"
+              class="vat-pagination"
+              background
+              layout="total, sizes, prev, pager, next, jumper"
+              :current-page="page.current"
+              :page-sizes="[10, 20, 50]"
+              :page-size="page.size"
+              :total="page.total"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange" />
+          </template>
+        </el-card>
+      </el-col>
+
+      <!-- 鍙充晶锛氭煴鐘跺姣斿浘 -->
+      <el-col :span="9" class="vat-col">
+        <el-card shadow="never" class="vat-card">
+          <template #header>
+            <span class="card-title">杩涢攢椤瑰鍊肩◣瀵规瘮</span>
+          </template>
+          <div class="summary-row">
+            <div class="summary-item input-bg">
+              <div class="summary-label">杩涢」绋庨鍚堣</div>
+              <div class="summary-value">楼{{ formatNumber(inputTotal) }}</div>
+            </div>
+            <div class="summary-item output-bg">
+              <div class="summary-label">閿�椤圭◣棰濆悎璁�</div>
+              <div class="summary-value">楼{{ formatNumber(outputTotal) }}</div>
+            </div>
+            <div class="summary-item diff-bg">
+              <div class="summary-label">宸</div>
+              <div class="summary-value" :class="diffValue >= 0 ? 'positive' : 'negative'">
+                {{ diffValue >= 0 ? '+' : '' }}楼{{ formatNumber(diffValue) }}
+              </div>
+            </div>
+          </div>
+          <div v-if="vatDetailList.length > 0" ref="vatChart" class="chart-container"></div>
+          <div v-else class="chart-empty">鏆傛棤鏁版嵁</div>
+        </el-card>
+      </el-col>
+    </el-row>
   </div>
 </template>
 
 <script setup>
-import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { onMounted, getCurrentInstance } from "vue";
-import { getTaxList } from "@/api/procurementManagement/taxComparison";
-import { ElMessageBox } from "element-plus";
-
-const { proxy } = getCurrentInstance();
+import { ref, reactive, computed, onMounted, nextTick, onBeforeUnmount } from "vue";
+import * as echarts from 'echarts';
+import { getVatDetail } from "@/api/procurementManagement/taxComparison";
 
 defineOptions({
   name: "澧炲�肩◣姣斿",
 });
 
-const {
-  loading,
-  filters,
-  columns,
-  dataList,
-  pagination,
-  getTableData,
-  resetFilters,
-  onCurrentChange,
-} = usePaginationApi(
-  getTaxList,
-  {
-    month: [], // 鏉ョエ鏃ユ湡
-  },
-  [
-    {
-      label: "鏈堜唤",
-      prop: "month",
-      align: "center",
-    },
-    {
-      label: "閿�椤圭◣棰�",
-      prop: "jtaxAmount",
-      align: "center",
-    },
-    {
-      label: "杩涢」绋庨",
-      prop: "xtaxAmount",
-      align: "center",
-    },
-    {
-      label: "閿�-杩�",
-      prop: "taxAmount",
-      align: "center",
-    },
-  ],
-  {}
-);
+const vatChart = ref(null);
+let chartInstance = null;
 
-const changePage = ({ page }) => {
-  pagination.currentPage = page;
-  onCurrentChange(page);
+const queryMonth = ref(new Date().toISOString().slice(0, 7));
+const monthOptions = ref([]);
+const vatDetailList = ref([]);
+const loading = ref(false);
+const page = reactive({ current: 1, size: 20, total: 0 });
+
+// 姹囨�婚
+const inputTotal = computed(() => {
+  return vatDetailList.value
+    .filter(i => i.orderType === '杩涢」')
+    .reduce((sum, i) => sum + (Number(i.taxAmount) || 0), 0);
+});
+const outputTotal = computed(() => {
+  return vatDetailList.value
+    .filter(i => i.orderType === '閿�椤�')
+    .reduce((sum, i) => sum + (Number(i.taxAmount) || 0), 0);
+});
+const diffValue = computed(() => outputTotal.value - inputTotal.value);
+
+const formatNumber = (val) => {
+  const num = Number(val);
+  if (isNaN(num)) return '0.00';
+  return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
 };
 
-// 瀵煎嚭
-const handleOut = () => {
-  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
-    confirmButtonText: "纭",
-    cancelButtonText: "鍙栨秷",
-    type: "warning",
-  })
-    .then(() => {
-      proxy.download("/purchase/report/exportTwo", {}, "澧炲�肩◣姣斿.xlsx");
+const loadVatDetail = () => {
+  loading.value = true;
+  const params = {
+    current: page.current,
+    size: page.size,
+    month: queryMonth.value || undefined,
+  };
+  getVatDetail(params)
+    .then((res) => {
+      if (res.code === 200) {
+        vatDetailList.value = res.data.records || [];
+        page.total = res.data.total || 0;
+        nextTick(() => renderChart());
+      }
     })
-    .catch(() => {
-      proxy.$modal.msg("宸插彇娑�");
+    .finally(() => {
+      loading.value = false;
     });
 };
 
+const renderChart = () => {
+  if (!vatChart.value) return;
+  if (chartInstance) {
+    chartInstance.dispose();
+  }
+  chartInstance = echarts.init(vatChart.value);
+
+  const data = vatDetailList.value;
+  const invoices = data.map(i => i.invoiceNo);
+
+  chartInstance.setOption({
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(255,255,255,0.95)',
+      borderColor: '#e0e0e0',
+      borderWidth: 1,
+      textStyle: { color: '#333', fontSize: 13 },
+      formatter: function (params) {
+        let html = `<b>${params[0].axisValue}</b><br/>`;
+        params.forEach(p => {
+          if (p.value > 0) {
+            html += `${p.marker} ${p.seriesName}: 楼${formatNumber(p.value)}<br/>`;
+          }
+        });
+        return html;
+      },
+    },
+    legend: {
+      data: ['杩涢」绋庨', '閿�椤圭◣棰�'],
+      bottom: 0,
+      textStyle: { fontSize: 12 },
+    },
+    grid: {
+      left: '10%',
+      right: '8%',
+      top: '8%',
+      bottom: '12%',
+    },
+    xAxis: {
+      type: 'category',
+      data: invoices,
+      axisLabel: {
+        rotate: 45,
+        fontSize: 10,
+        interval: 0,
+      },
+      axisTick: { alignWithLabel: true },
+    },
+    yAxis: {
+      type: 'value',
+      name: '绋庨(鍏�)',
+      nameTextStyle: { fontSize: 11 },
+      axisLabel: {
+        formatter: (val) => val >= 10000 ? `${(val / 10000).toFixed(1)}涓嘸 : val,
+      },
+    },
+    series: [
+      {
+        name: '杩涢」绋庨',
+        type: 'bar',
+        barWidth: '35%',
+        data: data.map(i => i.orderType === '杩涢」' ? Number(i.taxAmount) || 0 : null),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#667eea' },
+            { offset: 1, color: '#764ba2' },
+          ]),
+          borderRadius: [4, 4, 0, 0],
+        },
+        emphasis: {
+          itemStyle: { color: '#667eea' },
+        },
+      },
+      {
+        name: '閿�椤圭◣棰�',
+        type: 'bar',
+        barWidth: '35%',
+        data: data.map(i => i.orderType === '閿�椤�' ? Number(i.taxAmount) || 0 : null),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#f093fb' },
+            { offset: 1, color: '#f5576c' },
+          ]),
+          borderRadius: [4, 4, 0, 0],
+        },
+        emphasis: {
+          itemStyle: { color: '#f093fb' },
+        },
+      },
+    ],
+  });
+};
+
+const handleMonthChange = () => {
+  page.current = 1;
+  loadVatDetail();
+};
+
+const handleSizeChange = (val) => {
+  page.size = val;
+  loadVatDetail();
+};
+
+const handleCurrentChange = (val) => {
+  page.current = val;
+  loadVatDetail();
+};
+
+const generateMonthOptions = () => {
+  const now = new Date();
+  const options = [];
+  for (let i = 11; i >= 0; i--) {
+    const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
+    const y = d.getFullYear();
+    const m = String(d.getMonth() + 1).padStart(2, '0');
+    options.push(`${y}-${m}`);
+  }
+  monthOptions.value = options;
+};
+
+const handleResize = () => {
+  if (chartInstance) {
+    chartInstance.resize();
+  }
+};
+
 onMounted(() => {
-  getTableData();
+  generateMonthOptions();
+  loadVatDetail();
+  window.addEventListener('resize', handleResize);
+});
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', handleResize);
+  if (chartInstance) {
+    chartInstance.dispose();
+  }
 });
 </script>
 
 <style lang="scss" scoped>
-.table_list {
-  margin-top: unset;
+.vat-page {
+  height: calc(100vh - 84px);
 }
-</style>
+
+.vat-row {
+  height: 100%;
+}
+
+.vat-col {
+  height: 100%;
+}
+
+.vat-card {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  :deep(.el-card__header) {
+    flex-shrink: 0;
+  }
+  :deep(.el-card__body) {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.card-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.table-wrapper {
+  flex: 1;
+  overflow: hidden;
+}
+
+.vat-pagination {
+  flex-shrink: 0;
+  margin-top: 12px;
+  justify-content: flex-end;
+}
+
+.tax-amount {
+  font-weight: 600;
+  font-family: 'Monaco', 'Menlo', monospace;
+  &.input-tax { color: #667eea; }
+  &.output-tax { color: #f5576c; }
+}
+
+.summary-row {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 16px;
+  flex-shrink: 0;
+
+  .summary-item {
+    flex: 1;
+    padding: 12px 14px;
+    border-radius: 8px;
+    text-align: center;
+
+    &.input-bg {
+      background: linear-gradient(135deg, #f3f0ff 0%, #e8e5ff 100%);
+      .summary-value { color: #667eea; }
+    }
+    &.output-bg {
+      background: linear-gradient(135deg, #fff0f3 0%, #ffe0e6 100%);
+      .summary-value { color: #f5576c; }
+    }
+    &.diff-bg {
+      background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+      .summary-value {
+        &.positive { color: #f5576c; }
+        &.negative { color: #22c55e; }
+      }
+    }
+
+    .summary-label {
+      font-size: 12px;
+      color: #909399;
+      margin-bottom: 4px;
+    }
+    .summary-value {
+      font-size: 18px;
+      font-weight: 700;
+      font-family: 'Monaco', 'Menlo', monospace;
+    }
+  }
+}
+
+.chart-container {
+  flex: 1;
+  min-height: 0;
+}
+
+.empty-full {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.chart-empty {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #c0c4cc;
+  font-size: 13px;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3