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

---
 src/views/equipmentManagement/upkeep/index.vue | 1282 ++++++++++++++++++++++++++++++---------------------------
 1 files changed, 678 insertions(+), 604 deletions(-)

diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 245a162..9b92281 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -1,652 +1,726 @@
 <template>
-  <div class="app-container">
-    <el-tabs v-model="activeTab"
-             @tab-change="handleTabChange">
-      <!-- 淇濆吇浠诲姟tab -->
-      <el-tab-pane label="淇濆吇浠诲姟"
-                   name="scheduled">
-        <div class="search_form">
-          <el-form :model="scheduledFilters"
-                   :inline="true">
-            <el-form-item label="浠诲姟鍚嶇О">
-              <el-input v-model="scheduledFilters.taskName"
-                        style="width: 240px"
-                        placeholder="璇疯緭鍏ヤ换鍔″悕绉�"
-                        clearable
-                        :prefix-icon="Search"
-                        @change="getScheduledTableData" />
-            </el-form-item>
-            <el-form-item label="浠诲姟鐘舵��">
-              <el-select v-model="scheduledFilters.status"
-                         placeholder="璇烽�夋嫨浠诲姟鐘舵��"
-                         clearable
-                         style="width: 200px">
-                <el-option label="鍚敤"
-                           value="1" />
-                <el-option label="鍋滅敤"
-                           value="0" />
-              </el-select>
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary"
-                         @click="getScheduledTableData">鎼滅储</el-button>
-              <el-button @click="resetScheduledFilters">閲嶇疆</el-button>
-            </el-form-item>
-          </el-form>
-        </div>
-        <div class="table_list">
-          <div class="actions">
-            <el-text class="mx-1"
-                     size="large">淇濆吇浠诲姟</el-text>
-            <div>
-              <el-button type="primary"
-                         icon="Plus"
-                         @click="addScheduledTask">
-                鏂板浠诲姟
-              </el-button>
-              <el-button type="danger"
-                         icon="Delete"
-                         :disabled="scheduledMultipleList.length <= 0"
-                         @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))">
-                鎵归噺鍒犻櫎
-              </el-button>
-            </div>
-          </div>
-          <PIMTable rowKey="id"
-                    isSelection
-                    :column="scheduledColumns"
-                    :tableData="scheduledDataList"
-                    :page="{
+	<div class="app-container">
+		<el-tabs v-model="activeTab"
+		         @tab-change="handleTabChange">
+			<!-- 淇濆吇浠诲姟tab -->
+			<el-tab-pane label="淇濆吇浠诲姟"
+			             name="scheduled">
+				<div class="search_form">
+					<el-form :model="scheduledFilters"
+					         :inline="true">
+						<el-form-item label="浠诲姟鍚嶇О">
+							<el-input v-model="scheduledFilters.taskName"
+							          style="width: 240px"
+							          placeholder="璇疯緭鍏ヤ换鍔″悕绉�"
+							          clearable
+							          :prefix-icon="Search"
+							          @change="getScheduledTableData" />
+						</el-form-item>
+						<el-form-item label="浠诲姟鐘舵��">
+							<el-select v-model="scheduledFilters.status"
+							           placeholder="璇烽�夋嫨浠诲姟鐘舵��"
+							           clearable
+							           style="width: 200px">
+								<el-option label="鍚敤"
+								           value="1" />
+								<el-option label="鍋滅敤"
+								           value="0" />
+							</el-select>
+						</el-form-item>
+						<el-form-item>
+							<el-button type="primary"
+							           @click="getScheduledTableData">鎼滅储</el-button>
+							<el-button @click="resetScheduledFilters">閲嶇疆</el-button>
+						</el-form-item>
+					</el-form>
+				</div>
+				<div class="table_list">
+					<div class="actions">
+						<el-text class="mx-1"
+						         size="large">淇濆吇浠诲姟</el-text>
+						<div>
+							<el-button type="primary"
+							           icon="Plus"
+							           @click="addScheduledTask">
+								鏂板浠诲姟
+							</el-button>
+							<el-button type="danger"
+							           icon="Delete"
+							           :disabled="scheduledMultipleList.length <= 0"
+							           @click="delScheduledTaskByIds(scheduledMultipleList.map((item) => item.id))">
+								鎵归噺鍒犻櫎
+							</el-button>
+						</div>
+					</div>
+					<PIMTable rowKey="id"
+					          isSelection
+					          :column="scheduledColumns"
+					          :tableData="scheduledDataList"
+					          :page="{
               current: scheduledPagination.currentPage,
               size: scheduledPagination.pageSize,
               total: scheduledPagination.total,
             }"
-                    @selection-change="handleScheduledSelectionChange"
-                    @pagination="changeScheduledPage">
-            <template #statusRef="{ row }">
-              <el-tag v-if="row.status === 1"
-                      type="success">鍚敤</el-tag>
-              <el-tag v-if="row.status === 0"
-                      type="danger">鍋滅敤</el-tag>
-            </template>
-            <template #operation="{ row }">
-              <el-button type="primary"
-                         link
-                         @click="editScheduledTask(row)">
-                缂栬緫
-              </el-button>
-              <el-button type="danger"
-                         link
-                         @click="delScheduledTaskByIds(row.id)">
-                鍒犻櫎
-              </el-button>
-            </template>
-          </PIMTable>
-        </div>
-      </el-tab-pane>
-      <!-- 淇濆吇璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛� -->
-      <el-tab-pane label="淇濆吇璁板綍"
-                   name="record">
-        <div class="search_form">
-          <el-form :model="filters"
-                   :inline="true">
-            <el-form-item label="璁惧鍚嶇О">
-              <el-input v-model="filters.deviceName"
-                        style="width: 240px"
-                        placeholder="璇疯緭鍏ヨ澶囧悕绉�"
-                        clearable
-                        :prefix-icon="Search"
-                        @change="getTableData" />
-            </el-form-item>
-            <el-form-item label="璁″垝淇濆吇鏃ユ湡">
-              <el-date-picker v-model="filters.maintenancePlanTime"
-                              type="date"
-                              placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡"
-                              size="default"
-                              @change="(date) => handleDateChange(date,2)" />
-            </el-form-item>
-            <el-form-item label="瀹為檯淇濆吇鏃ユ湡">
-              <el-date-picker v-model="filters.maintenanceActuallyTime"
-                              type="date"
-                              placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
-                              size="default"
-                              @change="(date) => handleDateChange(date,1)" />
-            </el-form-item>
-            <el-form-item label="瀹為檯淇濆吇浜�">
-              <el-input v-model="filters.maintenanceActuallyName"
-                        style="width: 240px"
-                        placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
-                        clearable
-                        :prefix-icon="Search"
-                        @change="getTableData" />
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary"
-                         @click="getTableData">鎼滅储</el-button>
-              <el-button @click="resetFilters">閲嶇疆</el-button>
-            </el-form-item>
-          </el-form>
-        </div>
-        <div class="table_list">
-          <div class="actions">
-            <el-text class="mx-1"
-                     size="large">淇濆吇璁板綍</el-text>
-            <div>
-              <el-button type="success"
-                         icon="Van"
-                         @click="addPlan">
-                鏂板璁″垝
-              </el-button>
-              <el-button @click="handleOut">
-                瀵煎嚭
-              </el-button>
-              <el-button type="danger"
-                         icon="Delete"
-                         :disabled="multipleList.length <= 0 || hasFinishedStatus"
-                         @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-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>
-        </div>
-      </el-tab-pane>
-    </el-tabs>
-    <PlanModal ref="planModalRef"
-               @ok="getTableData" />
-    <MaintenanceModal ref="maintainModalRef"
-                      @ok="getTableData" />
-    <FormDia ref="formDiaRef"
-             @closeDia="getScheduledTableData" />
-    <FileList v-if="fileDialogVisible"
-              v-model:visible="fileDialogVisible"
-              :record-type="'device_maintenance'"
-              :record-id="currentMaintenanceTaskId" />
-  </div>
+					          @selection-change="handleScheduledSelectionChange"
+					          @pagination="changeScheduledPage">
+						<template #statusRef="{ row }">
+							<el-tag v-if="row.status === 1"
+							        type="success">鍚敤</el-tag>
+							<el-tag v-if="row.status === 0"
+							        type="danger">鍋滅敤</el-tag>
+						</template>
+						<template #operation="{ row }">
+							<el-button type="primary"
+							           link
+							           @click="editScheduledTask(row)">
+								缂栬緫
+							</el-button>
+							<el-button type="danger"
+							           link
+							           @click="delScheduledTaskByIds(row.id)">
+								鍒犻櫎
+							</el-button>
+						</template>
+					</PIMTable>
+				</div>
+			</el-tab-pane>
+			<!-- 淇濆吇璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛� -->
+			<el-tab-pane label="淇濆吇璁板綍"
+			             name="record">
+				<div class="search_form">
+					<el-form :model="filters"
+					         :inline="true">
+						<el-form-item label="璁惧鍚嶇О">
+							<el-input v-model="filters.deviceName"
+							          style="width: 240px"
+							          placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+							          clearable
+							          :prefix-icon="Search"
+							          @change="getTableData" />
+						</el-form-item>
+						<el-form-item label="璁″垝淇濆吇鏃ユ湡">
+							<el-date-picker v-model="filters.maintenancePlanTime"
+							                type="date"
+							                placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡"
+							                size="default"
+							                @change="(date) => handleDateChange(date,2)" />
+						</el-form-item>
+						<el-form-item label="瀹為檯淇濆吇鏃ユ湡">
+							<el-date-picker v-model="filters.maintenanceActuallyTime"
+							                type="date"
+							                placeholder="璇烽�夋嫨瀹為檯淇濆吇鏃ユ湡"
+							                size="default"
+							                @change="(date) => handleDateChange(date,1)" />
+						</el-form-item>
+						<el-form-item label="瀹為檯淇濆吇浜�">
+							<el-input v-model="filters.maintenanceActuallyName"
+							          style="width: 240px"
+							          placeholder="璇疯緭鍏ュ疄闄呬繚鍏讳汉"
+							          clearable
+							          :prefix-icon="Search"
+							          @change="getTableData" />
+						</el-form-item>
+						<el-form-item>
+							<el-button type="primary"
+							           @click="getTableData">鎼滅储</el-button>
+							<el-button @click="resetFilters">閲嶇疆</el-button>
+						</el-form-item>
+					</el-form>
+				</div>
+				<div class="table_list">
+					<div class="actions">
+						<el-text class="mx-1"
+						         size="large">淇濆吇璁板綍</el-text>
+						<div>
+							<el-button type="success"
+							           icon="Van"
+							           @click="addPlan">
+								鏂板璁″垝
+							</el-button>
+							<el-button @click="handleOut">
+								瀵煎嚭
+							</el-button>
+							<el-button type="danger"
+							           icon="Delete"
+							           :disabled="multipleList.length <= 0"
+							           @click="delRepairByIds(multipleList.map((item) => item.id))">
+								鎵归噺鍒犻櫎
+							</el-button>
+						</div>
+					</div>
+					<el-table
+						:data="groupedDataList"
+						border
+						v-loading="tableLoading"
+						@selection-change="handleSelectionChange"
+						:expand-row-keys="expandedRowKeys"
+						:row-key="(row) => row.date"
+						@expand-change="onExpandChange"
+					>
+						<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>
+		<PlanModal ref="planModalRef"
+		           @ok="getTableData" />
+		<MaintenanceModal ref="maintainModalRef"
+		                  @ok="getTableData" />
+		<FormDia ref="formDiaRef"
+		         @closeDia="getScheduledTableData" />
+		<FileList v-if="fileDialogVisible"
+		          v-model:visible="fileDialogVisible"
+		          :record-type="'device_maintenance'"
+		          :record-id="currentMaintenanceTaskId" />
+	
+	</div>
 </template>
 
 <script setup>
-  import {
-    ref,
-    onMounted,
-    reactive,
-    getCurrentInstance,
-    nextTick,
-    computed,
-    defineAsyncComponent,
-  } from "vue";
-  import { Search } from "@element-plus/icons-vue";
-  import { ElMessage, ElMessageBox } from "element-plus";
-  import PlanModal from "./Form/PlanModal.vue";
-  import MaintenanceModal from "./Form/MaintenanceModal.vue";
-  import FormDia from "./Form/formDia.vue";
-  import {
-    getUpkeepPage,
-    delUpkeep,
-    deviceMaintenanceTaskList,
-    deviceMaintenanceTaskDel,
-  } from "@/api/equipmentManagement/upkeep";
-  import dayjs from "dayjs";
+import {
+	ref,
+	onMounted,
+	reactive,
+	getCurrentInstance,
+	nextTick,
+	computed,
+	defineAsyncComponent,
+} from "vue";
+import { Search } from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import PlanModal from "./Form/PlanModal.vue";
+import MaintenanceModal from "./Form/MaintenanceModal.vue";
+import FormDia from "./Form/formDia.vue";
+import {
+	getUpkeepPage,
+	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(() =>
-    import("@/components/Dialog/FileList.vue")
-  );
+const { proxy } = getCurrentInstance();
+const FileList = defineAsyncComponent(() =>
+	import("@/components/Dialog/FileList.vue")
+);
 
-  // Tab鐩稿叧
-  const activeTab = ref("scheduled");
+// Tab鐩稿叧
+const activeTab = ref("scheduled");
 
-  // 璁″垝寮圭獥鎺у埗鍣�
-  const planModalRef = ref();
-  // 淇濆吇寮圭獥鎺у埗鍣�
-  const maintainModalRef = ref();
-  // 瀹氭椂浠诲姟寮圭獥鎺у埗鍣�
-  const formDiaRef = ref();
-  // 闄勪欢寮圭獥
-  const fileListDialogRef = ref(null);
-  const fileDialogVisible = ref(false);
-  const currentMaintenanceTaskId = ref(null);
+// 璁″垝寮圭獥鎺у埗鍣�
+const planModalRef = ref();
+// 淇濆吇寮圭獥鎺у埗鍣�
+const maintainModalRef = ref();
+// 瀹氭椂浠诲姟寮圭獥鎺у埗鍣�
+const formDiaRef = ref();
+// 闄勪欢寮圭獥
+const fileListDialogRef = ref(null);
+const fileDialogVisible = ref(false);
+const currentMaintenanceTaskId = ref(null);
 
-  // 淇濆吇璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
-  const filters = reactive({
-    deviceName: "",
-    maintenancePlanTime: "",
-    maintenanceActuallyTime: "",
-    maintenanceActuallyName: "",
-  });
+// 淇濆吇璁板綍tab锛堝師璁惧淇濆吇椤甸潰锛夌浉鍏冲彉閲�
+const filters = reactive({
+	deviceName: "",
+	maintenancePlanTime: "",
+	maintenanceActuallyTime: "",
+	maintenanceActuallyName: "",
+});
 
-  const dataList = ref([]);
-  const pagination = ref({
-    currentPage: 1,
-    pageSize: 10,
-    total: 0,
-  });
-  const multipleList = ref([]);
+const dataList = ref([]);
+const pagination = ref({
+	currentPage: 1,
+	pageSize: 100,
+	total: 0,
+});
+const multipleList = ref([]);
+const tableLoading = ref(false);
+const expandedRowKeys = ref([]);
 
-  // 淇濆吇浠诲姟tab鐩稿叧鍙橀噺
-  const scheduledFilters = reactive({
-    taskName: "",
-    status: "",
-  });
+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 scheduledDataList = ref([]);
-  const scheduledPagination = reactive({
-    currentPage: 1,
-    pageSize: 10,
-    total: 0,
-  });
-  const scheduledMultipleList = ref([]);
+const groupedDataList = computed(() => groupByDate(dataList.value));
 
-  // 淇濆吇浠诲姟琛ㄦ牸鍒楅厤缃�
-  const scheduledColumns = ref([
-    { prop: "taskName", label: "璁惧鍚嶇О" },
-    {
-      label: "瑙勬牸鍨嬪彿",
-      prop: "deviceModel",
-    },
-    {
-      label: "淇濆吇椤圭洰",
-      prop: "machineryCategory",
-      minWidth: 120,
-      formatData: cell => cell || "--",
-    },
-    {
-      prop: "frequencyType",
-      label: "棰戞",
-      minWidth: 150,
-      // PIMTable 浣跨敤鐨勬槸 formatData锛岃�屼笉鏄� Element-Plus 鐨� formatter
-      formatData: cell =>
-        ({
-          DAILY: "姣忔棩",
-          WEEKLY: "姣忓懆",
-          MONTHLY: "姣忔湀",
-          QUARTERLY: "瀛e害",
-        }[cell] || ""),
-    },
-    {
-      prop: "frequencyDetail",
-      label: "寮�濮嬫棩鏈熶笌鏃堕棿",
-      minWidth: 150,
-      // 鍚屾牱鏀圭敤 formatData锛孭IMTable 鍐呴儴浼氭妸鍗曞厓鏍煎�间紶杩涙潵
-      formatData: cell => {
-        if (typeof cell !== "string") return "";
-        let val = cell;
-        const replacements = {
-          MON: "鍛ㄤ竴",
-          TUE: "鍛ㄤ簩",
-          WED: "鍛ㄤ笁",
-          THU: "鍛ㄥ洓",
-          FRI: "鍛ㄤ簲",
-          SAT: "鍛ㄥ叚",
-          SUN: "鍛ㄦ棩",
-        };
-        // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
-        return val.replace(
-          /MON|TUE|WED|THU|FRI|SAT|SUN/g,
-          match => replacements[match]
-        );
-      },
-    },
-    { prop: "maintenancePerson", label: "淇濆吇浜�", minWidth: 100 },
-    { prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
-    {
-      prop: "registrationDate",
-      label: "鐧昏鏃ユ湡",
-      minWidth: 100,
-      formatData: cell =>
-        cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
-    },
-    {
-      fixed: "right",
-      label: "鎿嶄綔",
-      dataType: "slot",
-      slot: "operation",
-      align: "center",
-      width: "200px",
-    },
-  ]);
+// 鍗曟潯淇濆吇瀹屾垚
+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 columns = ref([
-    {
-      label: "璁惧鍚嶇О",
-      align: "center",
-      prop: "deviceName",
-    },
-    {
-      label: "瑙勬牸鍨嬪彿",
-      align: "center",
-      prop: "deviceModel",
-    },
-    {
-      label: "璁″垝淇濆吇鏃ユ湡",
-      align: "center",
-      prop: "maintenancePlanTime",
-      formatData: cell => {
-        return cell == null ? "-" : dayjs(cell).format("YYYY-MM-DD");
-      },
-    },
-    {
-      label: "褰曞叆浜�",
-      align: "center",
-      prop: "createUserName",
-    },
-    {
-      label: "淇濆吇椤圭洰",
-      align: "center",
-      prop: "machineryCategory",
-      formatData: cell => cell || "--",
-    },
-    // {
-    //   label: "褰曞叆鏃ユ湡",
-    //   align: "center",
-    //   prop: "createTime",
-    //   formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
-    //   width: 200,
-    // },
-    {
-      label: "瀹為檯淇濆吇浜�",
-      align: "center",
-      prop: "maintenanceActuallyName",
-    },
-    {
-      label: "瀹為檯淇濆吇鏃ユ湡",
-      align: "center",
-      prop: "maintenanceActuallyTime",
-      formatData: cell =>
-        cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
-    },
-    {
-      label: "淇濆吇缁撴灉",
-      align: "center",
-      prop: "maintenanceResult",
-      dataType: "slot",
-      slot: "maintenanceResultRef",
-    },
-    {
-      label: "鐘舵��",
-      align: "center",
-      prop: "status",
-      dataType: "slot",
-      slot: "statusRef",
-    },
-    {
-      fixed: "right",
-      label: "鎿嶄綔",
-      dataType: "slot",
-      slot: "operation",
-      align: "center",
-      width: "350px",
-    },
-  ]);
+// 涓�閿繚鍏诲畬鎴�
+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 { /* 鍙栨秷 */ }
+};
 
-  // Tab鍒囨崲澶勭悊
-  const handleTabChange = tabName => {
-    if (tabName === "record") {
-      getTableData();
-    } else if (tabName === "scheduled") {
-      getScheduledTableData();
-    }
-  };
+const onExpandChange = (row, expandedRows) => {
+	expandedRowKeys.value = expandedRows.map(r => r.date);
+};
 
-  // 淇濆吇浠诲姟鐩稿叧鏂规硶
-  const getScheduledTableData = async () => {
-    try {
-      const params = {
-        current: scheduledPagination.currentPage,
-        size: scheduledPagination.pageSize,
-        taskName: scheduledFilters.taskName || undefined,
-        status: scheduledFilters.status || undefined,
-      };
-      const { code, data } = await deviceMaintenanceTaskList(params);
-      if (code === 200) {
-        scheduledDataList.value = data?.records || [];
-        scheduledPagination.total = data?.total || 0;
-      }
-    } catch (error) {
-      ElMessage.error("鑾峰彇瀹氭椂浠诲姟鍒楄〃澶辫触");
-    }
-  };
+// 淇濆吇浠诲姟tab鐩稿叧鍙橀噺
+const scheduledFilters = reactive({
+	taskName: "",
+	status: "",
+});
 
-  const resetScheduledFilters = () => {
-    scheduledFilters.taskName = "";
-    scheduledFilters.status = "";
-    getScheduledTableData();
-  };
+const scheduledDataList = ref([]);
+const scheduledPagination = reactive({
+	currentPage: 1,
+	pageSize: 10,
+	total: 0,
+});
+const scheduledMultipleList = ref([]);
 
-  const handleScheduledSelectionChange = selection => {
-    scheduledMultipleList.value = selection;
-  };
+// 淇濆吇浠诲姟琛ㄦ牸鍒楅厤缃�
+const scheduledColumns = ref([
+	{ prop: "taskName", label: "璁惧鍚嶇О" },
+	{
+		label: "瑙勬牸鍨嬪彿",
+		prop: "deviceModel",
+	},
+	{
+		label: "淇濆吇椤圭洰",
+		prop: "machineryCategory",
+		minWidth: 120,
+		formatData: cell => cell || "--",
+	},
+	{
+		prop: "frequencyType",
+		label: "棰戞",
+		minWidth: 150,
+		// PIMTable 浣跨敤鐨勬槸 formatData锛岃�屼笉鏄� Element-Plus 鐨� formatter
+		formatData: cell =>
+			({
+				DAILY: "姣忔棩",
+				WEEKLY: "姣忓懆",
+				MONTHLY: "姣忔湀",
+				QUARTERLY: "瀛e害",
+			}[cell] || ""),
+	},
+	{
+		prop: "frequencyDetail",
+		label: "寮�濮嬫棩鏈熶笌鏃堕棿",
+		minWidth: 150,
+		// 鍚屾牱鏀圭敤 formatData锛孭IMTable 鍐呴儴浼氭妸鍗曞厓鏍煎�间紶杩涙潵
+		formatData: cell => {
+			if (typeof cell !== "string") return "";
+			let val = cell;
+			const replacements = {
+				MON: "鍛ㄤ竴",
+				TUE: "鍛ㄤ簩",
+				WED: "鍛ㄤ笁",
+				THU: "鍛ㄥ洓",
+				FRI: "鍛ㄤ簲",
+				SAT: "鍛ㄥ叚",
+				SUN: "鍛ㄦ棩",
+			};
+			// 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
+			return val.replace(
+				/MON|TUE|WED|THU|FRI|SAT|SUN/g,
+				match => replacements[match]
+			);
+		},
+	},
+	{ prop: "maintenancePerson", label: "淇濆吇浜�", minWidth: 100 },
+	{ prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
+	{
+		prop: "registrationDate",
+		label: "鐧昏鏃ユ湡",
+		minWidth: 100,
+		formatData: cell =>
+			cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
+	},
+	{
+		fixed: "right",
+		label: "鎿嶄綔",
+		dataType: "slot",
+		slot: "operation",
+		align: "center",
+		width: "200px",
+	},
+]);
 
-  const changeScheduledPage = page => {
-    scheduledPagination.currentPage = page.page;
-    scheduledPagination.pageSize = page.limit;
-    getScheduledTableData();
-  };
+// 淇濆吇璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
+const columns = ref([
+	{
+		label: "璁惧鍚嶇О",
+		align: "center",
+		prop: "deviceName",
+	},
+	{
+		label: "瑙勬牸鍨嬪彿",
+		align: "center",
+		prop: "deviceModel",
+	},
+	{
+		label: "璁″垝淇濆吇鏃ユ湡",
+		align: "center",
+		prop: "maintenancePlanTime",
+		formatData: cell => {
+			return cell == null ? "-" : dayjs(cell).format("YYYY-MM-DD");
+		},
+	},
+	{
+		label: "褰曞叆浜�",
+		align: "center",
+		prop: "createUserName",
+	},
+	{
+		label: "淇濆吇椤圭洰",
+		align: "center",
+		prop: "machineryCategory",
+		formatData: cell => cell || "--",
+	},
+	// {
+	//   label: "褰曞叆鏃ユ湡",
+	//   align: "center",
+	//   prop: "createTime",
+	//   formatData: (cell) => dayjs(cell).format("YYYY-MM-DD HH:mm:ss"),
+	//   width: 200,
+	// },
+	{
+		label: "瀹為檯淇濆吇浜�",
+		align: "center",
+		prop: "maintenanceActuallyName",
+	},
+	{
+		label: "瀹為檯淇濆吇鏃ユ湡",
+		align: "center",
+		prop: "maintenanceActuallyTime",
+		formatData: cell =>
+			cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-",
+	},
+	{
+		label: "淇濆吇缁撴灉",
+		align: "center",
+		prop: "maintenanceResult",
+		dataType: "slot",
+		slot: "maintenanceResultRef",
+	},
+	{
+		label: "鐘舵��",
+		align: "center",
+		prop: "status",
+		dataType: "slot",
+		slot: "statusRef",
+	},
+	{
+		fixed: "right",
+		label: "鎿嶄綔",
+		dataType: "slot",
+		slot: "operation",
+		align: "center",
+		width: "350px",
+	},
+]);
 
-  const addScheduledTask = () => {
-    nextTick(() => {
-      formDiaRef.value?.openDialog("add");
-    });
-  };
+// Tab鍒囨崲澶勭悊
+const handleTabChange = tabName => {
+	if (tabName === "record") {
+		getTableData();
+	} else if (tabName === "scheduled") {
+		getScheduledTableData();
+	}
+};
 
-  const editScheduledTask = row => {
-    if (row) {
-      nextTick(() => {
-        formDiaRef.value?.openDialog("edit", row);
-      });
-    }
-  };
+// 淇濆吇浠诲姟鐩稿叧鏂规硶
+const getScheduledTableData = async () => {
+	try {
+		const params = {
+			current: scheduledPagination.currentPage,
+			size: scheduledPagination.pageSize,
+			taskName: scheduledFilters.taskName || undefined,
+			status: scheduledFilters.status || undefined,
+		};
+		const { code, data } = await deviceMaintenanceTaskList(params);
+		if (code === 200) {
+			scheduledDataList.value = data?.records || [];
+			scheduledPagination.total = data?.total || 0;
+		}
+	} catch (error) {
+		ElMessage.error("鑾峰彇瀹氭椂浠诲姟鍒楄〃澶辫触");
+	}
+};
 
-  const delScheduledTaskByIds = async ids => {
-    try {
-      await ElMessageBox.confirm("纭畾鍒犻櫎閫変腑鐨勫畾鏃朵换鍔″悧锛�", "鎻愮ず", {
-        type: "warning",
-      });
-      const payload = Array.isArray(ids) ? ids : [ids];
-      await deviceMaintenanceTaskDel(payload);
-      ElMessage.success("鍒犻櫎瀹氭椂浠诲姟鎴愬姛");
-      getScheduledTableData();
-    } catch (error) {
-      // 鐢ㄦ埛鍙栨秷鍒犻櫎
-    }
-  };
+const resetScheduledFilters = () => {
+	scheduledFilters.taskName = "";
+	scheduledFilters.status = "";
+	getScheduledTableData();
+};
 
-  const handleScheduledOut = () => {
-    ElMessage.info("瀵煎嚭瀹氭椂浠诲姟鍔熻兘寰呭疄鐜�");
-  };
+const handleScheduledSelectionChange = selection => {
+	scheduledMultipleList.value = selection;
+};
 
-  // 淇濆吇璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
-  const getTableData = async () => {
-    try {
-      const params = {
-        current: pagination.value.currentPage,
-        size: pagination.value.pageSize,
-        deviceName: filters.deviceName || undefined,
-        maintenancePlanTime: filters.maintenancePlanTime
-          ? dayjs(filters.maintenancePlanTime).format("YYYY-MM-DD")
-          : undefined,
-        maintenanceActuallyTime: filters.maintenanceActuallyTime
-          ? dayjs(filters.maintenanceActuallyTime).format("YYYY-MM-DD")
-          : undefined,
-        maintenanceActuallyName: filters.maintenanceActuallyName || undefined,
-      };
+const changeScheduledPage = page => {
+	scheduledPagination.currentPage = page.page;
+	scheduledPagination.pageSize = page.limit;
+	getScheduledTableData();
+};
 
-      const { code, data } = await getUpkeepPage(params);
-      if (code === 200) {
-        dataList.value = data.records;
-        pagination.value.total = data.total;
-      }
-    } catch (error) {
-      console.log(error);
-    }
-  };
+const addScheduledTask = () => {
+	nextTick(() => {
+		formDiaRef.value?.openDialog("add");
+	});
+};
 
-  const resetFilters = () => {
-    filters.deviceName = "";
-    filters.maintenancePlanTime = "";
-    filters.maintenanceActuallyTime = "";
-    filters.maintenanceActuallyName = "";
-    getTableData();
-  };
+const editScheduledTask = row => {
+	if (row) {
+		nextTick(() => {
+			formDiaRef.value?.openDialog("edit", row);
+		});
+	}
+};
 
-  const handleSelectionChange = selection => {
-    multipleList.value = selection;
-  };
+const delScheduledTaskByIds = async ids => {
+	try {
+		await ElMessageBox.confirm("纭畾鍒犻櫎閫変腑鐨勫畾鏃朵换鍔″悧锛�", "鎻愮ず", {
+			type: "warning",
+		});
+		const payload = Array.isArray(ids) ? ids : [ids];
+		await deviceMaintenanceTaskDel(payload);
+		ElMessage.success("鍒犻櫎瀹氭椂浠诲姟鎴愬姛");
+		getScheduledTableData();
+	} catch (error) {
+		// 鐢ㄦ埛鍙栨秷鍒犻櫎
+	}
+};
 
-  // 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
-  const hasFinishedStatus = computed(() => {
-    return multipleList.value.some(item => item.status === 1);
-  });
+const handleScheduledOut = () => {
+	ElMessage.info("瀵煎嚭瀹氭椂浠诲姟鍔熻兘寰呭疄鐜�");
+};
 
-  const changePage = page => {
-    pagination.value.currentPage = page.page;
-    pagination.value.pageSize = page.limit;
-    getTableData();
-  };
+// 淇濆吇璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
+const getTableData = async () => {
+	tableLoading.value = true;
+	try {
+		const params = {
+			current: pagination.value.currentPage,
+			size: pagination.value.pageSize,
+			deviceName: filters.deviceName || undefined,
+			maintenancePlanTime: filters.maintenancePlanTime
+				? dayjs(filters.maintenancePlanTime).format("YYYY-MM-DD")
+				: undefined,
+			maintenanceActuallyTime: filters.maintenanceActuallyTime
+				? dayjs(filters.maintenanceActuallyTime).format("YYYY-MM-DD")
+				: undefined,
+			maintenanceActuallyName: filters.maintenanceActuallyName || undefined,
+		};
+		
+		const { code, data } = await getUpkeepPage(params);
+		if (code === 200) {
+			dataList.value = data.records;
+			pagination.value.total = data.total;
+		}
+	} catch (error) {
+		console.log(error);
+	} finally {
+		tableLoading.value = false;
+	}
+};
 
-  const addMaintain = row => {
-    maintainModalRef.value.open(row.id, row);
-  };
+const resetFilters = () => {
+	filters.deviceName = "";
+	filters.maintenancePlanTime = "";
+	filters.maintenanceActuallyTime = "";
+	filters.maintenanceActuallyName = "";
+	getTableData();
+};
 
-  const addPlan = () => {
-    planModalRef.value.openModal();
-  };
+const handleSelectionChange = selection => {
+	// 浠庨�変腑鐨勫垎缁勮涓敹闆嗘墍鏈夊瓙璁板綍ID
+	const ids = [];
+	selection.forEach(group => {
+		if (group.children) {
+			group.children.forEach(child => ids.push(child.id));
+		}
+	});
+	multipleList.value = ids;
+};
 
-  const editPlan = id => {
-    planModalRef.value.openEdit(id);
-  };
+const changePage = page => {
+	pagination.value.currentPage = page.page;
+	pagination.value.pageSize = page.limit;
+	getTableData();
+};
 
-  const delRepairByIds = async ids => {
-    // 妫�鏌ユ槸鍚︽湁瀹岀粨鐘舵�佺殑璁板綍
-    const hasFinished = multipleList.value.some(item => item.status === 1);
-    if (hasFinished) {
-      ElMessage.warning("涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�");
-      return;
-    }
+const addMaintain = row => {
+	maintainModalRef.value.open(row.id, row);
+};
 
-    try {
-      await ElMessageBox.confirm("纭鍒犻櫎淇濆吇鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
-        confirmButtonText: "纭畾",
-        cancelButtonText: "鍙栨秷",
-        type: "warning",
-      });
+const addPlan = () => {
+	planModalRef.value.openModal();
+};
 
-      const { code } = await delUpkeep(ids);
-      if (code === 200) {
-        ElMessage.success("鍒犻櫎鎴愬姛");
-        getTableData();
-      }
-    } catch (error) {
-      // 鐢ㄦ埛鍙栨秷鍒犻櫎
-    }
-  };
+const editPlan = id => {
+	planModalRef.value.openEdit(id);
+};
 
-  const handleOut = () => {
-    ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
-      confirmButtonText: "纭",
-      cancelButtonText: "鍙栨秷",
-      type: "warning",
-    })
-      .then(() => {
-        proxy.download("/device/maintenance/export", {}, "璁惧淇濆吇.xlsx");
-      })
-      .catch(() => {
-        ElMessage.info("宸插彇娑�");
-      });
-  };
+const delRepairByIds = async ids => {
+	const idList = Array.isArray(ids) ? ids : [ids];
+	if (idList.length === 0) return;
+	
+	try {
+		await ElMessageBox.confirm("纭鍒犻櫎淇濆吇鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
+			confirmButtonText: "纭畾",
+			cancelButtonText: "鍙栨秷",
+			type: "warning",
+		});
+		
+		const { code } = await delUpkeep(idList);
+		if (code === 200) {
+			ElMessage.success("鍒犻櫎鎴愬姛");
+			getTableData();
+		}
+	} catch (error) {
+		// 鐢ㄦ埛鍙栨秷鍒犻櫎
+	}
+};
 
-  const handleDateChange = (date, type) => {
-    if (type === 1) {
-      filters.maintenanceActuallyTime = date
-        ? dayjs(date).format("YYYY-MM-DD")
-        : "";
-    } else {
-      filters.maintenancePlanTime = date ? dayjs(date).format("YYYY-MM-DD") : "";
-    }
-    getTableData();
-  };
+const handleOut = () => {
+	ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+		confirmButtonText: "纭",
+		cancelButtonText: "鍙栨秷",
+		type: "warning",
+	})
+		.then(() => {
+			proxy.download("/device/maintenance/export", {}, "璁惧淇濆吇.xlsx");
+		})
+		.catch(() => {
+			ElMessage.info("宸插彇娑�");
+		});
+};
 
-  // 鎵撳紑闄勪欢寮圭獥
-  const openFileDialog = async row => {
-    currentMaintenanceTaskId.value = row.id;
-    fileDialogVisible.value = true;
-  };
+const handleDateChange = (date, type) => {
+	if (type === 1) {
+		filters.maintenanceActuallyTime = date
+			? dayjs(date).format("YYYY-MM-DD")
+			: "";
+	} else {
+		filters.maintenancePlanTime = date ? dayjs(date).format("YYYY-MM-DD") : "";
+	}
+	getTableData();
+};
 
-  onMounted(() => {
-    // 鏍规嵁榛樿婵�娲荤殑 Tab 璋冪敤瀵瑰簲鐨勬煡璇㈡帴鍙�
-    if (activeTab.value === "scheduled") {
-      getScheduledTableData();
-    } else {
-      getTableData();
-    }
-  });
+// 鎵撳紑闄勪欢寮圭獥
+const openFileDialog = async row => {
+	currentMaintenanceTaskId.value = row.id;
+	fileDialogVisible.value = true;
+};
+
+onMounted(() => {
+	// 鏍规嵁榛樿婵�娲荤殑 Tab 璋冪敤瀵瑰簲鐨勬煡璇㈡帴鍙�
+	if (activeTab.value === "scheduled") {
+		getScheduledTableData();
+	} else {
+		getTableData();
+	}
+});
 </script>
 
 <style lang="scss" scoped>
-  .table_list {
-    margin-top: unset;
-  }
-  .actions {
-    display: flex;
-    justify-content: space-between;
-    margin-bottom: 10px;
-  }
+.table_list {
+	margin-top: unset;
+}
+.actions {
+	display: flex;
+	justify-content: space-between;
+	margin-bottom: 10px;
+}
 </style>
 
 

--
Gitblit v1.9.3