From 8d3b1c06ed0ef0803c31085cb3e90aa6d3d622f0 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 11 六月 2026 14:51:13 +0800
Subject: [PATCH] refactor(workOrder): 重构工单管理页面为父子表格结构
---
src/views/productionManagement/workOrderManagement/index.vue | 495 ++++++++++++++++++++++++++++++++++--------------------
1 files changed, 314 insertions(+), 181 deletions(-)
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 60fe511..d8433b0 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -3,15 +3,6 @@
<div class="search_form mb20">
<div class="search-row">
<div class="search-item">
- <span class="search_title">宸ュ崟缂栧彿锛�</span>
- <el-input v-model="searchForm.workOrderNo"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- @change="handleQuery"
- clearable
- prefix-icon="Search" />
- </div>
- <div class="search-item">
<span class="search_title">鐢熶骇璁㈠崟鍙凤細</span>
<el-input v-model="searchForm.npsNo"
style="width: 240px"
@@ -27,18 +18,199 @@
</div>
</div>
<div class="table_list">
- <PIMTable rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :tableLoading="tableLoading"
- @pagination="pagination">
- <template #completionStatus="{ row }">
- <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
- :color="progressColor(toProgressPercentage(row?.completionStatus))"
- :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
- </template>
- </PIMTable>
+ <el-table :data="tableData"
+ border
+ v-loading="tableLoading"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.productionOrderId"
+ @expand-change="expandChange"
+ height="calc(100vh - 22em)">
+ <!-- 灞曞紑琛屽垪 -->
+ <el-table-column type="expand"
+ width="60"
+ fixed="left">
+ <template #default="props">
+ <el-table :data="props.row.children || []"
+ border
+ :row-class-name="({ row }) => getChildRowClassName(row)"
+ v-loading="childLoading[props.row.productionOrderId]"
+ style="margin: 10px 0;">
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="宸ュ崟缂栧彿"
+ prop="workOrderNo"
+ width="140">
+ <template #default="scope">
+ <span :class="{ 'rework-text': scope.row.workOrderNo?.startsWith('FG') }">
+ {{ scope.row.workOrderNo }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="宸ュ簭鍚嶇О"
+ prop="operationName"
+ width="100" />
+ <el-table-column label="宸ュ簭绫诲瀷"
+ width="80"
+ align="center">
+ <template #default="scope">
+ <span>{{ scope.row.type === 0 ? '璁℃椂' : '璁′欢' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璁″垝鏁伴噺"
+ prop="planQuantity"
+ width="100" />
+ <el-table-column label="瀹屾垚鏁伴噺"
+ prop="completeQuantity"
+ width="100" />
+ <el-table-column label="瀹屾垚杩涘害"
+ width="140">
+ <template #default="scope">
+ <el-progress :percentage="toProgressPercentage(scope.row?.completionStatus)"
+ :color="progressColor(toProgressPercentage(scope.row?.completionStatus))"
+ :status="toProgressPercentage(scope.row?.completionStatus) >= 100 ? 'success' : ''" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎶ュ簾鏁伴噺"
+ prop="scrapQty"
+ width="80">
+ <template #default="scope">
+ <span :class="{ 'scrap-text': scope.row.scrapQty > 0 }">
+ {{ scope.row.scrapQty || 0 }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎶ュ伐浜哄憳"
+ prop="userNames"
+ width="150"
+ show-overflow-tooltip />
+ <el-table-column label="璁″垝寮�濮嬫椂闂�"
+ prop="planStartTime"
+ width="120" />
+ <el-table-column label="璁″垝缁撴潫鏃堕棿"
+ prop="planEndTime"
+ width="120" />
+ <el-table-column label="瀹為檯寮�濮嬫椂闂�"
+ prop="actualStartTime"
+ width="120" />
+ <el-table-column label="瀹為檯缁撴潫鏃堕棿"
+ prop="actualEndTime"
+ width="120" />
+ <el-table-column label="鐘舵��"
+ width="100"
+ align="center">
+ <template #default="scope">
+ <el-tag :type="getStatusTagType(scope.row.status)">
+ {{ getStatusText(scope.row.status) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ width="200"
+ align="center"
+ fixed="right">
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ @click="downloadAndPrintWorkOrder(scope.row)">
+ 娴佽浆鍗�
+ </el-button>
+ <el-button link
+ type="primary"
+ @click="openWorkOrderFiles(scope.row)">
+ 闄勪欢
+ </el-button>
+ <el-button link
+ type="primary"
+ v-if="!scope.row.endOrder"
+ :disabled="isReportDisabled(scope.row)"
+ @click="showReportDialog(scope.row)">
+ 鎶ュ伐
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </el-table-column>
+ <!-- 涓昏〃鍒� -->
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="鐢熶骇璁㈠崟鍙�"
+ prop="npsNo"
+ width="150" />
+ <el-table-column label="浜у搧鍚嶇О"
+ prop="productName"
+ width="140" />
+ <el-table-column label="瑙勬牸"
+ prop="model"
+ width="150" />
+ <el-table-column label="鍗曚綅"
+ prop="unit"
+ width="80" />
+ <el-table-column label="宸ュ簭鍚嶇О"
+ prop="operationName"
+ width="200"
+ show-overflow-tooltip />
+ <el-table-column label="宸ュ崟鏁伴噺"
+ prop="productionTaskCount"
+ width="80">
+ <template #default="scope">
+ <span>鍏眥{ scope.row.productionTaskCount || 0 }}涓�</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璁″垝鏁伴噺"
+ prop="planQuantity"
+ width="100" />
+ <el-table-column label="瀹屾垚鏁伴噺"
+ prop="completeQuantity"
+ width="100" />
+ <el-table-column label="瀹屾垚杩涘害"
+ width="140">
+ <template #default="scope">
+ <el-progress :percentage="toProgressPercentage(scope.row?.completionStatus)"
+ :color="progressColor(toProgressPercentage(scope.row?.completionStatus))"
+ :status="toProgressPercentage(scope.row?.completionStatus) >= 100 ? 'success' : ''" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎶ュ簾鏁伴噺"
+ prop="scrapQty"
+ width="80">
+ <template #default="scope">
+ <span :class="{ 'scrap-text': scope.row.scrapQty > 0 }">
+ {{ scope.row.scrapQty || 0 }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��"
+ width="100"
+ align="center">
+ <template #default="scope">
+ <el-tag :type="getStatusTagType(scope.row.status)">
+ {{ getStatusText(scope.row.status) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="璁″垝寮�濮嬫椂闂�"
+ prop="planStartTime"
+ width="120" />
+ <el-table-column label="璁″垝缁撴潫鏃堕棿"
+ prop="planEndTime"
+ width="120" />
+ <el-table-column label="瀹為檯寮�濮嬫椂闂�"
+ prop="actualStartTime"
+ width="120" />
+ <el-table-column label="瀹為檯缁撴潫鏃堕棿"
+ prop="actualEndTime"
+ width="120" />
+ </el-table>
+ <PaginationComp v-show="page.total > 0"
+ :total="page.total"
+ :page="page.current"
+ :limit="page.size"
+ @pagination="pagination" />
</div>
<!-- 娴佽浆鍗″脊绐� -->
<el-dialog v-model="transferCardVisible"
@@ -262,11 +434,12 @@
</template>
<script setup>
- import { onMounted, ref, nextTick } from "vue";
+ import { onMounted, ref, nextTick, reactive, toRefs, getCurrentInstance } from "vue";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {
productWorkOrderPage,
+ getWorkOrderListByOrderId,
addProductMain,
downProductWorkOrder,
} from "@/api/productionManagement/workOrder.js";
@@ -275,7 +448,7 @@
import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
import { getDicts } from "@/api/system/dict/data";
import QRCode from "qrcode";
- import { getCurrentInstance, reactive, toRefs } from "vue";
+ import PaginationComp from "@/components/PIMTable/Pagination.vue";
import MaterialDialog from "./components/MaterialDialog.vue";
const FileList = defineAsyncComponent(() =>
import("@/components/Dialog/FileList.vue")
@@ -285,130 +458,31 @@
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
- const tableColumn = ref([
- {
- label: "宸ュ崟绫诲瀷",
- prop: "workOrderType",
- width: "80",
- },
- {
- label: "宸ュ崟缂栧彿",
- prop: "workOrderNo",
- width: "140",
- },
- {
- label: "鐢熶骇璁㈠崟鍙�",
- prop: "npsNo",
- width: "140",
- },
- {
- label: "浜у搧鍚嶇О",
- prop: "productName",
- width: "140",
- },
- {
- label: "瑙勬牸",
- prop: "model",
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- },
- {
- label: "宸ュ簭鍚嶇О",
- prop: "operationName",
- width: "100",
- },
- {
- label: "闇�姹傛暟閲�",
- prop: "planQuantity",
- width: "140",
- },
- {
- label: "瀹屾垚鏁伴噺",
- prop: "completeQuantity",
- width: "140",
- },
- {
- label: "瀹屾垚杩涘害",
- prop: "completionStatus",
- dataType: "slot",
- slot: "completionStatus",
- width: "140",
- },
- {
- label: "璁″垝寮�濮嬫椂闂�",
- prop: "planStartTime",
- width: "140",
- },
- {
- label: "璁″垝缁撴潫鏃堕棿",
- prop: "planEndTime",
- width: "140",
- },
- {
- label: "瀹為檯寮�濮嬫椂闂�",
- prop: "actualStartTime",
- width: "140",
- },
- {
- label: "瀹為檯缁撴潫鏃堕棿",
- prop: "actualEndTime",
- width: "140",
- },
- {
- label: "鎿嶄綔",
- width: "260",
- align: "center",
- dataType: "action",
- fixed: "right",
- operation: [
- {
- name: "娴佽浆鍗�",
- clickFun: row => {
- downloadAndPrintWorkOrder(row);
- },
- },
- {
- name: "闄勪欢",
- clickFun: row => {
- openWorkOrderFiles(row);
- },
- },
- // {
- // name: "鐗╂枡",
- // clickFun: row => {
- // openMaterialDialog(row);
- // },
- // },
- {
- name: "鎶ュ伐",
- clickFun: row => {
- showReportDialog(row);
- },
- showHide: row => !row.endOrder,
- disabled: row => {
- if (row.planQuantity <= 0) return true;
- if (!row.userIds) return false;
- try {
- const userIds =
- typeof row.userIds === "string"
- ? JSON.parse(row.userIds)
- : row.userIds;
- if (Array.isArray(userIds)) {
- return !userIds.some(id => String(id) === String(userStore.id));
- }
- return true;
- } catch (e) {
- return true;
- }
- },
- },
- ],
- },
- ]);
+ // 鐘舵�佹灇涓炬槧灏�
+ const statusMap = {
+ 1: '寰呯‘璁�',
+ 2: '寰呯敓浜�',
+ 3: '鐢熶骇涓�',
+ 4: '宸茬敓浜�',
+ };
+
+ const getStatusText = (status) => statusMap[status] || '鏈煡';
+
+ const getStatusTagType = (status) => {
+ switch (status) {
+ case 1: return 'info';
+ case 2: return 'warning';
+ case 3: return 'primary';
+ case 4: return 'success';
+ default: return 'info';
+ }
+ };
+
const tableData = ref([]);
const tableLoading = ref(false);
+ const expandedRowKeys = ref([]);
+ const childLoading = ref({});
+
const transferCardVisible = ref(false);
const transferCardData = ref([]);
const transferCardQrUrl = ref("");
@@ -434,7 +508,7 @@
paramGroups: {},
});
- const params = ref({});
+ const params = ref([]);
const dictOptions = ref({});
const paramLoading = ref(false);
@@ -445,7 +519,6 @@
return;
}
const num = Number(value);
- // 鏁存暟涓斿ぇ浜庣瓑浜�1
if (isNaN(num) || !Number.isInteger(num) || num < 0) {
callback(new Error("鐢熶骇鍚堟牸鏁伴噺蹇呴』澶т簬绛変簬0"));
return;
@@ -460,7 +533,6 @@
return;
}
const num = Number(value);
- // 鏁存暟涓斿ぇ浜庣瓑浜�0
if (isNaN(num) || !Number.isInteger(num) || num < 0) {
callback(new Error("鎶ュ簾鏁伴噺蹇呴』澶т簬绛変簬0"));
return;
@@ -474,7 +546,7 @@
scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
};
- // 澶勭悊鐢熶骇鍚堟牸鏁伴噺杈撳叆锛岄檺鍒跺繀椤诲ぇ浜庣瓑浜�0
+ // 澶勭悊鐢熶骇鍚堟牸鏁伴噺杈撳叆
const handleQuantityInput = value => {
if (value === "" || value === null || value === undefined) {
reportForm.quantity = null;
@@ -484,15 +556,12 @@
if (isNaN(num)) {
return;
}
- // 濡傛灉灏忎簬1锛屾竻闄�
if (num < 0) {
reportForm.quantity = null;
return;
}
- // 濡傛灉鏄皬鏁板彇鏁存暟閮ㄥ垎
if (!Number.isInteger(num)) {
const intValue = Math.floor(num);
- // 濡傛灉鍙栨暣鍚庡皬浜�1锛屾竻闄�
if (intValue < 0) {
reportForm.quantity = null;
return;
@@ -510,21 +579,17 @@
return;
}
const num = Number(value);
- // 濡傛灉鏄疦aN锛屼繚鎸佸師鍊�
if (isNaN(num)) {
return;
}
- // 濡傛灉鏄礋鏁帮紝娓呴櫎杈撳叆
if (num < 0) {
reportForm.scrapQty = null;
return;
}
- // 濡傛灉鏄皬鏁帮紝鍙栨暣鏁伴儴鍒�
if (!Number.isInteger(num)) {
reportForm.scrapQty = Math.floor(num);
return;
}
- // 鏈夋晥鐨勯潪璐熸暣鏁帮紙鍖呮嫭0锛�
reportForm.scrapQty = num;
};
@@ -539,11 +604,11 @@
const data = reactive({
searchForm: {
- workOrderNo: "",
npsNo: "",
},
});
const { searchForm } = toRefs(data);
+
const toProgressPercentage = val => {
const n = Number(val);
if (!Number.isFinite(n)) return 0;
@@ -551,6 +616,7 @@
if (n >= 100) return 100;
return Math.round(n);
};
+
const progressColor = percentage => {
const p = toProgressPercentage(percentage);
if (p < 30) return "#f56c6c";
@@ -560,7 +626,6 @@
};
// 鏌ヨ鍒楄〃
- /** 鎼滅储鎸夐挳鎿嶄綔 */
const handleQuery = () => {
page.current = 1;
getList();
@@ -574,11 +639,15 @@
const getList = () => {
tableLoading.value = true;
+ expandedRowKeys.value = [];
const params = { ...searchForm.value, ...page };
productWorkOrderPage(params)
.then(res => {
tableLoading.value = false;
- tableData.value = res.data.records;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ children: [],
+ }));
page.total = res.data.total;
})
.catch(() => {
@@ -586,7 +655,67 @@
});
};
- // 涓嬭浇骞舵墦鍗板伐鍗曟祦杞崱锛堟枃浠舵祦锛�
+ // 灞曞紑琛屾噿鍔犺浇
+ const expandChange = (row, expandedRows) => {
+ if (expandedRows.length > 0) {
+ expandedRowKeys.value = [];
+ const orderId = row.productionOrderId;
+
+ // 妫�鏌ユ槸鍚﹀凡鏈夋暟鎹�
+ if (row.children && row.children.length > 0) {
+ expandedRowKeys.value.push(orderId);
+ return;
+ }
+
+ childLoading.value[orderId] = true;
+
+ getWorkOrderListByOrderId(orderId)
+ .then(res => {
+ childLoading.value[orderId] = false;
+ const index = tableData.value.findIndex(
+ item => item.productionOrderId === orderId
+ );
+ if (index > -1) {
+ tableData.value[index].children = res.data || [];
+ }
+ expandedRowKeys.value.push(orderId);
+ })
+ .catch(error => {
+ childLoading.value[orderId] = false;
+ console.error('鍔犺浇宸ュ崟鏄庣粏澶辫触:', error);
+ });
+ } else {
+ expandedRowKeys.value = [];
+ }
+ };
+
+ // 瀛愯〃琛屾牱寮忥紙杩斿伐杩斾慨绾㈣壊鏍囪锛�
+ const getChildRowClassName = (row) => {
+ if (row.workOrderNo?.startsWith('FG')) {
+ return 'row-rework';
+ }
+ return '';
+ };
+
+ // 鎶ュ伐鎸夐挳绂佺敤閫昏緫
+ const isReportDisabled = (row) => {
+ if (row.planQuantity <= 0) return true;
+ if (!row.userIds) return false;
+ try {
+ const userIds =
+ typeof row.userIds === "string"
+ ? JSON.parse(row.userIds)
+ : row.userIds;
+ if (Array.isArray(userIds)) {
+ return !userIds.some(id => String(id) === String(userStore.id));
+ }
+ return true;
+ } catch (e) {
+ return true;
+ }
+ };
+
+ // 涓嬭浇骞舵墦鍗板伐鍗曟祦杞崱
const downloadAndPrintWorkOrder = async row => {
if (!row || !row.id) {
proxy.$modal.msgError("缂哄皯宸ュ崟ID锛屾棤娉曚笅杞芥祦杞崱");
@@ -596,7 +725,6 @@
? `宸ュ崟娴佽浆鍗${row.workOrderNo}.xlsx`
: "宸ュ崟娴佽浆鍗�.xlsx";
try {
- // 璋冪敤鎺ュ彛锛屼互 responseType: 'blob' 鑾峰彇鏂囦欢娴�
const blob = await downProductWorkOrder(row.id);
if (!blob) {
@@ -604,14 +732,12 @@
return;
}
- // 鍒涘缓 Blob URL
const fileBlob =
blob instanceof Blob
? blob
: new Blob([blob], { type: blob.type || "application/octet-stream" });
const url = window.URL.createObjectURL(fileBlob);
- // 鍒涘缓闅愯棌 iframe锛岀敤浜庤Е鍙戞祻瑙堝櫒鎵撳嵃
const iframe = document.createElement("iframe");
iframe.style.position = "fixed";
iframe.style.right = "0";
@@ -628,7 +754,6 @@
iframe.contentWindow?.print();
} catch (e) {
console.error("鑷姩璋冪敤鎵撳嵃澶辫触", e);
- // 閫�鑰屾眰鍏舵锛屾墦寮�鏂扮獥鍙g敱鐢ㄦ埛鎵嬪姩鎵撳嵃
window.open(url);
}
};
@@ -731,7 +856,6 @@
return;
}
- // 楠岃瘉鐢熶骇鍚堟牸鏁伴噺
if (
reportForm.quantity === null ||
reportForm.quantity === undefined ||
@@ -752,14 +876,6 @@
return;
}
- // if (quantity > reportForm.planQuantity) {
- // ElMessageBox.alert("鏈鐢熶骇鏁伴噺涓嶈兘瓒呰繃寰呯敓浜ф暟閲�", "鎻愮ず", {
- // confirmButtonText: "纭畾",
- // });
- // return;
- // }
-
- // 楠岃瘉鎶ュ簾鏁伴噺
const scrapQty = Number(reportForm.scrapQty);
if (!isNaN(scrapQty) && scrapQty < 0) {
ElMessageBox.alert("鎶ュ簾鏁伴噺涓嶈兘灏忎簬0", "鎻愮ず", {
@@ -767,13 +883,6 @@
});
return;
}
-
- // if (!isNaN(scrapQty) && scrapQty > quantity) {
- // ElMessageBox.alert("鎶ュ簾鏁伴噺涓嶈兘澶т簬鏈鐢熶骇鏁伴噺", "鎻愮ず", {
- // confirmButtonText: "纭畾",
- // });
- // return;
- // }
const productionOperationParamList = params.value.map(param => ({
...param,
@@ -863,7 +972,6 @@
onMounted(() => {
userStore.getInfo();
getList();
- // 鑾峰彇鐢ㄦ埛鍒楄〃
userListNoPageByTenantId().then(res => {
if (res.code === 200) {
userOptions.value = res.data;
@@ -955,6 +1063,31 @@
font-size: 12px;
min-width: 30px;
}
+
+ // 杩斿伐杩斾慨宸ュ崟绾㈣壊鏍囪
+ .row-rework {
+ background-color: #fef0f0 !important;
+
+ :deep(.el-table__cell) {
+ background-color: #fef0f0 !important;
+ }
+ }
+
+ .rework-text {
+ color: #f56c6c;
+ font-weight: bold;
+ }
+
+ // 鎶ュ簾鏁伴噺绾㈣壊鏍囪
+ .scrap-text {
+ color: #f56c6c;
+ }
+
+ // 瀛愯〃鏍兼牱寮�
+ :deep(.el-table__expanded-cell) {
+ padding: 10px 30px !important;
+ background-color: #fafafa;
+ }
</style>
<style lang="scss">
--
Gitblit v1.9.3