From c82a938c4fee27a6e478782ffc355809cd5cdcda Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期三, 14 一月 2026 17:06:11 +0800
Subject: [PATCH] Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-management into dev_New
---
src/views/productionManagement/productionProcess/index.vue | 218 +
src/views/productionManagement/processRoute/index.vue | 194 +
src/api/productionManagement/productionProductInput.js | 11
src/views/basicData/product/ProductSelectDialog.vue | 163
src/api/productionManagement/workOrder.js | 25
src/views/productionManagement/workOrder/index.vue | 587 +++
src/views/productionManagement/processRoute/processRouteItem/index.vue | 503 ++
src/views/qualityManagement/metricMaintenance/index0.vue | 415 ++
src/api/productionManagement/productionOrder.js | 71
src/views/productionManagement/productionDispatching/components/formDia.vue | 25
src/views/productionManagement/productionDispatching/index.vue | 464 ++
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue | 78
src/views/productionManagement/productionProcess/Edit.vue | 102
src/views/productManagement/productIdentifier/index.vue | 1187 +++---
src/views/productionManagement/productStructure/index.vue | 113
src/views/qualityManagement/metricBinding/index.vue | 432 ++
src/views/productionManagement/processRoute/ItemsForm.vue | 531 +++
src/api/productionManagement/processRouteItem.js | 19
src/views/productionManagement/processRoute/New.vue | 181 +
src/api/productionManagement/processRoute.js | 34
src/api/productionManagement/productStructure.js | 18
src/views/qualityManagement/metricMaintenance/index.vue | 1081 ++++--
src/api/qualityManagement/qualityTestStandardBinding.js | 28
src/views/productionManagement/operationScheduling/components/formDia.vue | 121
src/views/productionManagement/productionReporting/Output.vue | 106
src/api/productionManagement/productionProcess.js | 51
src/views/productionManagement/productionCosting/index.vue | 30
src/api/productionManagement/productionReporting.js | 10
src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue | 555 +++
src/components/PIMTable/PIMTable.vue | 28
src/api/qualityManagement/metricMaintenance.js | 107
src/views/productionManagement/operationScheduling/index.vue | 47
src/views/productionManagement/productStructure/StructureEdit.vue | 311 +
src/views/productionManagement/productionReporting/Input.vue | 107
src/api/productionManagement/productProcessRoute.js | 28
src/api/productionManagement/productionProductOutput.js | 11
src/views/productionManagement/productStructure/Detail/index.vue | 293 +
src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue | 153
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue | 129
src/views/productionManagement/productionReporting/index.vue | 888 ++--
src/api/productionManagement/productionProductMain.js | 11
src/views/productionManagement/productionOrder/index.vue | 367 +-
src/api/system/message.js | 2
src/api/basicData/productModel.js | 9
src/api/basicData/productProcess.js | 10
src/views/productionManagement/processRoute/Edit.vue | 186 +
src/views/productionManagement/productionProcess/New.vue | 96
src/views/productionManagement/productionReporting/components/formDia.vue | 38
48 files changed, 8,557 insertions(+), 1,617 deletions(-)
diff --git a/src/api/basicData/productModel.js b/src/api/basicData/productModel.js
new file mode 100644
index 0000000..f048f9e
--- /dev/null
+++ b/src/api/basicData/productModel.js
@@ -0,0 +1,9 @@
+import request from "@/utils/request.js";
+
+export function productModelList(query) {
+ return request({
+ url: '/basic/product/pageModel',
+ method: 'get',
+ params: query
+ })
+}
\ No newline at end of file
diff --git a/src/api/basicData/productProcess.js b/src/api/basicData/productProcess.js
new file mode 100644
index 0000000..e0208fa
--- /dev/null
+++ b/src/api/basicData/productProcess.js
@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 宸ュ簭鍒楄〃鍒嗛〉鏌ヨ
+export function productProcessListPage(query) {
+ return request({
+ url: '/productProcess/listPage',
+ method: 'get',
+ params: query
+ })
+}
diff --git a/src/api/productionManagement/processRoute.js b/src/api/productionManagement/processRoute.js
new file mode 100644
index 0000000..4d16775
--- /dev/null
+++ b/src/api/productionManagement/processRoute.js
@@ -0,0 +1,34 @@
+// 宸ヨ壓璺嚎椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/processRoute/page",
+ method: "get",
+ params: query,
+ });
+}
+
+export function add(data) {
+ return request({
+ url: "/processRoute",
+ method: "post",
+ data: data,
+ });
+}
+
+export function del(ids) {
+ return request({
+ url: '/processRoute/' + ids,
+ method: 'delete',
+ })
+}
+
+export function update(data) {
+ return request({
+ url: '/processRoute',
+ method: 'put',
+ data: data,
+ })
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/processRouteItem.js b/src/api/productionManagement/processRouteItem.js
new file mode 100644
index 0000000..ad4861c
--- /dev/null
+++ b/src/api/productionManagement/processRouteItem.js
@@ -0,0 +1,19 @@
+// 宸ヨ壓璺嚎椤圭洰椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒楄〃鏌ヨ
+export function findProcessRouteItemList(query) {
+ return request({
+ url: "/processRouteItem/list",
+ method: "get",
+ params: query,
+ });
+}
+
+export function addOrUpdateProcessRouteItem(data) {
+ return request({
+ url: "/processRouteItem",
+ method: "post",
+ data: data,
+ });
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/productProcessRoute.js b/src/api/productionManagement/productProcessRoute.js
new file mode 100644
index 0000000..d756e26
--- /dev/null
+++ b/src/api/productionManagement/productProcessRoute.js
@@ -0,0 +1,28 @@
+// 宸ヨ壓璺嚎椤圭洰椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒楄〃鏌ヨ
+export function findProductProcessRouteItemList(query) {
+ return request({
+ url: "/productProcessRoute/list",
+ method: "get",
+ params: query,
+ });
+}
+
+export function addOrUpdateProductProcessRouteItem(data) {
+ return request({
+ url: "/productProcessRoute/updateRouteItem",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鍒犻櫎瀹㈡埛妗f
+export function deleteRouteItem(ids) {
+ return request({
+ url: '/productProcessRoute/deleteRouteItem',
+ method: 'delete',
+ data: ids
+ })
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/productStructure.js b/src/api/productionManagement/productStructure.js
new file mode 100644
index 0000000..074e27a
--- /dev/null
+++ b/src/api/productionManagement/productStructure.js
@@ -0,0 +1,18 @@
+// 浜у搧缁撴瀯椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function queryList(id) {
+ return request({
+ url: "/productStructure/listByproductModelId/" + id,
+ method: "get",
+ });
+}
+
+export function add(data) {
+ return request({
+ url: "/productStructure",
+ method: "post",
+ data: data,
+ });
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/productionOrder.js b/src/api/productionManagement/productionOrder.js
index ab3dc06..0c37be1 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -9,6 +9,43 @@
params: query,
});
}
+
+
+export function productOrderListPage(query) {
+ return request({
+ url: "/productOrder/page",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+export function schedulingList(query) {
+ return request({
+ url: "/salesLedger/scheduling/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 淇濆瓨鐐掓満璁剧疆
+export function addSpeculatTrading(data) {
+ return request({
+ url: "/salesLedger/scheduling/addSpeculatTrading",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼鐐掓満璁剧疆
+export function updateSpeculatTrading(data) {
+ return request({
+ url: "/salesLedger/scheduling/updateSpeculatTrading",
+ method: "post",
+ data: data,
+ });
+}
+
// 鐢熶骇娲惧伐
export function productionDispatch(query) {
return request({
@@ -16,4 +53,38 @@
method: "post",
data: query,
});
+}
+// 鑷姩娲惧伐
+export function productionDispatchList(query) {
+ return request({
+ url: "/salesLedger/scheduling/productionDispatchList",
+ method: "post",
+ data: query,
+ });
+}
+
+// 鏌ヨ鎹熻�楃巼
+export function getLossRate() {
+ return request({
+ url: "/salesLedger/scheduling/loss",
+ method: "get",
+ });
+}
+
+// 鏂板鎹熻�楃巼
+export function addLossRate(data) {
+ return request({
+ url: "/salesLedger/scheduling/addLoss",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼鎹熻�楃巼
+export function updateLossRate(data) {
+ return request({
+ url: "/salesLedger/scheduling/updateLoss",
+ method: "post",
+ data: data,
+ });
}
\ No newline at end of file
diff --git a/src/api/productionManagement/productionProcess.js b/src/api/productionManagement/productionProcess.js
new file mode 100644
index 0000000..e3cd929
--- /dev/null
+++ b/src/api/productionManagement/productionProcess.js
@@ -0,0 +1,51 @@
+// 宸ュ簭椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/productProcess/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+export function processList(query) {
+ return request({
+ url: "/productProcess/list",
+ method: "get",
+ params: query,
+ });
+}
+
+export function add(data) {
+ return request({
+ url: "/productProcess",
+ method: "post",
+ data: data,
+ });
+}
+
+export function del(data) {
+ return request({
+ url: '/productProcess/batchDelete',
+ method: 'delete',
+ data: data,
+ })
+}
+
+export function update(data) {
+ return request({
+ url: '/productProcess/update',
+ method: 'put',
+ data: data,
+ })
+}
+
+// 宸ュ簭鏌ヨ
+export function list() {
+ return request({
+ url: "/productProcess/list",
+ method: "get",
+ });
+}
\ No newline at end of file
diff --git a/src/api/productionManagement/productionProductInput.js b/src/api/productionManagement/productionProductInput.js
new file mode 100644
index 0000000..f72cd9b
--- /dev/null
+++ b/src/api/productionManagement/productionProductInput.js
@@ -0,0 +1,11 @@
+// 鐢熶骇鎶曞叆椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function productionProductInputListPage(query) {
+ return request({
+ url: "/productionProductInput/listPage",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/api/productionManagement/productionProductMain.js b/src/api/productionManagement/productionProductMain.js
new file mode 100644
index 0000000..0493f8b
--- /dev/null
+++ b/src/api/productionManagement/productionProductMain.js
@@ -0,0 +1,11 @@
+// 鐢熶骇鎶ュ伐椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function productionProductMainListPage(query) {
+ return request({
+ url: "/productionProductMain/listPage",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/api/productionManagement/productionProductOutput.js b/src/api/productionManagement/productionProductOutput.js
new file mode 100644
index 0000000..10095e9
--- /dev/null
+++ b/src/api/productionManagement/productionProductOutput.js
@@ -0,0 +1,11 @@
+// 鐢熶骇浜у嚭椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function productionProductOutputListPage(query) {
+ return request({
+ url: "/productionProductOutput/listPage",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/api/productionManagement/productionReporting.js b/src/api/productionManagement/productionReporting.js
index fdec712..3e29943 100644
--- a/src/api/productionManagement/productionReporting.js
+++ b/src/api/productionManagement/productionReporting.js
@@ -32,4 +32,12 @@
method: "post",
data: query,
});
-}
\ No newline at end of file
+}
+// 鐢熶骇鎶ュ伐-鍒犻櫎
+export function productionReportDelete(query) {
+ return request({
+ url: "/productionProductMain/delete",
+ method: "delete",
+ data: query,
+ });
+}
diff --git a/src/api/productionManagement/workOrder.js b/src/api/productionManagement/workOrder.js
new file mode 100644
index 0000000..bf4b381
--- /dev/null
+++ b/src/api/productionManagement/workOrder.js
@@ -0,0 +1,25 @@
+import request from "@/utils/request";
+
+export function productWorkOrderPage(query) {
+ return request({
+ url: "/productWorkOrder/page",
+ method: "get",
+ params: query,
+ });
+}
+
+export function updateProductWorkOrder(data) {
+ return request({
+ url: "/productWorkOrder/updateProductWorkOrder",
+ method: "post",
+ data: data,
+ });
+}
+
+export function addProductMain(data) {
+ return request({
+ url: "/productionProductMain/addProductMain",
+ method: "post",
+ data: data,
+ });
+}
diff --git a/src/api/qualityManagement/metricMaintenance.js b/src/api/qualityManagement/metricMaintenance.js
index 9bdff23..53ca650 100644
--- a/src/api/qualityManagement/metricMaintenance.js
+++ b/src/api/qualityManagement/metricMaintenance.js
@@ -1,45 +1,100 @@
-import request from '@/utils/request'
+import request from "@/utils/request";
// 鏌ヨ鎸囨爣鍒楄〃
export function qualityTestStandardListPage(query) {
- return request({
- url: '/quality/qualityTestStandard/listPage',
- method: 'get',
- params: query,
- })
+ return request({
+ url: "/qualityTestStandard/listPage",
+ method: "get",
+ params: query,
+ });
}
// 鏂板鎸囨爣鍒楄〃
export function qualityTestStandardAdd(query) {
- return request({
- url: '/quality/qualityTestStandard/add',
- method: 'post',
- data: query,
- })
+ return request({
+ url: "/qualityTestStandard/add",
+ method: "post",
+ data: query,
+ });
}
// 淇敼鎸囨爣鍒楄〃
export function qualityTestStandardUpdate(query) {
- return request({
- url: '/quality/qualityTestStandard/update',
- method: 'post',
- data: query,
- })
+ return request({
+ url: "/qualityTestStandard/update",
+ method: "post",
+ data: query,
+ });
}
// 鍒犻櫎鎸囨爣鍒楄〃
export function qualityTestStandardDel(query) {
- return request({
- url: '/quality/qualityTestStandard/del',
- method: 'delete',
- data: query,
- })
+ return request({
+ url: "/qualityTestStandard/del",
+ method: "delete",
+ data: query,
+ });
}
// 鍒犻櫎鎸囨爣鍒楄〃
export function qualityInspectDetailByProductId(productId) {
- return request({
- url: '/quality/qualityTestStandard/product/' + productId,
- method: 'get',
- })
-}
\ No newline at end of file
+ return request({
+ url: "/qualityTestStandard/product/" + productId,
+ method: "get",
+ });
+}
+
+// 澶嶅埗鏍囧噯鍙傛暟
+export function qualityTestStandardCopyParam(id) {
+ return request({
+ url: "/qualityTestStandard/copyParam",
+ method: "post",
+ data: { id },
+ });
+}
+
+// 鎵归噺瀹℃牳锛堢姸鎬侊細1=閫氳繃/鎵瑰噯锛�2=鎾ら攢锛�
+// 浼犲弬锛歔{ id, state }]
+export function qualityTestStandardAudit(data) {
+ return request({
+ url: "/qualityTestStandard/qualityTestStandardAudit",
+ method: "post",
+ data,
+ });
+}
+
+// 鏍囧噯鍙傛暟锛氬垪琛紙涓嶅垎椤碉級
+export function qualityTestStandardParamList(query) {
+ return request({
+ url: "/qualityTestStandardParam/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏍囧噯鍙傛暟锛氭柊澧�
+export function qualityTestStandardParamAdd(data) {
+ return request({
+ url: "/qualityTestStandardParam/add",
+ method: "post",
+ data,
+ });
+}
+
+// 鏍囧噯鍙傛暟锛氫慨鏀�
+export function qualityTestStandardParamUpdate(data) {
+ return request({
+ url: "/qualityTestStandardParam/update",
+ method: "post",
+ data,
+ });
+}
+
+// 鏍囧噯鍙傛暟锛氬垹闄わ紙浼� id 鏁扮粍锛�
+export function qualityTestStandardParamDel(ids) {
+ return request({
+ url: "/qualityTestStandardParam/del",
+ method: "delete",
+ data: ids,
+ });
+}
diff --git a/src/api/qualityManagement/qualityTestStandardBinding.js b/src/api/qualityManagement/qualityTestStandardBinding.js
new file mode 100644
index 0000000..e4432a6
--- /dev/null
+++ b/src/api/qualityManagement/qualityTestStandardBinding.js
@@ -0,0 +1,28 @@
+import request from "@/utils/request";
+
+// 缁戝畾鍒楄〃锛堜笉鍒嗛〉锛�
+export function qualityTestStandardBindingList(query) {
+ return request({
+ url: "/qualityTestStandardBinding/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板缁戝畾锛堟敮鎸佹壒閲忥級
+export function qualityTestStandardBindingAdd(data) {
+ return request({
+ url: "/qualityTestStandardBinding/add",
+ method: "post",
+ data,
+ });
+}
+
+// 鍒犻櫎缁戝畾锛堜紶 id 鏁扮粍锛�
+export function qualityTestStandardBindingDel(ids) {
+ return request({
+ url: "/qualityTestStandardBinding/del",
+ method: "delete",
+ data: ids,
+ });
+}
diff --git a/src/api/system/message.js b/src/api/system/message.js
index 871d7e8..dd81cc5 100644
--- a/src/api/system/message.js
+++ b/src/api/system/message.js
@@ -30,7 +30,7 @@
// 涓�閿爣璁版墍鏈夋秷鎭负宸茶
export function markAllAsRead() {
return request({
- url: "/system/notice/markAllAsRead",
+ url: "/system/notice/readAll",
method: "post",
});
}
diff --git a/src/components/PIMTable/PIMTable.vue b/src/components/PIMTable/PIMTable.vue
index 9dcd440..4174151 100644
--- a/src/components/PIMTable/PIMTable.vue
+++ b/src/components/PIMTable/PIMTable.vue
@@ -46,6 +46,16 @@
:type="item.type"
:width="item.width"
>
+ <template #header="scope">
+ <div class="pim-table-header-cell">
+ <div class="pim-table-header-title">
+ {{ item.label }}
+ </div>
+ <div v-if="item.headerSlot" class="pim-table-header-extra">
+ <slot :name="item.headerSlot" :column="scope.column" />
+ </div>
+ </div>
+ </template>
<template
v-if="item.hasOwnProperty('colunmTemplate')"
#[item.colunmTemplate]="scope"
@@ -429,4 +439,22 @@
padding-right: 0 !important;
padding-left: 0 !important;
}
+
+.pim-table-header-cell {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ text-align: center;
+}
+
+.pim-table-header-title {
+ font-weight: 500;
+}
+
+.pim-table-header-extra :deep(.el-input),
+.pim-table-header-extra :deep(.el-select) {
+ width: 100%;
+}
</style>
diff --git a/src/views/basicData/product/ProductSelectDialog.vue b/src/views/basicData/product/ProductSelectDialog.vue
new file mode 100644
index 0000000..246cd88
--- /dev/null
+++ b/src/views/basicData/product/ProductSelectDialog.vue
@@ -0,0 +1,163 @@
+<template>
+ <el-dialog
+ v-model="visible"
+ title="閫夋嫨浜у搧"
+ width="900px"
+ destroy-on-close
+ :close-on-click-modal="false"
+ >
+ <el-form :inline="true" :model="query" class="mb-2">
+ <el-form-item label="浜у搧澶х被">
+ <el-input
+ v-model="query.productName"
+ placeholder="杈撳叆浜у搧澶х被"
+ clearable
+ @keyup.enter="onSearch"
+ />
+ </el-form-item>
+
+ <el-form-item label="鍨嬪彿鍚嶇О">
+ <el-input
+ v-model="query.model"
+ placeholder="杈撳叆鍨嬪彿鍚嶇О"
+ clearable
+ @keyup.enter="onSearch"
+ />
+ </el-form-item>
+
+ <el-form-item>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
+ <el-button @click="onReset">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 鍒楄〃 -->
+ <el-table
+ v-loading="loading"
+ :data="tableData"
+ height="420"
+ highlight-current-row
+ row-key="id"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="55" />
+ <el-table-column type="index" label="#" width="60"/>
+ <el-table-column prop="productName" label="浜у搧澶х被" min-width="160"/>
+ <el-table-column prop="model" label="鍨嬪彿鍚嶇О" min-width="200"/>
+ <el-table-column prop="unit" label="鍗曚綅" min-width="160"/>
+ </el-table>
+
+ <div class="mt-3 flex justify-end">
+ <el-pagination
+ background
+ layout="total, sizes, prev, pager, next, jumper"
+ :total="total"
+ v-model:page-size="page.pageSize"
+ v-model:current-page="page.pageNum"
+ :page-sizes="[10, 20, 50, 100]"
+ @size-change="onPageChange"
+ @current-change="onPageChange"
+ />
+ </div>
+
+ <template #footer>
+ <el-button @click="close()">鍙栨秷</el-button>
+ <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm">
+ 纭畾
+ </el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup lang="ts">
+import {computed, onMounted, reactive, ref, watch} from "vue";
+import {ElMessage} from "element-plus";
+import {productModelList} from '@/api/basicData/productModel'
+
+export type ProductRow = {
+ id: number;
+ productName: string;
+ model: string;
+ unit?: string;
+};
+
+const props = defineProps<{
+ modelValue: boolean;
+}>();
+
+const emit = defineEmits(['update:modelValue', 'confirm']);
+
+const visible = computed({
+ get: () => props.modelValue,
+ set: (v) => emit("update:modelValue", v),
+});
+
+const query = reactive({
+ productName: "",
+ model: "",
+});
+
+const page = reactive({
+ pageNum: 1,
+ pageSize: 10,
+});
+
+const loading = ref(false);
+const tableData = ref<ProductRow[]>([]);
+const total = ref(0);
+const multipleSelection = ref<ProductRow[]>([])
+
+function close() {
+ visible.value = false;
+}
+
+const handleSelectionChange = (val: ProductRow[]) => {
+ multipleSelection.value = val
+}
+
+function onSearch() {
+ page.pageNum = 1;
+ loadData();
+}
+
+function onReset() {
+ query.productName = "";
+ query.model = "";
+ page.pageNum = 1;
+ loadData();
+}
+
+function onPageChange() {
+ loadData();
+}
+
+function onConfirm() {
+ if (multipleSelection.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨涓�鏉′骇鍝�");
+ return;
+ }
+ emit("confirm", multipleSelection.value);
+ close();
+}
+
+async function loadData() {
+ loading.value = true;
+ try {
+ multipleSelection.value = []; // 缈婚〉/鎼滅储鍚庢竻绌洪�夋嫨鏇寸鍚堥鏈�
+ const res = await productModelList({
+ productName: query.productName.trim(),
+ model: query.model.trim(),
+ current: page.pageNum,
+ size: page.pageSize,
+ });
+ tableData.value = res.records;
+ total.value = res.total;
+ } finally {
+ loading.value = false;
+ }
+}
+
+onMounted(() => {
+ loadData()
+})
+</script>
diff --git a/src/views/productManagement/productIdentifier/index.vue b/src/views/productManagement/productIdentifier/index.vue
index 59d8f90..519f745 100644
--- a/src/views/productManagement/productIdentifier/index.vue
+++ b/src/views/productManagement/productIdentifier/index.vue
@@ -1,159 +1,231 @@
<template>
<div class="app-container">
<el-card class="box-card">
- <!-- 鎼滅储鍖哄煙 -->
- <el-row :gutter="20" class="search-row">
- <el-col :span="6">
- <el-input
- v-model="searchForm.productName"
- placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
- clearable
- @keyup.enter="handleSearch"
- >
- <template #prefix>
- <el-icon><Search /></el-icon>
- </template>
- </el-input>
- </el-col>
- <el-col :span="6">
- <el-select v-model="searchForm.identifierType" placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷" clearable>
- <el-option label="浜岀淮鐮�" value="浜岀淮鐮�"></el-option>
- <el-option label="闃蹭吉鐮�" value="闃蹭吉鐮�"></el-option>
- </el-select>
- </el-col>
- <el-col :span="6">
- <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" clearable>
- <el-option label="宸茬敓鎴�" value="宸茬敓鎴�"></el-option>
- <el-option label="宸插垎閰�" value="宸插垎閰�"></el-option>
- <el-option label="宸蹭娇鐢�" value="宸蹭娇鐢�"></el-option>
- <el-option label="宸蹭綔搴�" value="宸蹭綔搴�"></el-option>
- </el-select>
- </el-col>
- <el-col :span="6">
- <el-button type="primary" @click="handleSearch">鎼滅储</el-button>
- <el-button @click="resetSearch">閲嶇疆</el-button>
- <el-button style="float: right;" type="primary" @click="handleAdd">
- 鏂板鏍囪瘑
- </el-button>
- </el-col>
- </el-row>
-
+ <!-- 鎼滅储鍖哄煙 -->
+ <el-row :gutter="20"
+ class="search-row">
+ <el-col :span="6">
+ <el-input v-model="searchForm.productName"
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+ clearable
+ @keyup.enter="handleSearch">
+ <template #prefix>
+ <el-icon>
+ <Search />
+ </el-icon>
+ </template>
+ </el-input>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.identifierType"
+ placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷"
+ clearable>
+ <el-option label="浜岀淮鐮�"
+ value="浜岀淮鐮�"></el-option>
+ <el-option label="闃蹭吉鐮�"
+ value="闃蹭吉鐮�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-select v-model="searchForm.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ clearable>
+ <el-option label="宸茬敓鎴�"
+ value="宸茬敓鎴�"></el-option>
+ <el-option label="宸插垎閰�"
+ value="宸插垎閰�"></el-option>
+ <el-option label="宸蹭娇鐢�"
+ value="宸蹭娇鐢�"></el-option>
+ <el-option label="宸蹭綔搴�"
+ value="宸蹭綔搴�"></el-option>
+ </el-select>
+ </el-col>
+ <el-col :span="6">
+ <el-button type="primary"
+ @click="handleSearch">鎼滅储</el-button>
+ <el-button @click="resetSearch">閲嶇疆</el-button>
+ <el-button style="float: right;"
+ type="primary"
+ @click="handleAdd">
+ 鏂板鏍囪瘑
+ </el-button>
+ </el-col>
+ </el-row>
<!-- 浜у搧鏍囪瘑鍒楄〃 -->
- <el-table
- :data="filteredList"
- style="width: 100%"
- v-loading="loading"
- border
- stripe
- height="calc(100vh - 22em)"
- >
- <el-table-column prop="id" label="ID" width="80" align="center"/>
- <el-table-column prop="productName" label="浜у搧鍚嶇О" width="150" />
- <el-table-column prop="productCode" label="浜у搧缂栫爜" width="120" />
- <el-table-column prop="batchNo" label="鎵规鍙�" width="120" />
- <el-table-column prop="identifierType" label="鏍囪瘑绫诲瀷" width="100">
+ <el-table :data="filteredList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)">
+ <el-table-column prop="id"
+ label="ID"
+ width="80"
+ align="center" />
+ <el-table-column prop="productName"
+ label="浜у搧鍚嶇О"
+ width="150" />
+ <el-table-column prop="productCode"
+ label="浜у搧缂栫爜"
+ width="120" />
+ <el-table-column prop="batchNo"
+ label="鎵规鍙�"
+ width="120" />
+ <el-table-column prop="identifierType"
+ label="鏍囪瘑绫诲瀷"
+ width="100">
<template #default="scope">
<el-tag :type="getIdentifierTypeType(scope.row.identifierType)">
{{ scope.row.identifierType }}
</el-tag>
</template>
</el-table-column>
- <el-table-column prop="identifierCode" label="鏍囪瘑鐮�" />
- <el-table-column prop="status" label="鐘舵��" width="100">
+ <el-table-column prop="identifierCode"
+ label="鏍囪瘑鐮�" />
+ <el-table-column prop="status"
+ label="鐘舵��"
+ width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
- <el-table-column prop="generateTime" label="鐢熸垚鏃堕棿" width="160" />
- <el-table-column label="鎿嶄綔" fixed="right" align="center" width="280">
+ <el-table-column prop="generateTime"
+ label="鐢熸垚鏃堕棿"
+ width="160" />
+ <el-table-column label="鎿嶄綔"
+ fixed="right"
+ align="center"
+ width="280">
<template #default="scope">
- <el-button link type="primary" @click="handleView(scope.row)">鏌ョ湅</el-button>
- <el-button link type="primary" @click="handleEdit(scope.row)">缂栬緫</el-button>
- <el-button link type="success" @click="generateQRCode(scope.row)">鐢熸垚浜岀淮鐮�</el-button>
- <el-button link type="primary" @click="handleExport(scope.row)">瀵煎嚭</el-button>
- <el-button link type="primary" @click="handleReassign(scope.row)" v-if="scope.row.status === '宸插垎閰�'">閲嶆柊鍒嗛厤</el-button>
- <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ <el-button link
+ type="primary"
+ @click="handleView(scope.row)">鏌ョ湅</el-button>
+ <el-button link
+ type="primary"
+ @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button link
+ type="success"
+ @click="generateQRCode(scope.row)">鐢熸垚浜岀淮鐮�</el-button>
+ <el-button link
+ type="primary"
+ @click="handleExport(scope.row)">瀵煎嚭</el-button>
+ <el-button link
+ type="primary"
+ @click="handleReassign(scope.row)"
+ v-if="scope.row.status === '宸插垎閰�'">閲嶆柊鍒嗛厤</el-button>
+ <el-button link
+ type="danger"
+ @click="handleDelete(scope.row)">鍒犻櫎</el-button>
</template>
</el-table-column>
</el-table>
-
<!-- 鍒嗛〉 -->
- <pagination
- :total="pagination.total"
- layout="total, sizes, prev, pager, next, jumper"
- :page="pagination.currentPage"
- :limit="pagination.pageSize"
- @pagination="handleCurrentChange"
- />
+ <pagination :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.currentPage"
+ :limit="pagination.pageSize"
+ @pagination="handleCurrentChange" />
</el-card>
-
<!-- 鏂板/缂栬緫瀵硅瘽妗� -->
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
- <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+ <el-dialog v-model="dialogVisible"
+ :title="dialogTitle"
+ width="700px">
+ <el-form :model="form"
+ :rules="rules"
+ ref="formRef"
+ label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="浜у搧鍚嶇О" prop="productName">
- <el-input v-model="form.productName" placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"></el-input>
+ <el-form-item label="浜у搧鍚嶇О"
+ prop="productName">
+ <el-input v-model="form.productName"
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="浜у搧缂栫爜" prop="productCode">
- <el-input v-model="form.productCode" placeholder="璇疯緭鍏ヤ骇鍝佺紪鐮�"></el-input>
+ <el-form-item label="浜у搧缂栫爜"
+ prop="productCode">
+ <el-input v-model="form.productCode"
+ placeholder="璇疯緭鍏ヤ骇鍝佺紪鐮�"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="鎵规鍙�" prop="batchNo">
- <el-input v-model="form.batchNo" placeholder="璇疯緭鍏ユ壒娆″彿"></el-input>
+ <el-form-item label="鎵规鍙�"
+ prop="batchNo">
+ <el-input v-model="form.batchNo"
+ placeholder="璇疯緭鍏ユ壒娆″彿"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="鏍囪瘑绫诲瀷" prop="identifierType">
- <el-select v-model="form.identifierType" placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷" style="width: 100%">
- <el-option label="浜岀淮鐮�" value="浜岀淮鐮�"></el-option>
- <el-option label="闃蹭吉鐮�" value="闃蹭吉鐮�"></el-option>
+ <el-form-item label="鏍囪瘑绫诲瀷"
+ prop="identifierType">
+ <el-select v-model="form.identifierType"
+ placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷"
+ style="width: 100%">
+ <el-option label="浜岀淮鐮�"
+ value="浜岀淮鐮�"></el-option>
+ <el-option label="闃蹭吉鐮�"
+ value="闃蹭吉鐮�"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="鐢熸垚鏁伴噺" prop="quantity">
- <el-input-number v-model="form.quantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
+ <el-form-item label="鐢熸垚鏁伴噺"
+ prop="quantity">
+ <el-input-number v-model="form.quantity"
+ :min="1"
+ :max="10000"
+ style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="鐘舵��" prop="status">
- <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
- <el-option label="宸茬敓鎴�" value="宸茬敓鎴�"></el-option>
- <el-option label="宸插垎閰�" value="宸插垎閰�"></el-option>
- <el-option label="宸蹭娇鐢�" value="宸蹭娇鐢�"></el-option>
- <el-option label="宸蹭綔搴�" value="宸蹭綔搴�"></el-option>
+ <el-form-item label="鐘舵��"
+ prop="status">
+ <el-select v-model="form.status"
+ placeholder="璇烽�夋嫨鐘舵��"
+ style="width: 100%">
+ <el-option label="宸茬敓鎴�"
+ value="宸茬敓鎴�"></el-option>
+ <el-option label="宸插垎閰�"
+ value="宸插垎閰�"></el-option>
+ <el-option label="宸蹭娇鐢�"
+ value="宸蹭娇鐢�"></el-option>
+ <el-option label="宸蹭綔搴�"
+ value="宸蹭綔搴�"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
- <el-form-item label="澶囨敞" prop="remark">
- <el-input type="textarea" v-model="form.remark" placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" rows="3"></el-input>
+ <el-form-item label="澶囨敞"
+ prop="remark">
+ <el-input type="textarea"
+ v-model="form.remark"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+ rows="3"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
+ <el-button type="primary"
+ @click="handleSubmit">纭� 瀹�</el-button>
<el-button @click="dialogVisible = false">鍙� 娑�</el-button>
- <el-button type="primary" @click="handleSubmit">纭� 瀹�</el-button>
</div>
</template>
</el-dialog>
-
<!-- 鏍囪瘑鐢熸垚瀵硅瘽妗� -->
- <el-dialog v-model="generateDialogVisible" title="鏍囪瘑鐢熸垚" width="500px">
+ <el-dialog v-model="generateDialogVisible"
+ title="鏍囪瘑鐢熸垚"
+ width="500px">
<el-form label-width="100px">
<el-form-item label="浜у搧鍚嶇О">
<span>{{ currentProduct.productName }}</span>
@@ -167,30 +239,45 @@
<el-form-item label="鏍囪瘑绫诲瀷">
<span>{{ currentProduct.identifierType }}</span>
</el-form-item>
- <el-form-item label="鐢熸垚鏁伴噺" prop="generateQuantity">
- <el-input-number v-model="generateQuantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
+ <el-form-item label="鐢熸垚鏁伴噺"
+ prop="generateQuantity">
+ <el-input-number v-model="generateQuantity"
+ :min="1"
+ :max="10000"
+ style="width: 100%"></el-input-number>
</el-form-item>
- <el-form-item label="缂栫爜瑙勫垯" prop="codeRule">
- <el-select v-model="codeRule" placeholder="璇烽�夋嫨缂栫爜瑙勫垯" style="width: 100%">
- <el-option label="浜у搧缂栫爜+鎵规鍙�+搴忓彿" value="浜у搧缂栫爜+鎵规鍙�+搴忓彿"></el-option>
- <el-option label="鏃堕棿鎴�+闅忔満鏁�" value="鏃堕棿鎴�+闅忔満鏁�"></el-option>
- <el-option label="鑷畾涔夎鍒�" value="鑷畾涔夎鍒�"></el-option>
+ <el-form-item label="缂栫爜瑙勫垯"
+ prop="codeRule">
+ <el-select v-model="codeRule"
+ placeholder="璇烽�夋嫨缂栫爜瑙勫垯"
+ style="width: 100%">
+ <el-option label="浜у搧缂栫爜+鎵规鍙�+搴忓彿"
+ value="浜у搧缂栫爜+鎵规鍙�+搴忓彿"></el-option>
+ <el-option label="鏃堕棿鎴�+闅忔満鏁�"
+ value="鏃堕棿鎴�+闅忔満鏁�"></el-option>
+ <el-option label="鑷畾涔夎鍒�"
+ value="鑷畾涔夎鍒�"></el-option>
</el-select>
</el-form-item>
- <el-form-item label="鑷畾涔夊墠缂�" prop="customPrefix" v-if="codeRule === '鑷畾涔夎鍒�'">
- <el-input v-model="customPrefix" placeholder="璇疯緭鍏ヨ嚜瀹氫箟鍓嶇紑"></el-input>
+ <el-form-item label="鑷畾涔夊墠缂�"
+ prop="customPrefix"
+ v-if="codeRule === '鑷畾涔夎鍒�'">
+ <el-input v-model="customPrefix"
+ placeholder="璇疯緭鍏ヨ嚜瀹氫箟鍓嶇紑"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
+ <el-button type="primary"
+ @click="generateIdentifiers">鐢� 鎴�</el-button>
<el-button @click="generateDialogVisible = false">鍙� 娑�</el-button>
- <el-button type="primary" @click="generateIdentifiers">鐢� 鎴�</el-button>
</div>
</template>
</el-dialog>
-
<!-- 閲嶆柊鍒嗛厤瀵硅瘽妗� -->
- <el-dialog v-model="reassignDialogVisible" title="閲嶆柊鍒嗛厤鏍囪瘑" width="500px">
+ <el-dialog v-model="reassignDialogVisible"
+ title="閲嶆柊鍒嗛厤鏍囪瘑"
+ width="500px">
<el-form label-width="100px">
<el-form-item label="浜у搧鍚嶇О">
<span>{{ currentProduct.productName }}</span>
@@ -198,26 +285,38 @@
<el-form-item label="鏍囪瘑鐮�">
<span>{{ currentProduct.identifierCode }}</span>
</el-form-item>
- <el-form-item label="鏂版壒娆″彿" prop="newBatchNo">
- <el-input v-model="newBatchNo" placeholder="璇疯緭鍏ユ柊鎵规鍙�"></el-input>
+ <el-form-item label="鏂版壒娆″彿"
+ prop="newBatchNo">
+ <el-input v-model="newBatchNo"
+ placeholder="璇疯緭鍏ユ柊鎵规鍙�"></el-input>
</el-form-item>
- <el-form-item label="鍒嗛厤鍘熷洜" prop="reassignReason">
- <el-input type="textarea" v-model="reassignReason" rows="3" placeholder="璇疯緭鍏ラ噸鏂板垎閰嶅師鍥�"></el-input>
+ <el-form-item label="鍒嗛厤鍘熷洜"
+ prop="reassignReason">
+ <el-input type="textarea"
+ v-model="reassignReason"
+ rows="3"
+ placeholder="璇疯緭鍏ラ噸鏂板垎閰嶅師鍥�"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
+ <el-button type="primary"
+ @click="saveReassign">纭� 瀹�</el-button>
<el-button @click="reassignDialogVisible = false">鍙� 娑�</el-button>
- <el-button type="primary" @click="saveReassign">纭� 瀹�</el-button>
</div>
</template>
</el-dialog>
-
<!-- 浜岀淮鐮侀瑙堝璇濇 -->
- <el-dialog v-model="qrCodeDialogVisible" title="浜岀淮鐮侀瑙�" width="500px" center>
+ <el-dialog v-model="qrCodeDialogVisible"
+ title="浜岀淮鐮侀瑙�"
+ width="500px"
+ center>
<div class="qr-preview-container">
- <div v-if="qrCodeUrl" class="qr-image-container">
- <img :src="qrCodeUrl" alt="浜岀淮鐮�" class="qr-image" />
+ <div v-if="qrCodeUrl"
+ class="qr-image-container">
+ <img :src="qrCodeUrl"
+ alt="浜岀淮鐮�"
+ class="qr-image" />
<div class="qr-info">
<p><strong>浜у搧鍚嶇О锛�</strong>{{ currentQRProduct.productName }}</p>
<p><strong>浜у搧缂栫爜锛�</strong>{{ currentQRProduct.productCode }}</p>
@@ -226,29 +325,27 @@
<p><strong>鏍囪瘑绫诲瀷锛�</strong>{{ currentQRProduct.identifierType }}</p>
</div>
</div>
- <div v-else class="qr-loading">
- <el-icon class="is-loading"><Loading /></el-icon>
+ <div v-else
+ class="qr-loading">
+ <el-icon class="is-loading">
+ <Loading />
+ </el-icon>
<p>姝e湪鐢熸垚浜岀淮鐮�...</p>
</div>
</div>
-
<template #footer>
<div class="dialog-footer">
<el-button @click="qrCodeDialogVisible = false">鍏抽棴</el-button>
- <el-button
- v-if="qrCodeUrl"
- type="primary"
- @click="copyQRContent"
- icon="CopyDocument"
- >
+ <el-button v-if="qrCodeUrl"
+ type="primary"
+ @click="copyQRContent"
+ icon="CopyDocument">
澶嶅埗鍐呭
</el-button>
- <el-button
- v-if="qrCodeUrl"
- type="success"
- @click="downloadQRCode"
- icon="Download"
- >
+ <el-button v-if="qrCodeUrl"
+ type="success"
+ @click="downloadQRCode"
+ icon="Download">
涓嬭浇浜岀淮鐮�
</el-button>
</div>
@@ -258,451 +355,465 @@
</template>
<script setup>
-import { ref, reactive, computed } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { Plus, Search, Loading, Download } from '@element-plus/icons-vue'
-import Pagination from '@/components/PIMTable/Pagination.vue'
-import QRCode from 'qrcode'
+ import { ref, reactive, computed } from "vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import { Plus, Search, Loading, Download } from "@element-plus/icons-vue";
+ import Pagination from "@/components/PIMTable/Pagination.vue";
+ import QRCode from "qrcode";
-// 鍝嶅簲寮忔暟鎹�
-const loading = ref(false)
-const searchForm = reactive({
- productName: '',
- identifierType: '',
- status: ''
-})
+ // 鍝嶅簲寮忔暟鎹�
+ const loading = ref(false);
+ const searchForm = reactive({
+ productName: "",
+ identifierType: "",
+ status: "",
+ });
-const identifierList = ref([
- {
- id: 1,
- productName: '宸ヤ笟浼犳劅鍣ˋ鍨�',
- productCode: 'SENSOR001',
- batchNo: 'B202312001',
- identifierType: '浜岀淮鐮�',
- identifierCode: 'QR_SENSOR001_B202312001_001',
- status: '宸插垎閰�',
- generateTime: '2023-12-01 10:00:00',
- remark: '閲嶈浜у搧鏍囪瘑'
- },
- {
- id: 2,
- productName: '鎺у埗闈㈡澘B鍨�',
- productCode: 'PANEL002',
- batchNo: 'B202312002',
- identifierType: '闃蹭吉鐮�',
- identifierCode: 'SEC_PANEL002_B202312002_001',
- status: '宸茬敓鎴�',
- generateTime: '2023-12-02 14:30:00',
- remark: '甯歌浜у搧鏍囪瘑'
- },
- {
- id: 3,
- productName: '鏁版嵁閲囬泦鍣–鍨�',
- productCode: 'COLLECTOR003',
- batchNo: 'B202312003',
- identifierType: '闃蹭吉鐮�',
- identifierCode: 'SEC_COLLECTOR003_B202312003_001',
- status: '宸蹭娇鐢�',
- generateTime: '2023-12-03 09:15:00',
- remark: '娴嬭瘯浜у搧鏍囪瘑'
- }
-])
+ const identifierList = ref([
+ {
+ id: 1,
+ productName: "宸ヤ笟浼犳劅鍣ˋ鍨�",
+ productCode: "SENSOR001",
+ batchNo: "B202312001",
+ identifierType: "浜岀淮鐮�",
+ identifierCode: "QR_SENSOR001_B202312001_001",
+ status: "宸插垎閰�",
+ generateTime: "2023-12-01 10:00:00",
+ remark: "閲嶈浜у搧鏍囪瘑",
+ },
+ {
+ id: 2,
+ productName: "鎺у埗闈㈡澘B鍨�",
+ productCode: "PANEL002",
+ batchNo: "B202312002",
+ identifierType: "闃蹭吉鐮�",
+ identifierCode: "SEC_PANEL002_B202312002_001",
+ status: "宸茬敓鎴�",
+ generateTime: "2023-12-02 14:30:00",
+ remark: "甯歌浜у搧鏍囪瘑",
+ },
+ {
+ id: 3,
+ productName: "鏁版嵁閲囬泦鍣–鍨�",
+ productCode: "COLLECTOR003",
+ batchNo: "B202312003",
+ identifierType: "闃蹭吉鐮�",
+ identifierCode: "SEC_COLLECTOR003_B202312003_001",
+ status: "宸蹭娇鐢�",
+ generateTime: "2023-12-03 09:15:00",
+ remark: "娴嬭瘯浜у搧鏍囪瘑",
+ },
+ ]);
-const pagination = reactive({
- total: 3,
- currentPage: 1,
- pageSize: 10
-})
+ const pagination = reactive({
+ total: 3,
+ currentPage: 1,
+ pageSize: 10,
+ });
-const dialogVisible = ref(false)
-const dialogTitle = ref('鏂板鏍囪瘑')
-const form = reactive({
- productName: '',
- productCode: '',
- batchNo: '',
- identifierType: '',
- quantity: 1,
- status: '宸茬敓鎴�',
- remark: ''
-})
+ const dialogVisible = ref(false);
+ const dialogTitle = ref("鏂板鏍囪瘑");
+ const form = reactive({
+ productName: "",
+ productCode: "",
+ batchNo: "",
+ identifierType: "",
+ quantity: 1,
+ status: "宸茬敓鎴�",
+ remark: "",
+ });
-const rules = {
- productName: [{ required: true, message: '璇疯緭鍏ヤ骇鍝佸悕绉�', trigger: 'blur' }],
- productCode: [{ required: true, message: '璇疯緭鍏ヤ骇鍝佺紪鐮�', trigger: 'blur' }],
- batchNo: [{ required: true, message: '璇疯緭鍏ユ壒娆″彿', trigger: 'blur' }],
- identifierType: [{ required: true, message: '璇烽�夋嫨鏍囪瘑绫诲瀷', trigger: 'change' }],
- quantity: [{ required: true, message: '璇疯緭鍏ョ敓鎴愭暟閲�', trigger: 'blur' }],
- status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
-}
+ const rules = {
+ productName: [{ required: true, message: "璇疯緭鍏ヤ骇鍝佸悕绉�", trigger: "blur" }],
+ productCode: [{ required: true, message: "璇疯緭鍏ヤ骇鍝佺紪鐮�", trigger: "blur" }],
+ batchNo: [{ required: true, message: "璇疯緭鍏ユ壒娆″彿", trigger: "blur" }],
+ identifierType: [
+ { required: true, message: "璇烽�夋嫨鏍囪瘑绫诲瀷", trigger: "change" },
+ ],
+ quantity: [{ required: true, message: "璇疯緭鍏ョ敓鎴愭暟閲�", trigger: "blur" }],
+ status: [{ required: true, message: "璇烽�夋嫨鐘舵��", trigger: "change" }],
+ };
-const isEdit = ref(false)
-const editId = ref(null)
-const generateDialogVisible = ref(false)
-const reassignDialogVisible = ref(false)
-const currentProduct = ref({})
-const generateQuantity = ref(1)
-const codeRule = ref('')
-const customPrefix = ref('')
-const newBatchNo = ref('')
-const reassignReason = ref('')
-const formRef = ref()
+ const isEdit = ref(false);
+ const editId = ref(null);
+ const generateDialogVisible = ref(false);
+ const reassignDialogVisible = ref(false);
+ const currentProduct = ref({});
+ const generateQuantity = ref(1);
+ const codeRule = ref("");
+ const customPrefix = ref("");
+ const newBatchNo = ref("");
+ const reassignReason = ref("");
+ const formRef = ref();
-// 浜岀淮鐮佺浉鍏冲彉閲�
-const qrCodeDialogVisible = ref(false)
-const qrCodeUrl = ref('')
-const currentQRProduct = ref({})
+ // 浜岀淮鐮佺浉鍏冲彉閲�
+ const qrCodeDialogVisible = ref(false);
+ const qrCodeUrl = ref("");
+ const currentQRProduct = ref({});
-// 璁$畻灞炴��
-const filteredList = computed(() => {
- let list = identifierList.value
- if (searchForm.productName) {
- list = list.filter(item => item.productName.includes(searchForm.productName))
- }
- if (searchForm.identifierType) {
- list = list.filter(item => item.identifierType === searchForm.identifierType)
- }
- if (searchForm.status) {
- list = list.filter(item => item.status === searchForm.status)
- }
- return list
-})
+ // 璁$畻灞炴��
+ const filteredList = computed(() => {
+ let list = identifierList.value;
+ if (searchForm.productName) {
+ list = list.filter(item =>
+ item.productName.includes(searchForm.productName)
+ );
+ }
+ if (searchForm.identifierType) {
+ list = list.filter(
+ item => item.identifierType === searchForm.identifierType
+ );
+ }
+ if (searchForm.status) {
+ list = list.filter(item => item.status === searchForm.status);
+ }
+ return list;
+ });
-// 鏂规硶
-const getIdentifierTypeType = (type) => {
- const typeMap = {
- '浜岀淮鐮�': 'success',
- '闃蹭吉鐮�': 'warning'
- }
- return typeMap[type] || 'info'
-}
+ // 鏂规硶
+ const getIdentifierTypeType = type => {
+ const typeMap = {
+ 浜岀淮鐮�: "success",
+ 闃蹭吉鐮�: "warning",
+ };
+ return typeMap[type] || "info";
+ };
-const getStatusType = (status) => {
- const statusMap = {
- '宸茬敓鎴�': 'info',
- '宸插垎閰�': 'primary',
- '宸蹭娇鐢�': 'success',
- '宸蹭綔搴�': 'danger'
- }
- return statusMap[status] || 'info'
-}
+ const getStatusType = status => {
+ const statusMap = {
+ 宸茬敓鎴�: "info",
+ 宸插垎閰�: "primary",
+ 宸蹭娇鐢�: "success",
+ 宸蹭綔搴�: "danger",
+ };
+ return statusMap[status] || "info";
+ };
-const handleSearch = () => {
- // 鎼滅储閫昏緫宸插湪computed涓鐞�
-}
+ const handleSearch = () => {
+ // 鎼滅储閫昏緫宸插湪computed涓鐞�
+ };
-const resetSearch = () => {
- searchForm.productName = ''
- searchForm.identifierType = ''
- searchForm.status = ''
-}
+ const resetSearch = () => {
+ searchForm.productName = "";
+ searchForm.identifierType = "";
+ searchForm.status = "";
+ };
-const handleAdd = () => {
- dialogTitle.value = '鏂板鏍囪瘑'
- isEdit.value = false
- form.productName = ''
- form.productCode = ''
- form.batchNo = ''
- form.identifierType = ''
- form.quantity = 1
- form.status = '宸茬敓鎴�'
- form.remark = ''
- dialogVisible.value = true
-}
+ const handleAdd = () => {
+ dialogTitle.value = "鏂板鏍囪瘑";
+ isEdit.value = false;
+ form.productName = "";
+ form.productCode = "";
+ form.batchNo = "";
+ form.identifierType = "";
+ form.quantity = 1;
+ form.status = "宸茬敓鎴�";
+ form.remark = "";
+ dialogVisible.value = true;
+ };
-const handleView = (row) => {
- // 鏌ョ湅鏍囪瘑璇︽儏
- ElMessage.info('鏌ョ湅鏍囪瘑璇︽儏鍔熻兘寰呭疄鐜�')
-}
+ const handleView = row => {
+ // 鏌ョ湅鏍囪瘑璇︽儏
+ ElMessage.info("鏌ョ湅鏍囪瘑璇︽儏鍔熻兘寰呭疄鐜�");
+ };
-const handleEdit = (row) => {
- dialogTitle.value = '缂栬緫鏍囪瘑'
- isEdit.value = true
- editId.value = row.id
- Object.assign(form, row)
- dialogVisible.value = true
-}
+ const handleEdit = row => {
+ dialogTitle.value = "缂栬緫鏍囪瘑";
+ isEdit.value = true;
+ editId.value = row.id;
+ Object.assign(form, row);
+ dialogVisible.value = true;
+ };
-const handleExport = (row) => {
- // 瀵煎嚭鏍囪瘑
- ElMessage.success(`宸插鍑烘爣璇�: ${row.identifierCode}`)
-}
+ const handleExport = row => {
+ // 瀵煎嚭鏍囪瘑
+ ElMessage.success(`宸插鍑烘爣璇�: ${row.identifierCode}`);
+ };
-const handleReassign = (row) => {
- currentProduct.value = row
- newBatchNo.value = ''
- reassignReason.value = ''
- reassignDialogVisible.value = true
-}
+ const handleReassign = row => {
+ currentProduct.value = row;
+ newBatchNo.value = "";
+ reassignReason.value = "";
+ reassignDialogVisible.value = true;
+ };
-const handleDelete = (row) => {
- ElMessageBox.confirm('纭鍒犻櫎璇ユ爣璇嗗悧锛�', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).then(() => {
- const index = identifierList.value.findIndex(item => item.id === row.id)
+ const handleDelete = row => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ユ爣璇嗗悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ const index = identifierList.value.findIndex(item => item.id === row.id);
+ if (index > -1) {
+ identifierList.value.splice(index, 1);
+ pagination.total--;
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ }
+ });
+ };
+
+ // 鐢熸垚浜岀淮鐮�
+ const generateQRCode = async row => {
+ try {
+ // 妫�鏌ュ繀瑕佸瓧娈�
+ if (!row.productName || !row.productCode || !row.batchNo) {
+ ElMessage.warning("浜у搧淇℃伅涓嶅畬鏁达紝鏃犳硶鐢熸垚浜岀淮鐮�");
+ return;
+ }
+
+ currentQRProduct.value = row;
+ qrCodeUrl.value = "";
+ qrCodeDialogVisible.value = true;
+
+ // 鏋勫缓浜岀淮鐮佸唴瀹�
+ let qrContent = "";
+ if (row.identifierType === "浜岀淮鐮�") {
+ qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`;
+ } else if (row.identifierType === "闃蹭吉鐮�") {
+ // 闃蹭吉鐮佹牸寮忥細SEC_浜у搧缂栫爜_鎵规鍙穇鏃堕棿鎴砡闅忔満鏁�
+ const timestamp = Date.now();
+ const random = Math.random().toString(36).substr(2, 8);
+ qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`;
+ }
+
+ // 鐢熸垚浜岀淮鐮�
+ qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
+ width: 256,
+ margin: 2,
+ color: {
+ dark: "#000000",
+ light: "#FFFFFF",
+ },
+ errorCorrectionLevel: row.identifierType === "闃蹭吉鐮�" ? "H" : "M",
+ });
+
+ ElMessage.success("浜岀淮鐮佺敓鎴愭垚鍔燂紒");
+ } catch (error) {
+ console.error("鐢熸垚浜岀淮鐮佸け璐�:", error);
+ ElMessage.error("鐢熸垚浜岀淮鐮佸け璐ワ細" + error.message);
+ qrCodeDialogVisible.value = false;
+ }
+ };
+
+ // 涓嬭浇浜岀淮鐮�
+ const downloadQRCode = () => {
+ if (!qrCodeUrl.value) {
+ ElMessage.warning("璇峰厛鐢熸垚浜岀淮鐮�");
+ return;
+ }
+
+ const a = document.createElement("a");
+ a.href = qrCodeUrl.value;
+ a.download = `${currentQRProduct.value.productName}_${
+ currentQRProduct.value.identifierType
+ }_${new Date().getTime()}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ ElMessage.success("涓嬭浇鎴愬姛锛�");
+ };
+
+ // 澶嶅埗浜岀淮鐮佸唴瀹�
+ const copyQRContent = async () => {
+ if (!currentQRProduct.value) {
+ ElMessage.warning("娌℃湁鍙鍒剁殑鍐呭");
+ return;
+ }
+
+ try {
+ let content = "";
+ if (currentQRProduct.value.identifierType === "浜岀淮鐮�") {
+ content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`;
+ } else if (currentQRProduct.value.identifierType === "闃蹭吉鐮�") {
+ const timestamp = Date.now();
+ const random = Math.random().toString(36).substr(2, 8);
+ content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`;
+ }
+
+ await navigator.clipboard.writeText(content);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ } catch (error) {
+ // 闄嶇骇鏂规
+ const textArea = document.createElement("textarea");
+ textArea.value = content;
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textArea);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ }
+ };
+
+ const generateIdentifiers = () => {
+ if (!codeRule.value) {
+ ElMessage.warning("璇烽�夋嫨缂栫爜瑙勫垯");
+ return;
+ }
+
+ // 鐢熸垚鏍囪瘑鐨勯�昏緫
+ const newIdentifiers = [];
+ for (let i = 1; i <= generateQuantity.value; i++) {
+ let identifierCode = "";
+ if (codeRule.value === "浜у搧缂栫爜+鎵规鍙�+搴忓彿") {
+ identifierCode = `${currentProduct.value.productCode}_${
+ currentProduct.value.batchNo
+ }_${String(i).padStart(3, "0")}`;
+ } else if (codeRule.value === "鏃堕棿鎴�+闅忔満鏁�") {
+ identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
+ } else if (codeRule.value === "鑷畾涔夎鍒�") {
+ identifierCode = `${customPrefix.value || "CUSTOM"}_${Date.now()}_${i}`;
+ }
+
+ newIdentifiers.push({
+ id: Math.max(...identifierList.value.map(item => item.id)) + i,
+ productName: currentProduct.value.productName,
+ productCode: currentProduct.value.productCode,
+ batchNo: currentProduct.value.batchNo,
+ identifierType: currentProduct.value.identifierType,
+ identifierCode: identifierCode,
+ status: "宸茬敓鎴�",
+ generateTime: new Date().toLocaleString(),
+ remark: "鎵归噺鐢熸垚",
+ });
+ }
+
+ identifierList.value.push(...newIdentifiers);
+ pagination.total += newIdentifiers.length;
+ ElMessage.success(`鎴愬姛鐢熸垚 ${newIdentifiers.length} 涓爣璇哷);
+ generateDialogVisible.value = false;
+ };
+
+ const saveReassign = () => {
+ if (!newBatchNo.value) {
+ ElMessage.warning("璇疯緭鍏ユ柊鎵规鍙�");
+ return;
+ }
+
+ const index = identifierList.value.findIndex(
+ item => item.id === currentProduct.value.id
+ );
if (index > -1) {
- identifierList.value.splice(index, 1)
- pagination.total--
- ElMessage.success('鍒犻櫎鎴愬姛')
+ identifierList.value[index].batchNo = newBatchNo.value;
+ identifierList.value[index].status = "宸插垎閰�";
+ ElMessage.success("鏍囪瘑閲嶆柊鍒嗛厤鎴愬姛");
+ reassignDialogVisible.value = false;
}
- })
-}
+ };
-// 鐢熸垚浜岀淮鐮�
-const generateQRCode = async (row) => {
- try {
- // 妫�鏌ュ繀瑕佸瓧娈�
- if (!row.productName || !row.productCode || !row.batchNo) {
- ElMessage.warning('浜у搧淇℃伅涓嶅畬鏁达紝鏃犳硶鐢熸垚浜岀淮鐮�')
- return
- }
-
- currentQRProduct.value = row
- qrCodeUrl.value = ''
- qrCodeDialogVisible.value = true
-
- // 鏋勫缓浜岀淮鐮佸唴瀹�
- let qrContent = ''
- if (row.identifierType === '浜岀淮鐮�') {
- qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`
- } else if (row.identifierType === '闃蹭吉鐮�') {
- // 闃蹭吉鐮佹牸寮忥細SEC_浜у搧缂栫爜_鎵规鍙穇鏃堕棿鎴砡闅忔満鏁�
- const timestamp = Date.now()
- const random = Math.random().toString(36).substr(2, 8)
- qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`
- }
-
- // 鐢熸垚浜岀淮鐮�
- qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
- width: 256,
- margin: 2,
- color: {
- dark: '#000000',
- light: '#FFFFFF'
- },
- errorCorrectionLevel: row.identifierType === '闃蹭吉鐮�' ? 'H' : 'M'
- })
-
- ElMessage.success('浜岀淮鐮佺敓鎴愭垚鍔燂紒')
-
- } catch (error) {
- console.error('鐢熸垚浜岀淮鐮佸け璐�:', error)
- ElMessage.error('鐢熸垚浜岀淮鐮佸け璐ワ細' + error.message)
- qrCodeDialogVisible.value = false
- }
-}
+ const handleSubmit = () => {
+ formRef.value.validate(valid => {
+ if (valid) {
+ if (isEdit.value) {
+ // 缂栬緫
+ const index = identifierList.value.findIndex(
+ item => item.id === editId.value
+ );
+ if (index > -1) {
+ identifierList.value[index] = { ...form, id: editId.value };
+ ElMessage.success("缂栬緫鎴愬姛");
+ }
+ } else {
+ // 鏂板
+ const newId =
+ Math.max(...identifierList.value.map(item => item.id)) + 1;
-// 涓嬭浇浜岀淮鐮�
-const downloadQRCode = () => {
- if (!qrCodeUrl.value) {
- ElMessage.warning('璇峰厛鐢熸垚浜岀淮鐮�')
- return
- }
-
- const a = document.createElement('a')
- a.href = qrCodeUrl.value
- a.download = `${currentQRProduct.value.productName}_${currentQRProduct.value.identifierType}_${new Date().getTime()}.png`
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
- ElMessage.success('涓嬭浇鎴愬姛锛�')
-}
+ // 鏍规嵁鏍囪瘑绫诲瀷鐢熸垚涓嶅悓鐨勬爣璇嗙爜
+ let identifierCode = "";
+ if (form.identifierType === "浜岀淮鐮�") {
+ identifierCode = `QR_${form.productCode}_${form.batchNo}_001`;
+ } else if (form.identifierType === "闃蹭吉鐮�") {
+ identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`;
+ }
-// 澶嶅埗浜岀淮鐮佸唴瀹�
-const copyQRContent = async () => {
- if (!currentQRProduct.value) {
- ElMessage.warning('娌℃湁鍙鍒剁殑鍐呭')
- return
- }
-
- try {
- let content = ''
- if (currentQRProduct.value.identifierType === '浜岀淮鐮�') {
- content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`
- } else if (currentQRProduct.value.identifierType === '闃蹭吉鐮�') {
- const timestamp = Date.now()
- const random = Math.random().toString(36).substr(2, 8)
- content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`
- }
-
- await navigator.clipboard.writeText(content)
- ElMessage.success('鍐呭宸插鍒跺埌鍓创鏉�')
- } catch (error) {
- // 闄嶇骇鏂规
- const textArea = document.createElement('textarea')
- textArea.value = content
- document.body.appendChild(textArea)
- textArea.select()
- document.execCommand('copy')
- document.body.removeChild(textArea)
- ElMessage.success('鍐呭宸插鍒跺埌鍓创鏉�')
- }
-}
-
-const generateIdentifiers = () => {
- if (!codeRule.value) {
- ElMessage.warning('璇烽�夋嫨缂栫爜瑙勫垯')
- return
- }
-
- // 鐢熸垚鏍囪瘑鐨勯�昏緫
- const newIdentifiers = []
- for (let i = 1; i <= generateQuantity.value; i++) {
- let identifierCode = ''
- if (codeRule.value === '浜у搧缂栫爜+鎵规鍙�+搴忓彿') {
- identifierCode = `${currentProduct.value.productCode}_${currentProduct.value.batchNo}_${String(i).padStart(3, '0')}`
- } else if (codeRule.value === '鏃堕棿鎴�+闅忔満鏁�') {
- identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`
- } else if (codeRule.value === '鑷畾涔夎鍒�') {
- identifierCode = `${customPrefix.value || 'CUSTOM'}_${Date.now()}_${i}`
- }
-
- newIdentifiers.push({
- id: Math.max(...identifierList.value.map(item => item.id)) + i,
- productName: currentProduct.value.productName,
- productCode: currentProduct.value.productCode,
- batchNo: currentProduct.value.batchNo,
- identifierType: currentProduct.value.identifierType,
- identifierCode: identifierCode,
- status: '宸茬敓鎴�',
- generateTime: new Date().toLocaleString(),
- remark: '鎵归噺鐢熸垚'
- })
- }
-
- identifierList.value.push(...newIdentifiers)
- pagination.total += newIdentifiers.length
- ElMessage.success(`鎴愬姛鐢熸垚 ${newIdentifiers.length} 涓爣璇哷)
- generateDialogVisible.value = false
-}
-
-const saveReassign = () => {
- if (!newBatchNo.value) {
- ElMessage.warning('璇疯緭鍏ユ柊鎵规鍙�')
- return
- }
-
- const index = identifierList.value.findIndex(item => item.id === currentProduct.value.id)
- if (index > -1) {
- identifierList.value[index].batchNo = newBatchNo.value
- identifierList.value[index].status = '宸插垎閰�'
- ElMessage.success('鏍囪瘑閲嶆柊鍒嗛厤鎴愬姛')
- reassignDialogVisible.value = false
- }
-}
-
-const handleSubmit = () => {
- formRef.value.validate((valid) => {
- if (valid) {
- if (isEdit.value) {
- // 缂栬緫
- const index = identifierList.value.findIndex(item => item.id === editId.value)
- if (index > -1) {
- identifierList.value[index] = { ...form, id: editId.value }
- ElMessage.success('缂栬緫鎴愬姛')
+ identifierList.value.push({
+ ...form,
+ id: newId,
+ identifierCode: identifierCode,
+ generateTime: new Date().toLocaleString(),
+ });
+ pagination.total++;
+ ElMessage.success("鏂板鎴愬姛");
}
- } else {
- // 鏂板
- const newId = Math.max(...identifierList.value.map(item => item.id)) + 1
-
- // 鏍规嵁鏍囪瘑绫诲瀷鐢熸垚涓嶅悓鐨勬爣璇嗙爜
- let identifierCode = ''
- if (form.identifierType === '浜岀淮鐮�') {
- identifierCode = `QR_${form.productCode}_${form.batchNo}_001`
- } else if (form.identifierType === '闃蹭吉鐮�') {
- identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`
- }
-
- identifierList.value.push({
- ...form,
- id: newId,
- identifierCode: identifierCode,
- generateTime: new Date().toLocaleString()
- })
- pagination.total++
- ElMessage.success('鏂板鎴愬姛')
- }
- dialogVisible.value = false
- }
- })
-}
+ dialogVisible.value = false;
+ }
+ });
+ };
-const handleCurrentChange = (val) => {
- pagination.currentPage = val.page
- pagination.pageSize = val.limit
-}
+ const handleCurrentChange = val => {
+ pagination.currentPage = val.page;
+ pagination.pageSize = val.limit;
+ };
</script>
<style scoped>
-.search-row {
- margin-bottom: 20px;
-}
+ .search-row {
+ margin-bottom: 20px;
+ }
-.quick-actions-row {
- margin-bottom: 20px;
-}
+ .quick-actions-row {
+ margin-bottom: 20px;
+ }
-.quick-actions-row .el-alert {
- margin-bottom: 0;
-}
+ .quick-actions-row .el-alert {
+ margin-bottom: 0;
+ }
-.quick-actions-row .el-alert p {
- margin: 5px 0;
- font-size: 14px;
- line-height: 1.5;
-}
+ .quick-actions-row .el-alert p {
+ margin: 5px 0;
+ font-size: 14px;
+ line-height: 1.5;
+ }
-/* 浜岀淮鐮侀瑙堟牱寮� */
-.qr-preview-container {
- text-align: center;
- padding: 20px;
-}
+ /* 浜岀淮鐮侀瑙堟牱寮� */
+ .qr-preview-container {
+ text-align: center;
+ padding: 20px;
+ }
-.qr-image-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 20px;
-}
+ .qr-image-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ }
-.qr-image {
- max-width: 100%;
- height: auto;
- border: 2px solid #e0e0e0;
- border-radius: 8px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-}
+ .qr-image {
+ max-width: 100%;
+ height: auto;
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
-.qr-info {
- text-align: left;
- background: #f8f9fa;
- padding: 15px;
- border-radius: 8px;
- min-width: 300px;
-}
+ .qr-info {
+ text-align: left;
+ background: #f8f9fa;
+ padding: 15px;
+ border-radius: 8px;
+ min-width: 300px;
+ }
-.qr-info p {
- margin: 8px 0;
- color: #666;
- font-size: 14px;
-}
+ .qr-info p {
+ margin: 8px 0;
+ color: #666;
+ font-size: 14px;
+ }
-.qr-loading {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 15px;
- padding: 40px 0;
-}
+ .qr-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 15px;
+ padding: 40px 0;
+ }
-.qr-loading .el-icon {
- font-size: 32px;
- color: #409EFF;
-}
+ .qr-loading .el-icon {
+ font-size: 32px;
+ color: #409eff;
+ }
-.qr-loading p {
- color: #666;
- margin: 0;
-}
+ .qr-loading p {
+ color: #666;
+ margin: 0;
+ }
</style>
diff --git a/src/views/productionManagement/operationScheduling/components/formDia.vue b/src/views/productionManagement/operationScheduling/components/formDia.vue
index a4f36bf..06b46ac 100644
--- a/src/views/productionManagement/operationScheduling/components/formDia.vue
+++ b/src/views/productionManagement/operationScheduling/components/formDia.vue
@@ -8,20 +8,47 @@
>
<el-button type="primary" @click="addRow" style="margin-bottom: 10px;">鏂板</el-button>
<span style="font-size: 18px;margin-left: 10px">寰呮帓浜ф暟閲忥細{{pendingNum}}</span>
+<!-- <div style="margin-bottom: 10px; margin-left: 10px;">-->
+<!-- <el-form-item label="棰嗙敤锛�" style="margin-bottom: 0;">-->
+<!-- <el-input v-model="receive" placeholder="璇疯緭鍏ラ鐢�" style="width: 200px;" />-->
+<!-- </el-form-item>-->
+<!-- </div>-->
<el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id">
- <el-table-column label="搴忓彿" width="60">
+ <el-table-column label="搴忓彿" width="60" align="center">
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
- <el-table-column label="宸ュ簭" prop="process">
+ <el-table-column label="宸ュ簭" prop="process" width="150">
<template #default="scope">
<el-input v-model="scope.row.process" placeholder="璇疯緭鍏ュ伐搴�" />
</template>
</el-table-column>
- <el-table-column label="鍗曚綅" prop="unit">
+ <el-table-column label="浜х嚎" prop="productionLine" width="150">
+ <template #default="scope">
+ <el-select
+ v-model="scope.row.productionLine"
+ placeholder="閫夋嫨浜х嚎"
+ style="width: 100%;"
+ clearable
+ >
+ <el-option
+ v-for="line in productionLines"
+ :key="line.value"
+ :label="line.label"
+ :value="line.value"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚綅" prop="unit" width="90">
<template #default="scope">
<el-input v-model="scope.row.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙e懗/鍝佸悕/瑙勬牸" prop="type" width="150">
+ <template #default="scope">
+ <el-input v-model="scope.row.type" placeholder="璇疯緭鍏�" />
</template>
</el-table-column>
<el-table-column label="鎺掍骇鏁伴噺" width="200" prop="schedulingNum">
@@ -50,17 +77,20 @@
/>
</template>
</el-table-column>
- <el-table-column label="鎺掍骇鏃ユ湡" prop="schedulingDate">
+ <el-table-column label="鎺掍骇鏃ユ湡" prop="schedulingDate" width="200">
<template #default="scope">
<el-date-picker v-model="scope.row.schedulingDate" type="date" placeholder="閫夋嫨鏃ユ湡" style="width: 100%;" value-format="YYYY-MM-DD" format="YYYY-MM-DD"/>
</template>
</el-table-column>
- <el-table-column label="鎺掍骇浜�" prop="schedulingUserId">
+ <el-table-column label="鎺掍骇浜�" prop="schedulingUserId" width="150">
<template #default="scope">
<el-select
v-model="scope.row.schedulingUserId"
placeholder="閫夋嫨浜哄憳"
style="width: 100%;"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
>
<el-option
v-for="user in userList"
@@ -69,6 +99,11 @@
:value="user.userId"
/>
</el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶囨敞" prop="remark" width="200">
+ <template #default="scope">
+ <el-input v-model="scope.row.remark" placeholder="璇疯緭鍏ュ娉�" />
</template>
</el-table-column>
<el-table-column label="鎿嶄綔" width="80">
@@ -88,7 +123,7 @@
</template>
<script setup>
-import {ref} from "vue";
+import {ref, getCurrentInstance} from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {processScheduling} from "@/api/productionManagement/operationScheduling.js";
const { proxy } = getCurrentInstance()
@@ -97,33 +132,56 @@
const dialogFormVisible = ref(false);
const operationType = ref('')
-const tableData = ref([
- { process: '', schedulingDate: '', schedulingNum: '', schedulingUserId: '', workHours: '', unit: '' }
-]);
+const tableData = ref([]);
const unitFromRow = ref('');
const idFromRow = ref('');
-const pendingNum = ref('');
+const specificationModelFromRow = ref('');
+const pendingNum = ref(0);
const userList = ref([])
+const receive = ref('')
+const sunqianUserId = ref('')
+// 浜х嚎閫夐」
+const productionLines = ref([
+ { label: '浜х嚎1', value: '浜х嚎1' },
+ { label: '浜х嚎2', value: '浜х嚎2' },
+ { label: '浜х嚎3', value: '浜х嚎3' },
+ { label: '浜х嚎4', value: '浜х嚎4' }
+])
// 鎵撳紑寮规
const openDialog = (type, row) => {
operationType.value = type;
dialogFormVisible.value = true;
+ pendingNum.value = row?.pendingNum ?? 0;
+ unitFromRow.value = row?.unit ?? '';
+ idFromRow.value = row?.id ?? '';
+ specificationModelFromRow.value = row?.specificationModel ?? '';
+
userListNoPageByTenantId().then((res) => {
userList.value = res.data;
+ // 鎵惧埌瀛欏�╃殑鐢ㄦ埛ID骞惰缃负榛樿鍊�
+ const sunqianUser = userList.value.find(user => user.nickName === '瀛欏��');
+ if (sunqianUser) {
+ sunqianUserId.value = sunqianUser.userId;
+ }
+ // 鍦ㄧ敤鎴峰垪琛ㄥ姞杞藉畬鎴愬悗鍒涘缓琛屾暟鎹紝骞跺皢浜х嚎鏁版嵁甯﹀叆
+ tableData.value = [createRow(row)];
});
- pendingNum.value = row.pendingNum
- if (row && row.unit !== undefined) {
- unitFromRow.value = row.unit;
- idFromRow.value = row.id;
- tableData.value.forEach(item => {
- item.unit = row.unit;
- item.id = row.id;
- });
- } else {
- unitFromRow.value = '';
- }
}
+
+const createRow = (row) => ({
+ id: idFromRow.value,
+ process: '鍖呰',
+ schedulingDate: '',
+ schedulingNum: null,
+ schedulingUserId: sunqianUserId.value, // 榛樿璁剧疆涓哄瓩鍊╃殑鐢ㄦ埛ID
+ workHours: null,
+ unit: unitFromRow.value,
+ remark: '',
+ type: specificationModelFromRow.value,
+ productionLine: row?.productionLine ?? '', // 浠庤鏁版嵁涓幏鍙栦骇绾夸俊鎭�
+});
+
const submitForm = () => {
// 1. 妫�鏌ユ瘡涓�琛屾槸鍚﹀~鍐欏畬鏁�
for (let i = 0; i < tableData.value.length; i++) {
@@ -134,7 +192,8 @@
row.schedulingNum === '' || row.schedulingNum === null ||
!row.schedulingUserId ||
row.workHours === '' || row.workHours === null ||
- !row.unit
+ !row.unit ||
+ !row.productionLine
) {
proxy.$modal.msgError(`绗�${i + 1}琛屾暟鎹湭濉啓瀹屾暣`);
return;
@@ -148,7 +207,15 @@
proxy.$modal.msgError('鎺掍骇鏁伴噺鍚堣涓嶈兘瓒呰繃寰呮帓浜ф暟閲�');
return;
}
- processScheduling(tableData.value).then((res) => {
+ // 3. 灏� receive 瀛楁娣诲姞鍒版瘡鏉℃暟鎹腑锛屽苟绉婚櫎 loss 瀛楁
+ const submitData = tableData.value.map(row => {
+ const { loss, ...rest } = row;
+ return {
+ ...rest,
+ receive: receive.value
+ };
+ });
+ processScheduling(submitData).then((res) => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
})
@@ -159,6 +226,12 @@
// 鍏抽棴寮规
const closeDia = () => {
dialogFormVisible.value = false;
+ receive.value = '';
+ tableData.value = [];
+ unitFromRow.value = '';
+ idFromRow.value = '';
+ specificationModelFromRow.value = '';
+ pendingNum.value = 0;
emit('close')
};
defineExpose({
@@ -166,7 +239,7 @@
});
const addRow = () => {
- tableData.value.push({ id: idFromRow.value, process: '', unit: unitFromRow.value, schedulingNum: '', workHours: '', schedulingDate: '', schedulingUserId: '' });
+ tableData.value.push(createRow());
};
const removeRow = (index) => {
tableData.value.splice(index, 1);
diff --git a/src/views/productionManagement/operationScheduling/index.vue b/src/views/productionManagement/operationScheduling/index.vue
index 082b782..a9c06fb 100644
--- a/src/views/productionManagement/operationScheduling/index.vue
+++ b/src/views/productionManagement/operationScheduling/index.vue
@@ -7,11 +7,16 @@
style="width: 200px;"
@change="handleQuery" />
</el-form-item>
- <el-form-item label="椤圭洰鍚嶇О:">
- <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ <el-form-item label="鍚堝悓鍙�:">
+ <el-input v-model="searchForm.salesContractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
style="width: 200px;"
@change="handleQuery" />
</el-form-item>
+<!-- <el-form-item label="椤圭洰鍚嶇О:">-->
+<!-- <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"-->
+<!-- style="width: 200px;"-->
+<!-- @change="handleQuery" />-->
+<!-- </el-form-item>-->
<el-form-item label="娲惧伐鏃ユ湡:">
<el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
@@ -60,10 +65,12 @@
const data = reactive({
searchForm: {
staffName: "",
+ customerName: "",
+ salesContractNo: "",
status: 1,
- entryDate: null, // 褰曞叆鏃ユ湡
- entryDateStart: undefined,
- entryDateEnd: undefined,
+ entryDate: [dayjs().format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")], // 褰曞叆鏃ユ湡锛岄粯璁ゅ綋澶�
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().format("YYYY-MM-DD"),
},
});
const { searchForm } = toRefs(data);
@@ -105,21 +112,21 @@
prop: "salesContractNo",
width: 200,
},
- {
- label: "瀹㈡埛鍚堝悓鍙�",
- prop: "customerContractNo",
- width: 200,
- },
+ // {
+ // label: "瀹㈡埛鍚堝悓鍙�",
+ // prop: "customerContractNo",
+ // width: 200,
+ // },
{
label: "瀹㈡埛鍚嶇О",
prop: "customerName",
width: 200,
},
- {
- label: "椤圭洰鍚嶇О",
- prop: "projectName",
- width:300
- },
+ // {
+ // label: "椤圭洰鍚嶇О",
+ // prop: "projectName",
+ // width:300
+ // },
{
label: "浜у搧澶х被",
prop: "productCategory",
@@ -131,6 +138,16 @@
width: 150,
},
{
+ label: "缁戝畾鏈哄櫒",
+ prop: "speculativeTradingName",
+ width: 220,
+ },
+ // {
+ // label: "浜х嚎",
+ // prop: "productionLine",
+ // width: 220,
+ // },
+ {
label: "鍗曚綅",
prop: "unit",
},
diff --git a/src/views/productionManagement/processRoute/Edit.vue b/src/views/productionManagement/processRoute/Edit.vue
new file mode 100644
index 0000000..2ede998
--- /dev/null
+++ b/src/views/productionManagement/processRoute/Edit.vue
@@ -0,0 +1,186 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="缂栬緫宸ヨ壓璺嚎"
+ width="400"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="浜у搧澶х被锛�"
+ prop="productId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨浜у搧澶х被',
+ }
+ ]"
+ >
+ <el-tree-select
+ v-model="formState.productId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ check-strictly
+ @change="getModels"
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </el-form-item>
+
+ <el-form-item
+ label="瑙勬牸鍨嬪彿锛�"
+ prop="productModelId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨瑙勬牸鍨嬪彿',
+ }
+ ]"
+ >
+ <el-select
+ v-model="formState.productModelId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option
+ v-for="item in productModelsOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="澶囨敞" prop="description">
+ <el-input v-model="formState.description" type="textarea" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance, onMounted} from "vue";
+import {update} from "@/api/productionManagement/processRoute.js";
+import {modelList, productTreeList} from "@/api/basicData/product.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ record: {
+ type: Object,
+ required: true,
+ }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+let { proxy } = getCurrentInstance()
+const productModelsOptions = ref([])
+const productOptions = ref([])
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const setFormData = () => {
+ formState.value = props.record
+}
+
+const getProductOptions = () => {
+ productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res);
+ });
+};
+const getModels = (value) => {
+ formState.value.productModelId = undefined;
+ productModelsOptions.value = [];
+ if (value) {
+ modelList({ id: value }).then((res) => {
+ productModelsOptions.value = res;
+ });
+ }
+};
+
+const findNodeById = (nodes, productId) => {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === productId) {
+ return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣鐨刲abel
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝鐩存帴杩斿洖锛堝凡缁忔槸label瀛楃涓诧級
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+};
+
+function convertIdToValue(data) {
+ return data.map((item) => {
+ const { id, children, ...rest } = item;
+ const newItem = {
+ ...rest,
+ value: id, // 灏� id 鏀逛负 value
+ };
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children);
+ }
+
+ return newItem;
+ });
+}
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ update(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+
+
+onMounted(() => {
+ getProductOptions()
+ getModels(props.record.productId)
+ nextTick(() => {
+ setFormData()
+ });
+})
+</script>
diff --git a/src/views/productionManagement/processRoute/ItemsForm.vue b/src/views/productionManagement/processRoute/ItemsForm.vue
new file mode 100644
index 0000000..ed6e499
--- /dev/null
+++ b/src/views/productionManagement/processRoute/ItemsForm.vue
@@ -0,0 +1,531 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="宸ヨ壓璺嚎椤圭洰"
+ width="800px"
+ @close="closeModal"
+ >
+ <div class="operate-button">
+ <el-button
+ type="primary"
+ @click="isShowProductSelectDialog = true"
+ class="mb5"
+ style="margin-bottom: 10px;"
+ >
+ 閫夋嫨浜у搧
+ </el-button>
+
+ <el-switch
+ v-model="isTable"
+ inline-prompt
+ active-text="琛ㄦ牸"
+ inactive-text="鍒楄〃"
+ @change="handleViewChange"
+ />
+ </div>
+
+ <el-table
+ v-if="isTable"
+ ref="multipleTable"
+ v-loading="tableLoading"
+ border
+ :data="routeItems"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ row-key="id"
+ tooltip-effect="dark"
+ class="lims-table"
+ style="cursor: move;"
+ >
+ <el-table-column align="center" label="搴忓彿" width="60">
+ <template #default="scope">
+ {{ scope.$index + 1 }}
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ v-for="(item, index) in tableColumn"
+ :key="index"
+ :label="item.label"
+ :width="item.width"
+ show-overflow-tooltip
+ >
+ <template #default="scope" v-if="item.dataType === 'action'">
+ <el-button
+ v-for="(op, opIndex) in item.operation"
+ :key="opIndex"
+ :type="op.type"
+ :link="op.link"
+ size="small"
+ @click.stop="op.clickFun(scope.row)"
+ >
+ {{ op.name }}
+ </el-button>
+ </template>
+
+ <template #default="scope" v-else>
+ <template v-if="item.prop === 'processId'">
+ <el-select
+ v-model="scope.row[item.prop]"
+ style="width: 100%;"
+ @mousedown.stop
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </template>
+ <template v-else>
+ {{ scope.row[item.prop] || '-' }}
+ </template>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 浣跨敤鏅�歞iv鏇夸唬el-steps -->
+ <div
+ v-else
+ ref="stepsContainer"
+ class="mb5 custom-steps"
+ style="padding: 10px 0; display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-start;"
+ >
+ <div
+ v-for="(item, index) in routeItems"
+ :key="item.id"
+ class="custom-step draggable-step"
+ :data-id="item.id"
+ style="cursor: move; flex: 0 0 auto; min-width: 220px;"
+ >
+ <div class="step-content">
+ <div class="step-number">{{ index + 1 }}</div>
+ <el-card
+ :header="item.productName"
+ class="step-card"
+ style="cursor: move;"
+ >
+ <div class="step-card-content">
+ <p>{{ item.model }}</p>
+ <p>{{ item.unit }}</p>
+ <el-select
+ v-model="item.processId"
+ style="width: 100%;"
+ @mousedown.stop
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </div>
+ <template #footer>
+ <div class="step-card-footer">
+ <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">鍒犻櫎</el-button>
+ </div>
+ </template>
+ </el-card>
+ </div>
+ </div>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <ProductSelectDialog
+ v-model="isShowProductSelectDialog"
+ @confirm="handelSelectProducts"
+ />
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
+import { processList } from "@/api/productionManagement/productionProcess.js";
+import Sortable from 'sortablejs';
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ default: false
+ },
+ record: {
+ type: Object,
+ required: true,
+ default: () => ({})
+ }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+const processOptions = ref([]);
+const tableLoading = ref(false);
+const isShowProductSelectDialog = ref(false);
+const routeItems = ref([]);
+let tableSortable = null;
+let stepsSortable = null;
+const multipleTable = ref(null);
+const stepsContainer = ref(null);
+const isTable = ref(true);
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ }
+});
+
+const tableColumn = ref([
+ { label: "浜у搧鍚嶇О", prop: "productName", width: 180 },
+ { label: "瑙勬牸鍚嶇О", prop: "model", width: 150 },
+ { label: "鍗曚綅", prop: "unit", width: 80 },
+ { label: "宸ュ簭鍚嶇О", prop: "processId", width: 180 },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 100,
+ operation: [
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ clickFun: (row) => {
+ const idx = routeItems.value.findIndex(item => item.id === row.id);
+ if (idx > -1) {
+ removeItem(idx)
+ }
+ }
+ }
+ ]
+ }
+]);
+
+const removeItem = (index) => {
+ routeItems.value.splice(index, 1);
+ nextTick(() => initSortable());
+};
+
+const removeItemByID = (id) => {
+ const idx = routeItems.value.findIndex(item => item.id === id);
+ if (idx > -1) {
+ routeItems.value.splice(idx, 1);
+ nextTick(() => initSortable());
+ }
+};
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const handelSelectProducts = (products) => {
+ destroySortable();
+
+ const newData = products.map(({ id, ...product }) => ({
+ ...product,
+ productModelId: id,
+ routeId: props.record.id,
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
+ processId: undefined
+ }));
+
+ console.log('閫夋嫨浜у搧鍓嶆暟缁�:', routeItems.value);
+ routeItems.value.push(...newData);
+ routeItems.value = [...routeItems.value];
+ console.log('閫夋嫨浜у搧鍚庢暟缁�:', routeItems.value);
+
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ // 寮哄埗閲嶆柊娓叉煋缁勪欢
+ if (proxy?.$forceUpdate) {
+ proxy.$forceUpdate();
+ }
+
+ const temp = [...routeItems.value];
+ routeItems.value = [];
+ nextTick(() => {
+ routeItems.value = temp;
+ initSortable();
+ });
+ });
+};
+
+const findProcessRouteItems = () => {
+ tableLoading.value = true;
+ findProcessRouteItemList({ routeId: props.record.id })
+ .then(res => {
+ tableLoading.value = false;
+ routeItems.value = res.data.map(item => ({
+ ...item,
+ processId: item.processId === 0 ? undefined : item.processId
+ }));
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ });
+};
+
+const findProcessList = () => {
+ processList({})
+ .then(res => {
+ processOptions.value = res.data;
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+ });
+};
+
+const { proxy } = getCurrentInstance() || {};
+
+const handleSubmit = () => {
+ const hasEmptyProcess = routeItems.value.some(item => !item.processId);
+ if (hasEmptyProcess) {
+ proxy?.$modal?.msgError("璇蜂负鎵�鏈夐」鐩�夋嫨宸ュ簭");
+ return;
+ }
+
+ addOrUpdateProcessRouteItem({
+ routeId: props.record.id,
+ processRouteItem: routeItems.value.map(({ id, ...item }) => item)
+ })
+ .then(res => {
+ isShow.value = false;
+ emit('completed');
+ proxy?.$modal?.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ .catch(err => {
+ proxy?.$modal?.msgError(`鎻愪氦澶辫触锛�${err.msg || "缃戠粶寮傚父"}`);
+ });
+};
+
+const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (stepsSortable) {
+ stepsSortable.destroy();
+ stepsSortable = null;
+ }
+};
+
+const initSortable = () => {
+ destroySortable();
+
+ if (isTable.value) {
+ if (!multipleTable.value) return;
+ const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
+ multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
+ if (!tbody) return;
+
+ tableSortable = new Sortable(tbody, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ handle: '.el-table__row',
+ filter: '.el-button, .el-select',
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭锛屼笌琛ㄦ牸妯″紡淇濇寔涓�鑷�
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ routeItems.value = [...routeItems.value];
+ console.log('鎺掑簭鍚庢暟缁�:', routeItems.value);
+ }
+ });
+ } else {
+ if (!stepsContainer.value) return;
+
+ // 淇敼锛氱洿鎺ヤ娇鐢╯tepsContainer.value浣滀负鎷栨嫿瀹瑰櫒
+ const stepsList = stepsContainer.value;
+ if (!stepsList) {
+ console.warn('鏈壘鍒版楠ゆ潯鎷栨嫿瀹瑰櫒');
+ return;
+ }
+
+ // 淇敼锛氱畝鍖栨嫋鎷介厤缃�
+ stepsSortable = new Sortable(stepsList, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ draggable: '.draggable-step', // 鍙嫋鎷藉厓绱�
+ handle: '.draggable-step, .step-card', // 鎷栨嫿鎵嬫焺
+ filter: '.el-button, .el-select, .el-input', // 杩囨护鎸夐挳/閫夋嫨鍣�
+ forceFallback: true,
+ fallbackClass: 'sortable-fallback',
+ preventOnFilter: true,
+ scroll: true,
+ scrollSensitivity: 30,
+ scrollSpeed: 10,
+ bubbleScroll: true,
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ routeItems.value = [...routeItems.value];
+ }
+ });
+
+ // 璋冭瘯锛氭墦鍗板鍣ㄥ拰瀹炰緥锛岀‘璁ょ粦瀹氭垚鍔�
+ console.log('姝ラ鏉℃嫋鎷藉鍣�:', stepsList);
+ console.log('Sortable瀹炰緥:', stepsSortable);
+ }
+};
+
+const handleViewChange = () => {
+ destroySortable();
+ // 寤惰繜鍒濆鍖栵紝纭繚瑙嗗浘鍒囨崲鍚嶥OM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+};
+
+onMounted(() => {
+ findProcessRouteItems();
+ findProcessList();
+});
+
+onUnmounted(() => {
+ destroySortable();
+});
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow
+});
+</script>
+
+<style scoped>
+:deep(.sortable-ghost) {
+ opacity: 0.6;
+ background-color: #f5f7fa !important;
+}
+
+:deep(.el-table__row) {
+ transition: background-color 0.2s;
+}
+
+:deep(.el-table__row:hover) {
+ background-color: #f9fafc !important;
+}
+
+:deep(.el-card__footer){
+ padding: 0 !important;
+}
+
+.operate-button {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+/* 淇敼锛氳嚜瀹氫箟姝ラ鏉″鍣ㄦ牱寮� */
+.custom-steps {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ gap: 20px;
+ min-height: 100px;
+}
+
+/* 淇敼锛氳嚜瀹氫箟姝ラ椤规牱寮� */
+.custom-step {
+ cursor: move !important;
+ padding: 8px;
+ position: relative;
+ transition: all 0.2s ease;
+ flex: 0 0 auto;
+ min-width: 220px;
+ touch-action: none;
+}
+
+/* 鎷栨嫿鎮诞鏍峰紡锛屾彁绀哄彲鎷栨嫿 */
+.custom-step:hover {
+ background-color: rgba(64, 158, 255, 0.05);
+ transform: translateY(-2px);
+}
+
+.sortable-ghost {
+ opacity: 0.4;
+ background-color: #f5f7fa !important;
+ border: 2px dashed #409eff;
+ margin: 10px;
+ transform: scale(1.02);
+}
+
+.sortable-fallback {
+ opacity: 0.9;
+ background-color: #f5f7fa;
+ border: 1px solid #409eff;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ transform: rotate(2deg);
+ margin: 10px;
+}
+
+.step-card {
+ cursor: move !important;
+ transition: box-shadow 0.2s ease;
+ user-select: none;
+ -webkit-user-select: none;
+ pointer-events: auto;
+ margin: 10px;
+ height: 240px;
+}
+
+.step-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.step-content {
+ width: 220px;
+ user-select: none;
+}
+
+.step-card-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.step-card-footer {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 10px;
+}
+
+/* 鑷畾涔夊簭鍙锋牱寮忎紭鍖� */
+.step-number {
+ font-weight: bold;
+ text-align: center;
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ margin: 0 auto 10px;
+ background: #409eff;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 14px;
+}
+</style>
diff --git a/src/views/productionManagement/processRoute/New.vue b/src/views/productionManagement/processRoute/New.vue
new file mode 100644
index 0000000..856a2a4
--- /dev/null
+++ b/src/views/productionManagement/processRoute/New.vue
@@ -0,0 +1,181 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="鏂板宸ヨ壓璺嚎"
+ width="400"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="浜у搧澶х被锛�"
+ prop="productId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨浜у搧澶х被',
+ }
+ ]"
+ >
+ <el-tree-select
+ v-model="formState.productId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ check-strictly
+ @change="getModels"
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </el-form-item>
+
+ <el-form-item
+ label="瑙勬牸鍨嬪彿锛�"
+ prop="productModelId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨瑙勬牸鍨嬪彿',
+ }
+ ]"
+ >
+ <el-select
+ v-model="formState.productModelId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option
+ v-for="item in productModelsOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="澶囨敞" prop="description">
+ <el-input v-model="formState.description" type="textarea" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed, getCurrentInstance, onMounted} from "vue";
+import {add} from "@/api/productionManagement/processRoute.js";
+import {modelList, productTreeList} from "@/api/basicData/product.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ productId: undefined,
+ productModelId: undefined,
+ description: '',
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const productModelsOptions = ref([])
+const productOptions = ref([])
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const getProductOptions = () => {
+ productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res);
+ });
+};
+const getModels = (value) => {
+ formState.value.productId = undefined;
+ formState.value.productModelId = undefined;
+ productModelsOptions.value = [];
+
+ if (value) {
+ formState.value.productId = findNodeById(productOptions.value, value) || undefined;
+ modelList({ id: value }).then((res) => {
+ productModelsOptions.value = res;
+ });
+ }
+};
+
+const findNodeById = (nodes, productId) => {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === productId) {
+ return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥炶鑺傜偣鐨刲abel
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundNode = findNodeById(nodes[i].children, productId);
+ if (foundNode) {
+ return foundNode; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝鐩存帴杩斿洖锛堝凡缁忔槸label瀛楃涓诧級
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+};
+
+function convertIdToValue(data) {
+ return data.map((item) => {
+ const { id, children, ...rest } = item;
+ const newItem = {
+ ...rest,
+ value: id, // 灏� id 鏀逛负 value
+ };
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children);
+ }
+
+ return newItem;
+ });
+}
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ add(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+
+onMounted(() => {
+ getProductOptions()
+})
+</script>
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
new file mode 100644
index 0000000..7d5ab5d
--- /dev/null
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -0,0 +1,194 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="瑙勬牸鍚嶇О:">
+ <el-input v-model="searchForm.model" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right" class="mb10">
+ <el-button type="primary" @click="showNewModal">鏂板宸ヨ壓璺嚎</el-button>
+ <el-button type="danger" @click="handleDelete" :disabled="selectedRows.length === 0" plain>鍒犻櫎宸ヨ壓璺嚎</el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ />
+ </div>
+ <new-process
+ v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ @completed="getList"
+ />
+
+ <edit-process
+ v-if="isShowEditModal"
+ v-model:visible="isShowEditModal"
+ :record="record"
+ @completed="getList"
+ />
+
+ <route-item-form
+ v-if="isShowItemModal"
+ v-model:visible="isShowItemModal"
+ :record="record"
+ @completed="getList"
+ />
+ </div>
+</template>
+
+<script setup>
+import {onMounted, ref} from "vue";
+import NewProcess from "@/views/productionManagement/processRoute/New.vue";
+import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
+import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
+import {listPage, del} from "@/api/productionManagement/processRoute.js";
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const data = reactive({
+ searchForm: {
+ model: "",
+ },
+});
+const { searchForm } = toRefs(data);
+const tableColumn = ref([
+ {
+ label: "宸ヨ壓璺嚎缂栧彿",
+ prop: "processRouteCode",
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ },
+ {
+ label: "瑙勬牸鍚嶇О",
+ prop: "model",
+ },
+ {
+ label: "鎻忚堪",
+ prop: "description",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 280,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ showEditModal(row);
+ }
+ },
+ {
+ name: "璺嚎椤圭洰",
+ type: "text",
+ clickFun: (row) => {
+ showItemModal(row);
+ }
+ }
+ ]
+ }
+]);
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+const isShowNewModal = ref(false);
+const isShowEditModal = ref(false);
+const isShowItemModal = ref(false);
+const record = ref({});
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+});
+const { proxy } = getCurrentInstance()
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
+ listPage(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ }));
+ page.total = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鎵撳紑鏂板寮规
+const showNewModal = () => {
+ isShowNewModal.value = true
+};
+
+const showEditModal = (row) => {
+ isShowEditModal.value = true
+ record.value = row
+};
+
+const showItemModal = (row) => {
+ router.push({
+ path: '/productionManagement/processRouteItem',
+ query: {
+ id: row.id
+ }
+ })
+};
+
+// 鍒犻櫎
+function handleDelete() {
+ const ids = selectedRows.value.map((item) => item.id);
+ proxy.$modal
+ .confirm('鏄惁纭鍒犻櫎宸插嬀閫夌殑鏁版嵁椤癸紵')
+ .then(function () {
+ return del(ids);
+ })
+ .then(() => {
+ getList();
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+}
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped></style>
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
new file mode 100644
index 0000000..641ffff
--- /dev/null
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -0,0 +1,503 @@
+<template>
+ <div class="app-container">
+ <div class="operate-button">
+ <div style="margin-bottom: 15px;">
+ <el-button
+ type="primary"
+ @click="isShowProductSelectDialog = true"
+ >
+ 閫夋嫨浜у搧
+ </el-button>
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ </div>
+
+ <el-switch
+ v-model="isTable"
+ inline-prompt
+ active-text="琛ㄦ牸"
+ inactive-text="鍒楄〃"
+ @change="handleViewChange"
+ />
+ </div>
+ <el-table
+ v-if="isTable"
+ ref="multipleTable"
+ v-loading="tableLoading"
+ border
+ :data="routeItems"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ row-key="id"
+ tooltip-effect="dark"
+ class="lims-table"
+ style="cursor: move;"
+ >
+ <el-table-column align="center" label="搴忓彿" width="60">
+ <template #default="scope">
+ {{ scope.$index + 1 }}
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ v-for="(item, index) in tableColumn"
+ :key="index"
+ :label="item.label"
+ :width="item.width"
+ show-overflow-tooltip
+ >
+ <template #default="scope" v-if="item.dataType === 'action'">
+ <el-button
+ v-for="(op, opIndex) in item.operation"
+ :key="opIndex"
+ :type="op.type"
+ :link="op.link"
+ size="small"
+ @click.stop="op.clickFun(scope.row)"
+ >
+ {{ op.name }}
+ </el-button>
+ </template>
+
+ <template #default="scope" v-else>
+ <template v-if="item.prop === 'processId'">
+ <el-select
+ v-model="scope.row[item.prop]"
+ style="width: 100%;"
+ @mousedown.stop
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </template>
+ <template v-else>
+ {{ scope.row[item.prop] || '-' }}
+ </template>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 浣跨敤鏅�歞iv鏇夸唬el-steps -->
+ <div
+ v-else
+ ref="stepsContainer"
+ class="mb5 custom-steps"
+ >
+ <div
+ v-for="(item, index) in routeItems"
+ :key="item.id"
+ class="custom-step draggable-step"
+ :data-id="item.id"
+ style="cursor: move; flex: 0 0 auto; min-width: 220px;"
+ >
+ <div class="step-content">
+ <div class="step-number">{{ index + 1 }}</div>
+ <el-card
+ :header="item.productName"
+ class="step-card"
+ style="cursor: move;"
+ >
+ <div class="step-card-content">
+ <p>{{ item.model }}</p>
+ <p>{{ item.unit }}</p>
+ <el-select
+ v-model="item.processId"
+ style="width: 100%;"
+ @mousedown.stop
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </div>
+ <template #footer>
+ <div class="step-card-footer">
+ <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">鍒犻櫎</el-button>
+ </div>
+ </template>
+ </el-card>
+ </div>
+ </div>
+ </div>
+
+ <ProductSelectDialog
+ v-model="isShowProductSelectDialog"
+ @confirm="handelSelectProducts"
+ />
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
+import { processList } from "@/api/productionManagement/productionProcess.js";
+import Sortable from 'sortablejs';
+import { useRoute, useRouter } from 'vue-router'
+
+const processOptions = ref([]);
+const tableLoading = ref(false);
+const isShowProductSelectDialog = ref(false);
+const routeItems = ref([]);
+let tableSortable = null;
+let stepsSortable = null;
+const multipleTable = ref(null);
+const stepsContainer = ref(null);
+const isTable = ref(true);
+
+const route = useRoute()
+const router = useRouter()
+const routeId = computed({
+ get() {
+ return route.query.id;
+ },
+
+ set(val) {
+ emit('update:router', val)
+ }
+});
+
+
+const tableColumn = ref([
+ { label: "浜у搧鍚嶇О", prop: "productName"},
+ { label: "瑙勬牸鍚嶇О", prop: "model" },
+ { label: "鍗曚綅", prop: "unit" },
+ { label: "宸ュ簭鍚嶇О", prop: "processId", width: 200 },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 100,
+ operation: [
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ clickFun: (row) => {
+ const idx = routeItems.value.findIndex(item => item.id === row.id);
+ if (idx > -1) {
+ removeItem(idx)
+ }
+ }
+ }
+ ]
+ }
+]);
+
+const removeItem = (index) => {
+ routeItems.value.splice(index, 1);
+ nextTick(() => initSortable());
+};
+
+const removeItemByID = (id) => {
+ const idx = routeItems.value.findIndex(item => item.id === id);
+ if (idx > -1) {
+ routeItems.value.splice(idx, 1);
+ nextTick(() => initSortable());
+ }
+};
+
+const handelSelectProducts = (products) => {
+ destroySortable();
+
+ const newData = products.map(({ id, ...product }) => ({
+ ...product,
+ productModelId: id,
+ routeId: routeId.value,
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
+ processId: undefined
+ }));
+
+ console.log('閫夋嫨浜у搧鍓嶆暟缁�:', routeItems.value);
+ routeItems.value.push(...newData);
+ routeItems.value = [...routeItems.value];
+ console.log('閫夋嫨浜у搧鍚庢暟缁�:', routeItems.value);
+
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ // 寮哄埗閲嶆柊娓叉煋缁勪欢
+ if (proxy?.$forceUpdate) {
+ proxy.$forceUpdate();
+ }
+
+ const temp = [...routeItems.value];
+ routeItems.value = [];
+ nextTick(() => {
+ routeItems.value = temp;
+ initSortable();
+ });
+ });
+};
+
+const findProcessRouteItems = () => {
+ tableLoading.value = true;
+ findProcessRouteItemList({ routeId: routeId.value })
+ .then(res => {
+ tableLoading.value = false;
+ routeItems.value = res.data.map(item => ({
+ ...item,
+ processId: item.processId === 0 ? undefined : item.processId
+ }));
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ });
+};
+
+const findProcessList = () => {
+ processList({})
+ .then(res => {
+ processOptions.value = res.data;
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+ });
+};
+
+const { proxy } = getCurrentInstance() || {};
+
+const handleSubmit = () => {
+ const hasEmptyProcess = routeItems.value.some(item => !item.processId);
+ if (hasEmptyProcess) {
+ proxy?.$modal?.msgError("璇蜂负鎵�鏈夐」鐩�夋嫨宸ュ簭");
+ return;
+ }
+
+ addOrUpdateProcessRouteItem({
+ routeId: routeId.value,
+ processRouteItem: routeItems.value.map(({ id, ...item }) => item)
+ })
+ .then(res => {
+ router.push({
+ path: '/productionManagement/processRoute',
+ })
+ proxy?.$modal?.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ .catch(err => {
+ proxy?.$modal?.msgError(`鎻愪氦澶辫触锛�${err.msg || "缃戠粶寮傚父"}`);
+ });
+};
+
+const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (stepsSortable) {
+ stepsSortable.destroy();
+ stepsSortable = null;
+ }
+};
+
+const initSortable = () => {
+ destroySortable();
+
+ if (isTable.value) {
+ if (!multipleTable.value) return;
+ const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
+ multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
+ if (!tbody) return;
+
+ tableSortable = new Sortable(tbody, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ handle: '.el-table__row',
+ filter: '.el-button, .el-select',
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭锛屼笌琛ㄦ牸妯″紡淇濇寔涓�鑷�
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ routeItems.value = [...routeItems.value];
+ console.log('鎺掑簭鍚庢暟缁�:', routeItems.value);
+ }
+ });
+ } else {
+ if (!stepsContainer.value) return;
+
+ // 淇敼锛氱洿鎺ヤ娇鐢╯tepsContainer.value浣滀负鎷栨嫿瀹瑰櫒
+ const stepsList = stepsContainer.value;
+ if (!stepsList) {
+ console.warn('鏈壘鍒版楠ゆ潯鎷栨嫿瀹瑰櫒');
+ return;
+ }
+
+ // 淇敼锛氱畝鍖栨嫋鎷介厤缃�
+ stepsSortable = new Sortable(stepsList, {
+ animation: 150,
+ ghostClass: 'sortable-ghost',
+ draggable: '.draggable-step', // 鍙嫋鎷藉厓绱�
+ handle: '.draggable-step, .step-card', // 鎷栨嫿鎵嬫焺
+ filter: '.el-button, .el-select, .el-input', // 杩囨护鎸夐挳/閫夋嫨鍣�
+ forceFallback: true,
+ fallbackClass: 'sortable-fallback',
+ preventOnFilter: true,
+ scroll: true,
+ scrollSensitivity: 30,
+ scrollSpeed: 10,
+ bubbleScroll: true,
+ onEnd: (evt) => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ routeItems.value = [...routeItems.value];
+ }
+ });
+
+ // 璋冭瘯锛氭墦鍗板鍣ㄥ拰瀹炰緥锛岀‘璁ょ粦瀹氭垚鍔�
+ console.log('姝ラ鏉℃嫋鎷藉鍣�:', stepsList);
+ console.log('Sortable瀹炰緥:', stepsSortable);
+ }
+};
+
+const handleViewChange = () => {
+ destroySortable();
+ // 寤惰繜鍒濆鍖栵紝纭繚瑙嗗浘鍒囨崲鍚嶥OM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+};
+
+onMounted(() => {
+ findProcessRouteItems();
+ findProcessList();
+});
+
+onUnmounted(() => {
+ destroySortable();
+});
+
+defineExpose({
+ handleSubmit,
+});
+</script>
+
+<style scoped>
+:deep(.sortable-ghost) {
+ opacity: 0.6;
+ background-color: #f5f7fa !important;
+}
+
+:deep(.el-table__row) {
+ transition: background-color 0.2s;
+}
+
+:deep(.el-table__row:hover) {
+ background-color: #f9fafc !important;
+}
+
+:deep(.el-card__footer){
+ padding: 0 !important;
+}
+
+.operate-button {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+/* 淇敼锛氳嚜瀹氫箟姝ラ鏉″鍣ㄦ牱寮� */
+.custom-steps {
+ min-height: 100px;
+ padding: 10px 0;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+ align-items: flex-start;
+}
+
+/* 淇敼锛氳嚜瀹氫箟姝ラ椤规牱寮� */
+.custom-step {
+ cursor: move !important;
+ padding: 8px;
+ position: relative;
+ transition: all 0.2s ease;
+ flex: 0 0 auto;
+ min-width: 220px;
+ touch-action: none;
+}
+
+/* 鎷栨嫿鎮诞鏍峰紡锛屾彁绀哄彲鎷栨嫿 */
+.custom-step:hover {
+ background-color: rgba(64, 158, 255, 0.05);
+ transform: translateY(-2px);
+}
+
+.sortable-ghost {
+ opacity: 0.4;
+ background-color: #f5f7fa !important;
+ border: 2px dashed #409eff;
+ margin: 10px;
+ transform: scale(1.02);
+}
+
+.sortable-fallback {
+ opacity: 0.9;
+ background-color: #f5f7fa;
+ border: 1px solid #409eff;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ transform: rotate(2deg);
+ margin: 10px;
+}
+
+.step-card {
+ cursor: move !important;
+ transition: box-shadow 0.2s ease;
+ user-select: none;
+ -webkit-user-select: none;
+ pointer-events: auto;
+ margin: 10px;
+ height: 260px;
+}
+
+.step-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.step-content {
+ width: 245px;
+ user-select: none;
+}
+
+.step-card-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ height: 140px;
+}
+
+.step-card-footer {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 10px;
+}
+
+/* 鑷畾涔夊簭鍙锋牱寮忎紭鍖� */
+.step-number {
+ font-weight: bold;
+ text-align: center;
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ margin: 0 auto 10px;
+ background: #409eff;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 14px;
+}
+</style>
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
new file mode 100644
index 0000000..a131830
--- /dev/null
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -0,0 +1,293 @@
+<template>
+ <div class="app-container">
+ <el-button v-if="dataValue.isEdit"
+ type="primary"
+ @click="addItem"
+ style="margin-bottom: 10px">娣诲姞
+ </el-button>
+ <el-button v-if="!dataValue.isEdit"
+ type="primary"
+ @click="dataValue.isEdit = true"
+ style="margin-bottom: 10px">缂栬緫
+ </el-button>
+ <el-button v-if="dataValue.isEdit"
+ type="primary"
+ @click="cancelEdit"
+ style="margin-bottom: 10px">鍙栨秷
+ </el-button>
+ <el-button type="primary"
+ :loading="dataValue.loading"
+ @click="submit"
+ :disabled="!dataValue.isEdit"
+ style="margin-bottom: 10px">纭
+ </el-button>
+ <el-table
+ :data="tableData"
+ border
+ :preserve-expanded-content="false"
+ style="width: 100%"
+ >
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-form ref="form"
+ :model="dataValue">
+ <el-table :data="dataValue.dataList"
+ style="width: 100%">
+ <el-table-column prop="productName"
+ label="浜у搧"/>
+ <el-table-column prop="model"
+ label="瑙勬牸">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :prop="`dataList.${$index}.model`"
+ :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-select v-model="row.model"
+ placeholder="璇烽�夋嫨瑙勬牸"
+ clearable
+ :disabled="!dataValue.isEdit"
+ style="width: 100%"
+ @visible-change="(v) => { if (v) openDialog($index) }">
+ <el-option v-if="row.model"
+ :label="row.model"
+ :value="row.model" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="processId"
+ label="娑堣�楀伐搴�">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.processId`"
+ :rules="[{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+ style="margin: 0">
+ <el-select v-model="row.processId"
+ placeholder="璇烽�夋嫨"
+ filterable
+ clearable
+ style="width: 100%"
+ :disabled="!dataValue.isEdit">
+ <el-option v-for="item in dataValue.processOptions"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitQuantity"
+ label="鍗曚綅浜у嚭鎵�闇�鏁伴噺">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.unitQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.unitQuantity"
+ :min="0"
+ :precision="2"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="demandedQuantity"
+ label="闇�姹傛�婚噺">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.demandedQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.demandedQuantity"
+ :min="0"
+ :precision="2"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unit"
+ label="鍗曚綅">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.unit`"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input v-model="row.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="diskQuantity"
+ label="鐩樻暟锛堢洏锛�">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.diskQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ョ洏鏁�', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.diskQuantity"
+ :min="0"
+ :precision="0"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" fixed="right" width="100">
+ <template #default="{ row, $index }">
+ <el-button type="danger"
+ text
+ @click="dataValue.dataList.splice($index, 1)">鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form>
+ </template>
+ </el-table-column>
+ <el-table-column label="浜у搧缂栫爜" prop="productCode" />
+ <el-table-column label="浜у搧鍚嶇О" prop="productName" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ </el-table>
+
+ <product-select-dialog v-if="dataValue.showProductDialog"
+ v-model:model-value="dataValue.showProductDialog"
+ @confirm="handleProduct" />
+ </div>
+</template>
+
+<script setup lang="ts">
+import {
+ computed,
+ defineAsyncComponent,
+ defineComponent,
+ onMounted,
+ reactive,
+ ref,
+} from "vue";
+import { queryList, add } from "@/api/productionManagement/productStructure.js";
+import { list } from "@/api/productionManagement/productionProcess";
+import { ElMessage } from "element-plus";
+import {useRoute, useRouter} from "vue-router";
+
+defineComponent({
+ name: "StructureEdit",
+});
+
+const ProductSelectDialog = defineAsyncComponent(
+ () => import("@/views/basicData/product/ProductSelectDialog.vue")
+);
+const form = ref();
+
+const route = useRoute()
+const router = useRouter()
+const routeId = computed({
+ get() {
+ return route.query.id;
+ },
+
+ set(val) {
+ emit('update:router', val)
+ }
+});
+
+
+const dataValue = reactive({
+ dataList: [],
+ productOptions: [],
+ processOptions: [],
+ showProductDialog: false,
+ currentRowIndex: null,
+ loading: false,
+ isEdit: false,
+});
+
+const tableData = reactive([
+ {
+ productName: "",
+ model: "",
+ unit: "",
+ productCode: "",
+ }
+])
+
+const openDialog = index => {
+ dataValue.currentRowIndex = index;
+ dataValue.showProductDialog = true;
+};
+
+const fetchData = async () => {
+ const { data } = await queryList(routeId.value);
+ tableData[0].productName = data.productName;
+ tableData[0].model = data.model;
+ tableData[0].unit = data.unit;
+ tableData[0].productCode = data.productCode;
+ dataValue.dataList = data.productStructureList;
+};
+
+const fetchProcessOptions = async () => {
+ const { data } = await list(routeId.value);
+ dataValue.processOptions = data;
+};
+
+const handleProduct = row => {
+ if (row?.length > 1) {
+ ElMessage.error("鍙兘閫夋嫨涓�涓骇鍝�");
+ }
+ dataValue.dataList[dataValue.currentRowIndex].productName =
+ row[0].productName;
+ dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
+ dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+ dataValue.showProductDialog = false;
+};
+
+const submit = () => {
+ form.value
+ .validate(valid => {
+ dataValue.loading = true;
+ if (valid) {
+ add({
+ parentId: routeId.value,
+ productStructureList: dataValue.dataList || [],
+ }).then(res => {
+ router.push({
+ path: '/productionManagement/productionManagement/productStructure/index',
+ })
+ ElMessage.success("淇濆瓨鎴愬姛");
+ dataValue.loading = false;
+ });
+ }
+ })
+ .finally(() => {
+ dataValue.loading = false;
+ });
+};
+
+const addItem = () => {
+ dataValue.dataList.push({
+ productName: "",
+ productId: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ unitQuantity: 0,
+ demandedQuantity: 0,
+ unit: "",
+ diskQuantity: 0,
+ });
+};
+
+const cancelEdit = () => {
+ dataValue.isEdit = false;
+ dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
+};
+
+onMounted(() => {
+ fetchData();
+ fetchProcessOptions();
+});
+</script>
\ No newline at end of file
diff --git a/src/views/productionManagement/productStructure/StructureEdit.vue b/src/views/productionManagement/productStructure/StructureEdit.vue
new file mode 100644
index 0000000..4d07f5d
--- /dev/null
+++ b/src/views/productionManagement/productStructure/StructureEdit.vue
@@ -0,0 +1,311 @@
+<template>
+ <el-dialog v-model="visible"
+ title="缁撴瀯"
+ width="1200"
+ close-on-click-modal
+ @close="visible = false">
+ <el-button v-if="dataValue.isEdit"
+ type="primary"
+ @click="addItem"
+ style="margin-bottom: 10px">娣诲姞
+ </el-button>
+ <el-button v-if="!dataValue.isEdit"
+ type="primary"
+ @click="dataValue.isEdit = true"
+ style="margin-bottom: 10px">缂栬緫
+ </el-button>
+ <el-button v-if="dataValue.isEdit"
+ type="primary"
+ @click="cancelEdit"
+ style="margin-bottom: 10px">鍙栨秷
+ </el-button>
+
+ <el-table
+ :data="tableData"
+ border
+ :preserve-expanded-content="false"
+ style="width: 100%"
+ >
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-form ref="form"
+ :model="dataValue">
+ <el-table :data="dataValue.dataList"
+ style="width: 100%">
+ <el-table-column prop="productName"
+ label="浜у搧"
+ width="150" />
+ <el-table-column prop="model"
+ label="瑙勬牸"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item v-if="dataValue.isEdit"
+ :prop="`dataList.${$index}.model`"
+ :rules="[{ required: true, message: '璇烽�夋嫨瑙勬牸', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-select v-model="row.model"
+ placeholder="璇烽�夋嫨浜у搧"
+ clearable
+ :disabled="!dataValue.isEdit"
+ style="width: 100%"
+ @visible-change="(v) => { if (v) openDialog($index) }">
+ <el-option v-if="row.model"
+ :label="row.model"
+ :value="row.model" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="processId"
+ label="娑堣�楀伐搴�"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.processId`"
+ :rules="[{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
+ style="margin: 0">
+ <el-select v-model="row.processId"
+ placeholder="璇烽�夋嫨"
+ filterable
+ clearable
+ style="width: 100%"
+ :disabled="!dataValue.isEdit">
+ <el-option v-for="item in dataValue.processOptions"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitQuantity"
+ label="鍗曚綅浜у嚭鎵�闇�鏁伴噺"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.unitQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.unitQuantity"
+ :min="0"
+ :precision="2"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="demandedQuantity"
+ label="闇�姹傛�婚噺"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.demandedQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ラ渶姹傛�婚噺', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.demandedQuantity"
+ :min="0"
+ :precision="2"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unit"
+ label="鍗曚綅"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.unit`"
+ :rules="[{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input v-model="row.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="diskQuantity"
+ label="鐩樻暟锛堢洏锛�"
+ width="150">
+ <template #default="{ row, $index }">
+ <el-form-item :prop="`dataList.${$index}.diskQuantity`"
+ :rules="[{ required: true, message: '璇疯緭鍏ョ洏鏁�', trigger: ['blur','change'] }]"
+ style="margin: 0">
+ <el-input-number v-model="row.diskQuantity"
+ :min="0"
+ :precision="0"
+ :step="1"
+ controls-position="right"
+ style="width: 100%"
+ :disabled="!dataValue.isEdit" />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔">
+ <template #default="{ row, $index }">
+ <el-button type="danger"
+ text
+ @click="dataValue.dataList.splice($index, 1)">鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form>
+ </template>
+ </el-table-column>
+ <el-table-column label="浜у搧缂栫爜" prop="productCode" />
+ <el-table-column label="浜у搧鍚嶇О" prop="productName" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="model" />
+ <el-table-column label="鍗曚綅" prop="unit" />
+ </el-table>
+
+ <product-select-dialog v-if="dataValue.showProductDialog"
+ v-model:model-value="dataValue.showProductDialog"
+ @confirm="handleProduct" />
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ :loading="dataValue.loading"
+ @click="submit"
+ :disabled="!dataValue.isEdit">
+ 纭
+ </el-button>
+ <el-button @click="visible = false">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup lang="ts">
+ import {
+ computed,
+ defineAsyncComponent,
+ defineComponent,
+ onMounted,
+ reactive,
+ ref,
+ } from "vue";
+ import { queryList, add } from "@/api/productionManagement/productStructure.js";
+ import { list } from "@/api/productionManagement/productionProcess";
+ import { ElMessage } from "element-plus";
+
+ defineComponent({
+ name: "StructureEdit",
+ });
+
+ const ProductSelectDialog = defineAsyncComponent(
+ () => import("@/views/basicData/product/ProductSelectDialog.vue")
+ );
+ const form = ref();
+
+ const props = defineProps({
+ showModel: {
+ type: Boolean,
+ default: false,
+ },
+ record: {
+ type: Object,
+ required: true,
+ },
+ });
+
+ const emits = defineEmits(["update:showModel"]);
+ const visible = computed({
+ get() {
+ return props.showModel;
+ },
+ set(val) {
+ emits("update:showModel", val);
+ },
+ });
+
+ const dataValue = reactive({
+ dataList: [],
+ productOptions: [],
+ processOptions: [],
+ showProductDialog: false,
+ currentRowIndex: null,
+ loading: false,
+ isEdit: false,
+ });
+
+ const tableData = [
+ {
+ productName: props.record.productName,
+ model: props.record.model,
+ unit: props.record.unit,
+ productCode: props.record.productCode,
+ }
+ ]
+
+ const openDialog = index => {
+ dataValue.currentRowIndex = index;
+ dataValue.showProductDialog = true;
+ };
+
+ const fetchData = async () => {
+ const { data } = await queryList(props.record.id);
+ dataValue.dataList = data;
+ };
+
+ const fetchProcessOptions = async () => {
+ const { data } = await list(props.record.id);
+ dataValue.processOptions = data;
+ };
+
+ const handleProduct = row => {
+ if (row?.length > 1) {
+ ElMessage.error("鍙兘閫夋嫨涓�涓骇鍝�");
+ }
+ dataValue.dataList[dataValue.currentRowIndex].productName =
+ row[0].productName;
+ dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
+ dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+ dataValue.showProductDialog = false;
+ };
+
+ const submit = () => {
+ form.value
+ .validate(valid => {
+ dataValue.loading = true;
+ if (valid) {
+ add({
+ parentId: props.record.id,
+ productStructureList: dataValue.dataList || [],
+ }).then(res => {
+ ElMessage.success("淇濆瓨鎴愬姛");
+ visible.value = false;
+ dataValue.loading = false;
+ });
+ }
+ })
+ .finally(() => {
+ dataValue.loading = false;
+ });
+ };
+
+ const addItem = () => {
+ dataValue.dataList.push({
+ productName: "",
+ productId: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ unitQuantity: 0,
+ demandedQuantity: 0,
+ unit: "",
+ diskQuantity: 0,
+ });
+ };
+
+ const cancelEdit = () => {
+ dataValue.isEdit = false;
+ dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
+ };
+
+ onMounted(() => {
+ fetchData();
+ fetchProcessOptions();
+ });
+</script>
\ No newline at end of file
diff --git a/src/views/productionManagement/productStructure/index.vue b/src/views/productionManagement/productStructure/index.vue
new file mode 100644
index 0000000..e32ff8d
--- /dev/null
+++ b/src/views/productionManagement/productStructure/index.vue
@@ -0,0 +1,113 @@
+<template>
+ <div class="app-container">
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ >
+ <template #detail="{row}">
+ <el-button
+ type="primary"
+ text
+ @click="showDetail(row.id)">{{ row.productName }}
+ </el-button>
+ </template>
+ </PIMTable>
+ <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow"/>
+ </div>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {productModelList} from "@/api/basicData/productModel.js";
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue'))
+
+const tableColumn = ref([
+ {
+ label: "浜у搧缂栫爜",
+ prop: "productCode",
+ slot: "detail"
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ dataType: 'slot',
+ slot: "detail"
+ },
+ {
+ label: "瑙勬牸鍨嬪彿",
+ prop: "model",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ }
+]);
+const tableData = ref([]);
+const tableLoading = ref(false);
+const showEdit = ref(false);
+const selectedRows = ref([]);
+const currentRow = ref({});
+const page = reactive({
+ current: 1,
+ size: 10,
+ total: 0,
+});
+const data = reactive({
+ form: {
+ productName: "",
+ },
+ rules: {
+ productName: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
+ },
+ modelForm: {
+ otherModel: '',
+ model: "",
+ unit: "",
+ speculativeTradingName: [],
+ },
+});
+const {form, rules} = toRefs(data);
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+// 鏌ヨ瑙勬牸鍨嬪彿
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getModelList();
+};
+
+const showDetail = (id) => {
+ router.push({
+ path: '/productionManagement/productStructureDetail',
+ query: {
+ id: id
+ }
+ })
+}
+const getModelList = () => {
+ tableLoading.value = true;
+ productModelList({
+ current: page.current,
+ size: page.size,
+ }).then((res) => {
+ tableData.value = res.records;
+ page.total = res.total;
+ tableLoading.value = false;
+ });
+};
+onMounted(() => {
+ getModelList();
+})
+</script>
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
index 76e7414..a597317 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -14,6 +14,15 @@
clearable
prefix-icon="Search"
/>
+ <span class="search_title ml10">鍚堝悓鍙凤細</span>
+ <el-input
+ v-model="searchForm.salesContractNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏�"
+ @change="handleQuery"
+ clearable
+ prefix-icon="Search"
+ />
<el-button type="primary" @click="handleQuery" style="margin-left: 10px"
>鎼滅储</el-button
>
@@ -61,21 +70,21 @@
prop: "salesContractNo",
width: 220,
},
- {
- label: "瀹㈡埛鍚堝悓鍙�",
- prop: "customerContractNo",
- width: 250,
- },
+ // {
+ // label: "瀹㈡埛鍚堝悓鍙�",
+ // prop: "customerContractNo",
+ // width: 250,
+ // },
{
label: "瀹㈡埛鍚嶇О",
prop: "customerName",
width: 250,
},
- {
- label: "椤圭洰鍚嶇О",
- prop: "projectName",
- width:300
- },
+ // {
+ // label: "椤圭洰鍚嶇О",
+ // prop: "projectName",
+ // width:300
+ // },
{
label: "浜у搧澶х被",
prop: "productCategory",
@@ -121,6 +130,7 @@
const data = reactive({
searchForm: {
schedulingUserName: "",
+ salesContractNo: "",
entryDate: [
dayjs().format("YYYY-MM-DD"),
dayjs().add(1, "day").format("YYYY-MM-DD"),
diff --git a/src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue b/src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue
new file mode 100644
index 0000000..b4a76f6
--- /dev/null
+++ b/src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue
@@ -0,0 +1,153 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="dialogFormVisible"
+ title="鑷姩娲惧伐"
+ width="80%"
+ @close="closeDia"
+ >
+ <el-form :model="form" label-width="140px" label-position="top" ref="formRef">
+ <el-divider content-position="left">娲惧伐鍒楄〃</el-divider>
+
+ <el-table
+ :data="dispatchList"
+ border
+ style="width: 100%; margin-top: 20px;"
+ :row-class-name="tableRowClassName"
+ >
+ <el-table-column label="搴忓彿" type="index" width="60" align="center" />
+ <el-table-column label="鍚堝悓鍙�" prop="salesContractNo" width="200" />
+ <el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" width="200" />
+ <!-- <el-table-column label="椤圭洰鍚嶇О" prop="projectName" width="250" /> -->
+ <el-table-column label="浜у搧澶х被" prop="productCategory" width="150" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" width="200" />
+ <el-table-column label="缁戝畾鏈哄櫒" prop="speculativeTradingName" width="120" />
+ <el-table-column label="鎬绘暟閲�" prop="quantity" width="100" align="right" />
+ <el-table-column label="宸叉帓浜�" prop="schedulingNum" width="100" align="right" fixed="right" />
+ <el-table-column label="寰呮帓浜�" prop="pendingQuantity" width="100" align="right" fixed="right" />
+ <el-table-column label="鏈鎺掍骇" width="150" align="center" fixed="right">
+ <template #default="{ row }">
+ <el-input-number
+ v-model="row.schedulingNum"
+ :min="0"
+ :max="row.pendingQuantity"
+ :step="1"
+ :precision="0"
+ size="small"
+ style="width: 120px"
+ @change="(value) => changeCurrentNum(value, row)"
+ />
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭娲惧伐</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, reactive, toRefs, computed} from "vue";
+import {productionDispatch, productionDispatchList} from "@/api/productionManagement/productionOrder.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+
+const data = reactive({
+ form: {},
+ dispatchList: [], // 娲惧伐鍒楄〃鏁版嵁
+});
+
+const { form, dispatchList } = toRefs(data);
+
+
+// 琛ㄦ牸琛屾牱寮�
+const tableRowClassName = ({ rowIndex }) => {
+ if (rowIndex % 2 === 1) {
+ return 'even-row'
+ }
+ return ''
+}
+
+// 淇敼鏈鎺掍骇鏁伴噺
+const changeCurrentNum = (value, row) => {
+ if (value > row.pendingQuantity) {
+ row.schedulingNum = row.pendingQuantity
+ proxy.$modal.msgWarning('鎺掍骇鏁伴噺涓嶅彲澶т簬寰呮帓浜ф暟閲�')
+ }
+}
+
+// 鎵撳紑寮规
+const openDialog = (type, rows) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+
+ // 澶勭悊浼犲叆鐨勬暟鎹�
+ dispatchList.value = rows.map(row => ({
+ ...row,
+ schedulingNum: 0, // 鍒濆鍖栨湰娆℃帓浜ф暟閲忎负0
+ pendingQuantity: (Number(row.quantity) || 0) - (Number(row.schedulingNum) || 0) // 璁$畻寰呮帓浜ф暟閲�
+ }))
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ // 妫�鏌ユ槸鍚︽湁鎺掍骇鏁版嵁
+ const hasSchedulingData = dispatchList.value.some(item => item.schedulingNum > 0)
+ if (!hasSchedulingData) {
+ proxy.$modal.msgWarning('璇疯嚦灏戜负涓�鏉¤褰曡缃帓浜ф暟閲�')
+ return
+ }
+
+ // 鏋勯�犳彁浜ゆ暟鎹� - 鐩存帴浼犻�掓暟缁勶紝涓嶈繃婊�
+ const submitData = dispatchList.value
+
+ console.log('鎻愪氦鑷姩娲惧伐鏁版嵁:', submitData)
+
+ // 璋冪敤API锛堣繖閲岄渶瑕佹牴鎹疄闄呮帴鍙h皟鏁达級
+ productionDispatchList(submitData).then(res => {
+ proxy.$modal.msgSuccess(res.msg);
+ closeDia();
+ }).catch(err => {
+ proxy.$modal.msgError("娲惧伐澶辫触");
+ console.error('娲惧伐澶辫触:', err);
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ dispatchList.value = []
+ emit('close')
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+:deep(.even-row) {
+ background-color: #fafafa;
+}
+
+:deep(.el-table .cell) {
+ padding: 8px 12px;
+}
+
+:deep(.el-table th) {
+ background-color: #f5f7fa;
+ color: #606266;
+ font-weight: 600;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/productionDispatching/components/formDia.vue b/src/views/productionManagement/productionDispatching/components/formDia.vue
index a60f751..971bc6e 100644
--- a/src/views/productionManagement/productionDispatching/components/formDia.vue
+++ b/src/views/productionManagement/productionDispatching/components/formDia.vue
@@ -7,7 +7,7 @@
@close="closeDia"
>
<el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
- <el-row :gutter="30">
+ <!-- <el-row :gutter="30">
<el-col :span="12">
<el-form-item label="椤圭洰鍚嶇О锛�" prop="projectName">
<el-input v-model="form.projectName" placeholder="璇疯緭鍏�" clearable disabled/>
@@ -16,6 +16,18 @@
<el-col :span="12">
<el-form-item label="浜у搧澶х被锛�" prop="productCategory">
<el-input v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ </el-row> -->
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="specificationModel">
+ <el-input v-model="form.specificationModel" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁戝畾鏈哄櫒锛�" prop="speculativeTradingName">
+ <el-input v-model="form.speculativeTradingName" placeholder="鑷姩鑾峰彇" clearable disabled/>
</el-form-item>
</el-col>
</el-row>
@@ -46,6 +58,11 @@
/>
</el-form-item>
</el-col>
+ <el-col :span="12">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <el-input v-model="form.productCategory" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
@@ -54,6 +71,9 @@
v-model="form.schedulingUserId"
placeholder="閫夋嫨浜哄憳"
style="width: 100%;"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
>
<el-option
v-for="user in userList"
@@ -105,11 +125,13 @@
form: {
projectName: "",
productCategory: "",
+ specificationModel: "", // 瑙勬牸鍨嬪彿
quantity: "",
schedulingNum: "",
schedulingUserId: "",
schedulingDate: "",
pendingQuantity: "",
+ speculativeTradingName: "", // 缁戝畾鏈哄櫒鍚嶇О
},
rules: {
schedulingNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
@@ -121,6 +143,7 @@
const userList = ref([])
const userStore = useUserStore()
+
// 鎵撳紑寮规
const openDialog = (type, row) => {
operationType.value = type;
diff --git a/src/views/productionManagement/productionDispatching/index.vue b/src/views/productionManagement/productionDispatching/index.vue
index 527880f..2d39890 100644
--- a/src/views/productionManagement/productionDispatching/index.vue
+++ b/src/views/productionManagement/productionDispatching/index.vue
@@ -1,5 +1,32 @@
<template>
<div class="app-container">
+ <!-- 鐐掓満1-4 灞曠ず锛堟�婚噺 / 姝e湪鐢熶骇閲� / 绌轰綑閲忥級 -->
+ <div class="machines-grid">
+ <div v-for="machine in machines" :key="machine.id" class="machine-card">
+ <div class="machine-title">{{ machine.name }}</div>
+ <div class="machine-metrics">
+ <div class="machine-control">
+ <span>鎬婚噺(kg)锛�</span>
+ <el-input-number v-model="machineData[machine.name].workLoad" :min="0" :step="1" size="small" />
+ </div>
+ <div><span> 棰勮鎶曞叆閲�(kg)锛�</span><span>{{ machineData[machine.name].currentWorkLoad }}</span></div>
+ <div><span>绌轰綑宸ヤ綔閲�(kg)锛�</span><span>{{ machineData[machine.name].vacant }}</span></div>
+ </div>
+ </div>
+ <div class="save-button-container">
+ <div class="loss-rate-container">
+ <span class="loss-rate-label">鎹熻�楃巼(%)锛�</span>
+ <el-select v-model="rate" placeholder="璇烽�夋嫨鎹熻�楃巼" style="width: 120px" size="small">
+ <el-option label="6" :value="6" />
+ <el-option label="7" :value="7" />
+ <el-option label="8" :value="8" />
+ <el-option label="9" :value="9" />
+ <el-option label="10" :value="10" />
+ </el-select>
+ </div>
+ <el-button type="primary" @click="saveMachineTotals" size="small">淇濆瓨璁剧疆</el-button>
+ </div>
+ </div>
<div class="search_form">
<div>
<span class="search_title">瀹㈡埛鍚嶇О锛�</span>
@@ -11,24 +38,40 @@
clearable
prefix-icon="Search"
/>
- <span class="search_title ml10">椤圭洰鍚嶇О锛�</span>
+ <span class="search_title ml10">鍚堝悓鍙凤細</span>
<el-input
- v-model="searchForm.projectName"
+ v-model="searchForm.salesContractNo"
style="width: 240px"
placeholder="璇疯緭鍏�"
@change="handleQuery"
clearable
prefix-icon="Search"
/>
+<!-- <span class="search_title ml10">椤圭洰鍚嶇О锛�</span>-->
+<!-- <el-input-->
+<!-- v-model="searchForm.projectName"-->
+<!-- style="width: 240px"-->
+<!-- placeholder="璇疯緭鍏�"-->
+<!-- @change="handleQuery"-->
+<!-- clearable-->
+<!-- prefix-icon="Search"-->
+<!-- />-->
<span class="search_title ml10">褰曞叆鏃ユ湡锛�</span>
<el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
+ <el-checkbox
+ style="margin-left: 10px"
+ v-model="searchForm.status"
+ label="涓嶆樉绀哄緟鎺掓暟閲忎负0"
+ @change="handleQuery"
+ />
<el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
</div>
<div>
- <el-button type="primary" @click="openForm('add')">鐢熶骇娲惧伐</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- </div>
+ <el-button type="primary" @click="openForm('add')">鐢熶骇娲惧伐</el-button>
+ <el-button type="success" @click="openAutoDispatch">鑷姩娲惧伐</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
</div>
<div class="table_list">
<PIMTable
@@ -44,23 +87,27 @@
></PIMTable>
</div>
<form-dia ref="formDia" @close="handleQuery"></form-dia>
+ <auto-dispatch-dia ref="autoDispatchDia" @close="handleQuery"></auto-dispatch-dia>
</div>
</template>
<script setup>
-import {onMounted, ref} from "vue";
+import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, computed, watch} from "vue";
import FormDia from "@/views/productionManagement/productionDispatching/components/formDia.vue";
+import AutoDispatchDia from "@/views/productionManagement/productionDispatching/components/autoDispatchDia.vue";
import dayjs from "dayjs";
-import {schedulingListPage} from "@/api/productionManagement/productionOrder.js";
+import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading, getLossRate, addLossRate, updateLossRate} from "@/api/productionManagement/productionOrder.js";
import { ElMessageBox } from "element-plus";
const data = reactive({
searchForm: {
customerName: "",
+ salesContractNo: "",
projectName: "",
- entryDate: null, // 褰曞叆鏃ユ湡
- entryDateStart: undefined,
- entryDateEnd: undefined,
+ status: true,
+ entryDate: [dayjs().format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")], // 褰曞叆鏃ユ湡锛岄粯璁ゅ綋澶�
+ entryDateStart: dayjs().format("YYYY-MM-DD"),
+ entryDateEnd: dayjs().format("YYYY-MM-DD"),
},
});
const { searchForm } = toRefs(data);
@@ -71,19 +118,9 @@
width: 220,
},
{
- label: "瀹㈡埛鍚堝悓鍙�",
- prop: "customerContractNo",
- width: 250,
- },
- {
label: "瀹㈡埛鍚嶇О",
prop: "customerName",
width: 250,
- },
- {
- label: "椤圭洰鍚嶇О",
- prop: "projectName",
- width:300
},
{
label: "浜у搧澶х被",
@@ -93,7 +130,12 @@
{
label: "瑙勬牸鍨嬪彿",
prop: "specificationModel",
- width: 220,
+ width: 120,
+ },
+ {
+ label: "缁戝畾鏈哄櫒",
+ prop: "speculativeTradingName",
+ width: 160,
},
{
label: "鍗曚綅",
@@ -104,6 +146,32 @@
label: "褰曞叆鏃ユ湡",
prop: "entryDate",
width: 120,
+ },
+ {
+ label: "鐘舵��",
+ prop: "status",
+ dataType: "tag",
+ formatType: (params) => {
+ if (params == '鐢熶骇涓�') {
+ return "warning";
+ } else if (params == '鏈紑濮�') {
+ return "danger";
+ } else {
+ return "success";
+ }
+ },
+ },
+ {
+ label: "鐢熶骇杩涘害",
+ prop: "progress",
+ formatData: (cellValue) => {
+ // 濡傛灉鍊间负绌烘垨undefined锛屾樉绀虹┖瀛楃涓�
+ if (cellValue === null || cellValue === undefined || cellValue === '') {
+ return '';
+ }
+ // 鐩存帴鍦ㄦ暟瀛楀悗闈㈡坊鍔犵櫨鍒嗗彿
+ return `${cellValue}%`;
+ }
},
{
label: "鏁伴噺",
@@ -118,6 +186,7 @@
label: "寰呮帓鏁伴噺",
prop: "pendingQuantity",
width: 100,
+ fixed: 'right',
},
]);
const tableData = ref([]);
@@ -129,7 +198,140 @@
total: 0,
});
const formDia = ref()
+const autoDispatchDia = ref()
const { proxy } = getCurrentInstance()
+
+// 鐐掓満鏁版嵁
+const machineData = reactive({
+ "鐐掓満1": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
+ "鐐掓満2": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
+ "鐐掓満3": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
+ "鐐掓満4": { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
+})
+
+// 鐐掓満閰嶇疆鏁扮粍
+const machines = [
+ { id: 1, name: '鐐掓満1' },
+ { id: 2, name: '鐐掓満2' },
+ { id: 3, name: '鐐掓満3' },
+ { id: 4, name: '鐐掓満4' }
+]
+
+// 淇濆瓨鐐掓満鎬婚噺璁剧疆
+const saveMachineTotals = () => {
+ // 楠岃瘉鎹熻�楃巼鏄惁宸查�夋嫨
+ if (rate.value === null || rate.value === undefined || isNaN(rate.value)) {
+ proxy.$message.warning('璇烽�夋嫨鎹熻�楃巼');
+ return;
+ }
+
+ // 鏋勯�犱繚瀛樻暟鎹暟缁勶紝浣跨敤machines鏁扮粍寰幆鏋勫缓
+ const saveData = machines.map(machine => {
+ const saveItem = {
+ name: machine.name, // 鐐掓満鍚嶇О
+ workLoad: machineData[machine.name].workLoad, // 鎬婚噺
+ currentWorkLoad: machineData[machine.name].currentWorkLoad, // 棰勮鎶曞叆閲�
+ vacant: machineData[machine.name].vacant // 绌轰綑閲�
+ };
+
+ // 濡傛灉鏄慨鏀规搷浣滐紝闇�瑕佷紶閫抜d瀛楁
+ if (hasQueryData.value) {
+ const queryData = getMachineQueryData(machine.id);
+ if (queryData && queryData.id) {
+ saveItem.id = queryData.id;
+ }
+ }
+
+ return saveItem;
+ });
+
+ // 鏋勯�犳崯鑰楃巼鏁版嵁
+ const rateData = {
+ rate: rate.value
+ };
+
+ // 濡傛灉鏈塈D锛岃鏄庢槸淇敼鎿嶄綔
+ if (rateId.value) {
+ rateData.id = rateId.value;
+ }
+
+ // 鏍规嵁鏄惁鏈夋煡璇㈡暟鎹喅瀹氳皟鐢ㄦ柊澧炴帴鍙h繕鏄慨鏀规帴鍙�
+ const saveApi = hasQueryData.value ? updateSpeculatTrading : addSpeculatTrading;
+ const successMessage = hasQueryData.value ? '鐐掓満璁剧疆淇敼鎴愬姛' : '鐐掓満璁剧疆鏂板鎴愬姛';
+
+ // 鏍规嵁鏄惁鏈塈D鍐冲畾璋冪敤鏂板鎺ュ彛杩樻槸淇敼鎺ュ彛
+ const rateApi = rateId.value ? updateLossRate : addLossRate;
+ const rateSuccessMessage = rateId.value ? '鎹熻�楃巼淇敼鎴愬姛' : '鎹熻�楃巼鏂板鎴愬姛';
+
+ // 骞惰璋冪敤涓や釜鎺ュ彛
+ Promise.all([
+ saveApi(saveData),
+ rateApi(rateData)
+ ]).then(([saveRes, rateRes]) => {
+ proxy.$message.success(successMessage);
+ proxy.$message.success(rateSuccessMessage);
+
+ // 淇濆瓨鎴愬姛鍚庯紝璁剧疆hasQueryData涓簍rue锛屼笅娆′繚瀛樺皢璋冪敤淇敼鎺ュ彛
+ if (!hasQueryData.value) {
+ hasQueryData.value = true;
+ }
+
+ // 濡傛灉杩斿洖浜咺D锛屼繚瀛樿捣鏉�
+ if (rateRes && rateRes.data && rateRes.data.id) {
+ rateId.value = rateRes.data.id;
+ }
+
+ // 淇濆瓨鎴愬姛鍚庨噸鏂拌皟鐢ㄦ煡璇㈤〉闈�
+ getList();
+ }).catch(err => {
+ proxy.$message.error('淇濆瓨澶辫触');
+ console.error('淇濆瓨澶辫触:', err);
+ });
+}
+
+// 鑾峰彇鐐掓満鏌ヨ鏁版嵁
+const machineQueryData = ref([]);
+
+const getMachineQueryData = (machineId) => {
+ return machineQueryData.value.find(item => item.id === machineId);
+};
+
+const getMachineIndex = (item) => {
+ // 鍏煎澶氱瀛楁鍛藉悕锛岃繑鍥� 1-4 涔嬩竴锛屽惁鍒欒繑鍥� 0锛堟湭鐭ワ級
+ const candidates = [item.machineId, item.machineNo, item.machine, item.deviceNo, item.deviceId]
+ for (const v of candidates) {
+ if (v === undefined || v === null) continue
+ const n = Number(String(v).replace(/[^\d]/g, "")) // 鎶藉彇鏁板瓧
+ if ([1,2,3,4].includes(n)) return n
+ }
+ return 0
+}
+
+const computeTodaySummary = () => {
+ const todayStr = dayjs().format("YYYY-MM-DD")
+
+ // 閲嶇疆鎵�鏈夌倰鏈烘暟鎹�
+ machines.forEach(machine => {
+ machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
+ })
+
+ tableData.value.forEach(item => {
+ // 浠呯粺璁″綋澶�
+ const isToday = dayjs(item.entryDate).format("YYYY-MM-DD") === todayStr
+ if (!isToday) return
+
+ // 浣跨敤姝g‘鐨勫瓧娈靛悕锛歸orkLoad锛堢倰鏈哄伐浣滈噺锛�, currentWorkLoad锛堢倰鏈烘鍦ㄥ伐浣滈噺锛�
+ const workLoad = Number(item.workLoad) || 0
+ const currentWorkLoad = Number(item.currentWorkLoad) || 0
+ const machineName = item.speculativeTradingName || '鐐掓満1'
+
+ if (machineData[machineName]) {
+ machineData[machineName].workLoad += workLoad
+ machineData[machineName].currentWorkLoad += currentWorkLoad
+ machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad
+ }
+ })
+}
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
@@ -137,6 +339,56 @@
page.current = 1;
getList();
};
+
+// 鏄惁鏈夋煡璇㈡暟鎹�
+const hasQueryData = ref(false)
+// 鎹熻�楃巼
+const rate = ref(6)
+// 鎹熻�楃巼ID
+const rateId = ref(null)
+
+// 鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+const getMachineProductionData = () => {
+ schedulingList().then((res) => {
+ // 澶勭悊鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+ if (res.data && Array.isArray(res.data)) {
+ // 璁剧疆鏄惁鏈夋煡璇㈡暟鎹�
+ hasQueryData.value = res.data.length > 0
+
+ // 淇濆瓨鏌ヨ鏁版嵁鍒癿achineQueryData
+ machineQueryData.value = res.data;
+
+ // 閲嶇疆鎵�鏈夌倰鏈烘暟鎹�
+ machines.forEach(machine => {
+ machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
+ });
+
+ // 閬嶅巻鏁版嵁锛屾牴鎹煡璇㈣繑鍥炵殑鏁版嵁缁撴瀯澶勭悊
+ res.data.forEach(item => {
+ // 鏍规嵁name瀛楁纭畾鐐掓満
+ const machineName = item.name || '鐐掓満1';
+
+ if (machineData[machineName]) {
+ // 濡傛灉鏌ヨ鏁版嵁涓湁workLoad锛屽垯鍒濆鍖栫倰鏈烘�婚噺
+ if (item.workLoad !== null && item.workLoad !== undefined) {
+ machineData[machineName].workLoad = Number(item.workLoad) || 0;
+ }
+
+ // 濡傛灉鏌ヨ鏁版嵁涓湁currentWorkLoad锛屽垯璁剧疆姝e湪宸ヤ綔閲�
+ if (item.currentWorkLoad !== null && item.currentWorkLoad !== undefined) {
+ machineData[machineName].currentWorkLoad = Number(item.currentWorkLoad) || 0;
+ }
+
+ // 璁$畻绌轰綑宸ヤ綔閲�
+ machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad;
+ }
+ });
+ }
+ }).catch(err => {
+ console.error('鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹け璐�:', err);
+ });
+};
+
const changeDaterange = (value) => {
if (value) {
searchForm.value.entryDateStart = value[0];
@@ -165,9 +417,33 @@
pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
}));
page.total = res.data.total;
+ computeTodaySummary()
+
+ // 鍚屾椂鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
+ getMachineProductionData();
+ // 鑾峰彇鎹熻�楃巼鏁版嵁
+ getLossRateData();
}).catch(() => {
tableLoading.value = false;
})
+};
+
+// 鑾峰彇鎹熻�楃巼鏁版嵁
+const getLossRateData = () => {
+ getLossRate().then((res) => {
+ const data = res.data || res;
+ if (data && data.rate !== undefined && data.rate !== null) {
+ rate.value = Number(data.rate); // 纭繚杞崲涓烘暟瀛�
+ rateId.value = data.id || null;
+ } else {
+ rate.value = 6;
+ rateId.value = null;
+ }
+ }).catch(err => {
+ console.error('鑾峰彇鎹熻�楃巼鏁版嵁澶辫触:', err);
+ rate.value = 6;
+ rateId.value = null;
+ });
};
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
@@ -189,6 +465,26 @@
})
};
+// 鎵撳紑鑷姩娲惧伐寮规
+const openAutoDispatch = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$message.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+ return;
+ }
+
+ // 杩囨护鎺夊緟鎺掍骇鏁伴噺涓�0鐨勬暟鎹�
+ const validRows = selectedRows.value.filter(row => row.pendingQuantity > 0);
+
+ if (validRows.length === 0) {
+ proxy.$message.warning("閫変腑鐨勬暟鎹棤闇�娲惧伐");
+ return;
+ }
+
+ nextTick(() => {
+ autoDispatchDia.value?.openDialog('auto', validRows)
+ })
+};
+
// 瀵煎嚭
const handleOut = () => {
ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
@@ -206,7 +502,129 @@
onMounted(() => {
getList();
+ getLossRateData();
});
</script>
-<style scoped></style>
+<style scoped>
+.summary-bar{
+ display: flex;
+ gap: 16px;
+ margin: 10px 0 16px 0;
+}
+.summary-item{
+ background: #f5f7fa;
+ border: 1px solid #ebeef5;
+ border-radius: 6px;
+ padding: 10px 16px;
+ min-width: 160px;
+}
+.summary-label{
+ color: #909399;
+ font-size: 12px;
+ margin-bottom: 6px;
+}
+.summary-value{
+ color: #303133;
+ font-size: 20px;
+ font-weight: 600;
+}
+.summary-control{
+ display: flex;
+ align-items: center;
+ height: 28px;
+}
+.machines-grid{
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ margin-bottom: 20px;
+ padding: 16px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+}
+.machine-card{
+ border: 1px solid #dee2e6;
+ border-radius: 8px;
+ padding: 16px;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+ transition: all 0.3s ease;
+}
+.machine-card:hover{
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+}
+.machine-title{
+ font-weight: 600;
+ font-size: 16px;
+ margin-bottom: 12px;
+ color: #2c3e50;
+ text-align: center;
+ padding-bottom: 8px;
+ border-bottom: 2px solid #3498db;
+}
+.machine-metrics{
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ color: #495057;
+}
+.machine-control{
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f1f3f4;
+}
+.machine-control span{
+ font-size: 14px;
+ white-space: nowrap;
+ color: #6c757d;
+ font-weight: 500;
+}
+.machine-metrics > div:not(.machine-control) {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 4px 0;
+ font-size: 14px;
+}
+.machine-metrics > div:not(.machine-control) span:first-child {
+ color: #6c757d;
+}
+.machine-metrics > div:not(.machine-control) span:last-child {
+ font-weight: 600;
+ color: #2c3e50;
+}
+.save-button-container{
+ grid-column: 1 / -1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 16px;
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid #e9ecef;
+}
+.loss-rate-container{
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.loss-rate-label{
+ font-size: 14px;
+ color: #6c757d;
+ font-weight: 500;
+ white-space: nowrap;
+}
+</style>
+
+
+
+
+
+
+
diff --git a/src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue b/src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue
new file mode 100644
index 0000000..354f9de
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue
@@ -0,0 +1,555 @@
+<template>
+ <div>
+ <el-dialog v-model="isShow"
+ title="宸ヨ壓璺嚎椤圭洰"
+ width="800px"
+ @close="closeModal">
+ <div class="operate-button">
+ <el-button type="primary"
+ @click="isShowProductSelectDialog = true"
+ class="mb5"
+ style="margin-bottom: 10px;">
+ 閫夋嫨浜у搧
+ </el-button>
+ <el-switch v-model="isTable"
+ inline-prompt
+ active-text="琛ㄦ牸"
+ inactive-text="鍒楄〃"
+ @change="handleViewChange" />
+ </div>
+ <el-table v-if="isTable"
+ ref="multipleTable"
+ v-loading="tableLoading"
+ border
+ :data="routeItems"
+ :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+ row-key="id"
+ tooltip-effect="dark"
+ class="lims-table"
+ style="cursor: move;">
+ <el-table-column align="center"
+ label="搴忓彿"
+ width="60">
+ <template #default="scope">
+ {{ scope.$index + 1 }}
+ </template>
+ </el-table-column>
+ <el-table-column v-for="(item, index) in tableColumn"
+ :key="index"
+ :label="item.label"
+ :width="item.width"
+ show-overflow-tooltip>
+ <template #default="scope"
+ v-if="item.dataType === 'action'">
+ <el-button v-for="(op, opIndex) in item.operation"
+ :key="opIndex"
+ :type="op.type"
+ :link="op.link"
+ size="small"
+ @click.stop="op.clickFun(scope.row)">
+ {{ op.name }}
+ </el-button>
+ </template>
+ <template #default="scope"
+ v-else>
+ <template v-if="item.prop === 'processId'">
+ <el-select v-model="scope.row[item.prop]"
+ style="width: 100%;"
+ @mousedown.stop>
+ <el-option v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id" />
+ </el-select>
+ </template>
+ <template v-else>
+ {{ scope.row[item.prop] || '-' }}
+ </template>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- 浣跨敤鏅�歞iv鏇夸唬el-steps -->
+ <div v-else
+ ref="stepsContainer"
+ class="mb5 custom-steps"
+ style="padding: 10px 0; display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-start;">
+ <div v-for="(item, index) in routeItems"
+ :key="item.id"
+ class="custom-step draggable-step"
+ :data-id="item.id"
+ style="cursor: move; flex: 0 0 auto; min-width: 220px;">
+ <div class="step-content">
+ <div class="step-number">{{ index + 1 }}</div>
+ <el-card :header="item.productName"
+ class="step-card"
+ style="cursor: move;">
+ <div class="step-card-content">
+ <p>{{ item.model }}</p>
+ <p>{{ item.unit }}</p>
+ <el-select v-model="item.processId"
+ style="width: 100%;"
+ @mousedown.stop>
+ <el-option v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id" />
+ </el-select>
+ </div>
+ <template #footer>
+ <div class="step-card-footer">
+ <el-button type="danger"
+ link
+ size="small"
+ @click.stop="removeItemByID(item.id)">鍒犻櫎</el-button>
+ </div>
+ </template>
+ </el-card>
+ </div>
+ </div>
+ </div>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ <ProductSelectDialog v-model="isShowProductSelectDialog"
+ @confirm="handelSelectProducts" />
+ </div>
+</template>
+
+<script setup>
+ import {
+ ref,
+ computed,
+ getCurrentInstance,
+ onMounted,
+ onUnmounted,
+ nextTick,
+ } from "vue";
+ import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import {
+ findProductProcessRouteItemList,
+ addOrUpdateProductProcessRouteItem,
+ deleteRouteItem,
+ } from "@/api/productionManagement/productProcessRoute.js";
+ import { processList } from "@/api/productionManagement/productionProcess.js";
+ import Sortable from "sortablejs";
+
+ const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ default: false,
+ },
+ record: {
+ type: Object,
+ required: true,
+ default: () => ({}),
+ },
+ });
+
+ const emit = defineEmits(["update:visible", "completed"]);
+
+ const processOptions = ref([]);
+ const tableLoading = ref(false);
+ const isShowProductSelectDialog = ref(false);
+ const routeItems = ref([]);
+ let tableSortable = null;
+ let stepsSortable = null;
+ const multipleTable = ref(null);
+ const stepsContainer = ref(null);
+ const isTable = ref(true);
+
+ const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit("update:visible", val);
+ },
+ });
+
+ const tableColumn = ref([
+ { label: "浜у搧鍚嶇О", prop: "productName", width: 180 },
+ { label: "瑙勬牸鍚嶇О", prop: "model", width: 150 },
+ { label: "鍗曚綅", prop: "unit", width: 80 },
+ { label: "宸ュ簭鍚嶇О", prop: "processId", width: 180 },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 100,
+ operation: [
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ clickFun: row => {
+ console.log(row.id, "鍒犻櫎");
+
+ const dragSortx = routeItems.value.findIndex(
+ item => item.dragSort === row.dragSort
+ );
+ const idx = routeItems.value.findIndex(item => item.id === row.id);
+ console.log(idx, "idx");
+ if (row.id) {
+ deleteRouteItemByIds({ id: row.id }, idx);
+ } else {
+ removeItem(dragSortx);
+ }
+ },
+ },
+ ],
+ },
+ ]);
+
+ const removeItem = index => {
+ console.log("杞垹闄�", index);
+
+ routeItems.value.splice(index, 1);
+ updateDragSort();
+ nextTick(() => initSortable());
+ };
+
+ const removeItemByID = id => {
+ const idx = routeItems.value.findIndex(item => item.id === id);
+ if (idx > -1) {
+ routeItems.value.splice(idx, 1);
+ updateDragSort();
+ nextTick(() => initSortable());
+ }
+ };
+
+ const deleteRouteItemByIds = (ids, index) => {
+ deleteRouteItem(ids).then(res => {
+ routeItems.value.splice(index, 1);
+ updateDragSort();
+ nextTick(() => initSortable());
+ });
+ };
+
+ const closeModal = () => {
+ isShow.value = false;
+ };
+
+ const updateDragSort = () => {
+ routeItems.value.forEach((item, index) => {
+ item.dragSort = index + 1;
+ });
+ routeItems.value = [...routeItems.value];
+ console.log("鏇存柊鍚庣殑鏁扮粍:", routeItems.value);
+ };
+
+ const handelSelectProducts = products => {
+ destroySortable();
+
+ // 璁$畻鏂扮殑dragSort鍊艰捣濮嬬偣
+ const maxDragSort =
+ routeItems.value.length > 0
+ ? Math.max(...routeItems.value.map(item => item.dragSort || 0))
+ : 0;
+
+ const newData = products.map(({ id, ...product }, index) => ({
+ ...product,
+ productModelId: id,
+ routeId: props.record.id,
+ // id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
+ processId: undefined,
+ dragSort: maxDragSort + index + 1,
+ }));
+
+ console.log("閫夋嫨浜у搧鍓嶆暟缁�:", routeItems.value);
+ routeItems.value.push(...newData);
+ updateDragSort();
+ console.log("閫夋嫨浜у搧鍚庢暟缁�:", routeItems.value);
+
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ // 寮哄埗閲嶆柊娓叉煋缁勪欢
+ if (proxy?.$forceUpdate) {
+ proxy.$forceUpdate();
+ }
+
+ const temp = [...routeItems.value];
+ routeItems.value = [];
+ nextTick(() => {
+ routeItems.value = temp;
+ initSortable();
+ });
+ });
+ };
+
+ const findProcessRouteItems = () => {
+ tableLoading.value = true;
+ findProductProcessRouteItemList({ orderId: props.record.id })
+ .then(res => {
+ tableLoading.value = false;
+ routeItems.value = res.data.map(item => ({
+ ...item,
+ processId: item.processId === 0 ? undefined : item.processId,
+ }));
+ // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ });
+ };
+
+ const findProcessList = () => {
+ processList({})
+ .then(res => {
+ processOptions.value = res.data;
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+ });
+ };
+
+ const { proxy } = getCurrentInstance() || {};
+
+ const handleSubmit = () => {
+ const hasEmptyProcess = routeItems.value.some(item => !item.processId);
+ if (hasEmptyProcess) {
+ proxy?.$modal?.msgError("璇蜂负鎵�鏈夐」鐩�夋嫨宸ュ簭");
+ return;
+ }
+
+ addOrUpdateProductProcessRouteItem({
+ routeId: props.record.id,
+ processRouteItem: routeItems.value,
+ })
+ .then(res => {
+ isShow.value = false;
+ emit("completed");
+ proxy?.$modal?.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ .catch(err => {
+ proxy?.$modal?.msgError(`鎻愪氦澶辫触锛�${err.msg || "缃戠粶寮傚父"}`);
+ });
+ };
+
+ const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (stepsSortable) {
+ stepsSortable.destroy();
+ stepsSortable = null;
+ }
+ };
+
+ const initSortable = () => {
+ destroySortable();
+
+ if (isTable.value) {
+ if (!multipleTable.value) return;
+ const tbody =
+ multipleTable.value.$el.querySelector(".el-table__body tbody") ||
+ multipleTable.value.$el.querySelector(
+ ".el-table__body-wrapper > table > tbody"
+ );
+ if (!tbody) return;
+
+ tableSortable = new Sortable(tbody, {
+ animation: 150,
+ ghostClass: "sortable-ghost",
+ handle: ".el-table__row",
+ filter: ".el-button, .el-select",
+ onEnd: evt => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex])
+ return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭锛屼笌琛ㄦ牸妯″紡淇濇寔涓�鑷�
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ updateDragSort();
+ console.log("鎺掑簭鍚庢暟缁�:", routeItems.value);
+ },
+ });
+ } else {
+ if (!stepsContainer.value) return;
+
+ // 淇敼锛氱洿鎺ヤ娇鐢╯tepsContainer.value浣滀负鎷栨嫿瀹瑰櫒
+ const stepsList = stepsContainer.value;
+ if (!stepsList) {
+ console.warn("鏈壘鍒版楠ゆ潯鎷栨嫿瀹瑰櫒");
+ return;
+ }
+
+ // 淇敼锛氱畝鍖栨嫋鎷介厤缃�
+ stepsSortable = new Sortable(stepsList, {
+ animation: 150,
+ ghostClass: "sortable-ghost",
+ draggable: ".draggable-step", // 鍙嫋鎷藉厓绱�
+ handle: ".draggable-step, .step-card", // 鎷栨嫿鎵嬫焺
+ filter: ".el-button, .el-select, .el-input", // 杩囨护鎸夐挳/閫夋嫨鍣�
+ forceFallback: true,
+ fallbackClass: "sortable-fallback",
+ preventOnFilter: true,
+ scroll: true,
+ scrollSensitivity: 30,
+ scrollSpeed: 10,
+ bubbleScroll: true,
+ onEnd: evt => {
+ if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex])
+ return;
+
+ // 浣跨敤鏁扮粍 splice 鏂规硶閲嶆柊鎺掑簭
+ const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
+ routeItems.value.splice(evt.newIndex, 0, moveItem);
+ updateDragSort();
+ },
+ });
+
+ // 璋冭瘯锛氭墦鍗板鍣ㄥ拰瀹炰緥锛岀‘璁ょ粦瀹氭垚鍔�
+ console.log("姝ラ鏉℃嫋鎷藉鍣�:", stepsList);
+ console.log("Sortable瀹炰緥:", stepsSortable);
+ }
+ };
+
+ const handleViewChange = () => {
+ destroySortable();
+ // 寤惰繜鍒濆鍖栵紝纭繚瑙嗗浘鍒囨崲鍚嶥OM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+ };
+
+ onMounted(() => {
+ findProcessRouteItems();
+ findProcessList();
+ });
+
+ onUnmounted(() => {
+ destroySortable();
+ });
+
+ defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+ });
+</script>
+
+<style scoped>
+ :deep(.sortable-ghost) {
+ opacity: 0.6;
+ background-color: #f5f7fa !important;
+ }
+
+ :deep(.el-table__row) {
+ transition: background-color 0.2s;
+ }
+
+ :deep(.el-table__row:hover) {
+ background-color: #f9fafc !important;
+ }
+
+ :deep(.el-card__footer) {
+ padding: 0 !important;
+ }
+
+ .operate-button {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ /* 淇敼锛氳嚜瀹氫箟姝ラ鏉″鍣ㄦ牱寮� */
+ .custom-steps {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ gap: 20px;
+ min-height: 100px;
+ }
+
+ /* 淇敼锛氳嚜瀹氫箟姝ラ椤规牱寮� */
+ .custom-step {
+ cursor: move !important;
+ padding: 8px;
+ position: relative;
+ transition: all 0.2s ease;
+ flex: 0 0 auto;
+ min-width: 220px;
+ touch-action: none;
+ }
+
+ /* 鎷栨嫿鎮诞鏍峰紡锛屾彁绀哄彲鎷栨嫿 */
+ .custom-step:hover {
+ background-color: rgba(64, 158, 255, 0.05);
+ transform: translateY(-2px);
+ }
+
+ .sortable-ghost {
+ opacity: 0.4;
+ background-color: #f5f7fa !important;
+ border: 2px dashed #409eff;
+ margin: 10px;
+ transform: scale(1.02);
+ }
+
+ .sortable-fallback {
+ opacity: 0.9;
+ background-color: #f5f7fa;
+ border: 1px solid #409eff;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ transform: rotate(2deg);
+ margin: 10px;
+ }
+
+ .step-card {
+ cursor: move !important;
+ transition: box-shadow 0.2s ease;
+ user-select: none;
+ -webkit-user-select: none;
+ pointer-events: auto;
+ margin: 10px;
+ height: 240px;
+ }
+
+ .step-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ .step-content {
+ width: 220px;
+ user-select: none;
+ }
+
+ .step-card-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .step-card-footer {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 10px;
+ }
+
+ /* 鑷畾涔夊簭鍙锋牱寮忎紭鍖� */
+ .step-number {
+ font-weight: bold;
+ text-align: center;
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ margin: 0 auto 10px;
+ background: #409eff;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 14px;
+ }
+</style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 9928d46..df7f950 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -1,198 +1,193 @@
<template>
- <div class="app-container">
- <div class="search_form">
- <div>
- <span class="search_title">瀹㈡埛鍚嶇О锛�</span>
- <el-input
- v-model="searchForm.customerName"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- @change="handleQuery"
- clearable
- prefix-icon="Search"
- />
- <span class="search_title ml10">椤圭洰鍚嶇О锛�</span>
- <el-input
- v-model="searchForm.projectName"
- style="width: 240px"
- placeholder="璇疯緭鍏�"
- @change="handleQuery"
- clearable
- prefix-icon="Search"
- />
- <span class="search_title ml10">褰曞叆鏃ユ湡锛�</span>
- <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
- <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
- >鎼滅储</el-button
- >
- </div>
- <div>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- </div>
- </div>
- <div class="table_list">
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :tableLoading="tableLoading"
- @pagination="pagination"
- ></PIMTable>
- </div>
- </div>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ :inline="true">
+ <el-form-item label="瀹㈡埛鍚嶇О:">
+ <el-input v-model="searchForm.customerName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="鍚堝悓鍙�:">
+ <el-input v-model="searchForm.salesContractNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="浜у搧鍚嶇О:">
+ <el-input v-model="searchForm.productCategory"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="瑙勬牸:">
+ <el-input v-model="searchForm.specificationModel"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ <div>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"></PIMTable>
+ </div>
+ <process-route-item-form v-if="isShowItemModal"
+ v-model:visible="isShowItemModal"
+ :record="record"
+ @completed="getList" />
+ </div>
</template>
<script setup>
-import {onMounted, ref} from "vue";
-import { ElMessageBox } from "element-plus";
-import dayjs from "dayjs";
-import {schedulingListPage} from "@/api/productionManagement/productionOrder.js";
-const { proxy } = getCurrentInstance();
+ import { onMounted, ref } from "vue";
+ import { ElMessageBox } from "element-plus";
+ import dayjs from "dayjs";
+ import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
+ const { proxy } = getCurrentInstance();
+ import ProcessRouteItemForm from "@/views/productionManagement/productionOrder/ProcessRouteItemForm.vue";
-const tableColumn = ref([
- {
- label: "褰曞叆鏃ユ湡",
- prop: "entryDate",
- width: 120,
- },
- {
- label: "鍚堝悓鍙�",
- prop: "salesContractNo",
- width: 220,
- },
- {
- label: "瀹㈡埛鍚堝悓鍙�",
- prop: "customerContractNo",
- width: 250,
- },
- {
- label: "瀹㈡埛鍚嶇О",
- prop: "customerName",
- width: 250,
- },
- {
- label: "椤圭洰鍚嶇О",
- prop: "projectName",
- width:300
- },
- {
- label: "浠樻鐘舵��",
- prop: "status",
- dataType: "tag",
- formatType: (params) => {
- if (params == '鏈畬鎴�') {
- return "danger";
- } else if (params == '宸插畬鎴�') {
- return "success";
- } else {
- return null;
- }
- },
- },
- {
- label: "浜у搧澶х被",
- prop: "productCategory",
- width: 160,
- },
- {
- label: "瑙勬牸鍨嬪彿",
- prop: "specificationModel",
- width: 220,
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- width:90
- },
- {
- label: "鏁伴噺",
- prop: "quantity",
- },
- {
- label: "鎺掍骇鏁伴噺",
- prop: "schedulingNum",
- width: 100,
- },
- {
- label: "瀹屽伐鏁伴噺",
- prop: "successNum",
- width: 100,
- },
-]);
-const tableData = ref([]);
-const tableLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
- total: 0,
-});
+ const tableColumn = ref([
+ {
+ label: "鐢熶骇璁㈠崟鍙�",
+ prop: "npsNo",
+ },
+ {
+ label: "閿�鍞悎鍚屽彿",
+ prop: "salesContractNo",
+ },
+ {
+ label: "瀹㈡埛鍚嶇О",
+ prop: "customerName",
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productCategory",
+ },
+ {
+ label: "瑙勬牸",
+ prop: "specificationModel",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 200,
+ operation: [
+ {
+ name: "宸ヨ壓璺嚎",
+ type: "text",
+ clickFun: row => {
+ showRouteItemModal(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
-const data = reactive({
- searchForm: {
- customerName: "",
- projectName: "",
- entryDate: null, // 褰曞叆鏃ユ湡
- entryDateStart: undefined,
- entryDateEnd: undefined,
- },
-});
-const { searchForm } = toRefs(data);
+ const data = reactive({
+ searchForm: {
+ customerName: "",
+ salesContractNo: "",
+ projectName: "",
+ productCategory: "",
+ specificationModel: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-const changeDaterange = (value) => {
- if (value) {
- searchForm.value.entryDateStart = value[0];
- searchForm.value.entryDateEnd = value[1];
- } else {
- searchForm.value.entryDateStart = undefined;
- searchForm.value.entryDateEnd = undefined;
- }
- handleQuery();
-};
-const getList = () => {
- tableLoading.value = true;
- // 鏋勯�犱竴涓柊鐨勫璞★紝涓嶅寘鍚玡ntryDate瀛楁
- const params = { ...searchForm.value, ...page };
- params.entryDate = undefined
- schedulingListPage(params).then((res) => {
- tableLoading.value = false;
- tableData.value = res.data.records;
- page.total = res.data.total;
- }).catch(() => {
- tableLoading.value = false;
- })
-};
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const changeDaterange = value => {
+ if (value) {
+ searchForm.value.entryDateStart = value[0];
+ searchForm.value.entryDateEnd = value[1];
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ // 鏋勯�犱竴涓柊鐨勫璞★紝涓嶅寘鍚玡ntryDate瀛楁
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined;
+ productOrderListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+ };
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/salesLedger/scheduling/export", {}, "鐢熶骇璁㈠崟.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
+ const isShowItemModal = ref(false);
+ const record = ref({});
+ const showRouteItemModal = row => {
+ isShowItemModal.value = true;
+ record.value = row;
+ };
-onMounted(() => {
- getList();
-});
+ // 瀵煎嚭
+ const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/salesLedger/scheduling/export", {}, "鐢熶骇璁㈠崟.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
+
+ const handleConfirmRoute = () => {};
+
+ onMounted(() => {
+ getList();
+ });
</script>
<style scoped lang="scss"></style>
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
new file mode 100644
index 0000000..31e3109
--- /dev/null
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -0,0 +1,102 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="缂栬緫宸ュ簭"
+ width="400"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="宸ュ簭鍚嶇О锛�"
+ prop="name"
+ :rules="[
+ {
+ required: true,
+ message: '璇疯緭鍏ュ伐搴忓悕绉�',
+ },
+ {
+ max: 100,
+ message: '鏈�澶�100涓瓧绗�',
+ }
+ ]">
+ <el-input v-model="formState.name" />
+ </el-form-item>
+ <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
+ <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance } from "vue";
+import {update} from "@/api/productionManagement/productionProcess.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+
+ record: {
+ type: Object,
+ required: true,
+ }
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ id: props.record.id,
+ name: props.record.name,
+ remark: props.record.remark,
+ salaryQuota: props.record.salaryQuota,
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ update(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
new file mode 100644
index 0000000..a73a755
--- /dev/null
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -0,0 +1,96 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="鏂板宸ュ簭"
+ width="400"
+ @close="closeModal"
+ >
+ <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="宸ュ簭鍚嶇О锛�"
+ prop="name"
+ :rules="[
+ {
+ required: true,
+ message: '璇疯緭鍏ュ伐搴忓悕绉�',
+ },
+ {
+ max: 100,
+ message: '鏈�澶�100涓瓧绗�',
+ }
+ ]">
+ <el-input v-model="formState.name" />
+ </el-form-item>
+ <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
+ <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleSubmit">纭</el-button>
+ <el-button @click="closeModal">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, computed, getCurrentInstance } from "vue";
+import {add} from "@/api/productionManagement/productionProcess.js";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const formState = ref({
+ name: '',
+ remark: '',
+ salaryQuota: '',
+});
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+let { proxy } = getCurrentInstance()
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const handleSubmit = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (valid) {
+ add(formState.value).then(res => {
+ // 鍏抽棴妯℃�佹
+ isShow.value = false;
+ // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
+ emit('completed');
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ }
+ })
+};
+
+defineExpose({
+ closeModal,
+ handleSubmit,
+ isShow,
+});
+</script>
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
new file mode 100644
index 0000000..ea2a341
--- /dev/null
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -0,0 +1,218 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ :inline="true">
+ <el-form-item label="宸ュ簭鍚嶇О:">
+ <el-input v-model="searchForm.name"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿:">
+ <el-input v-model="searchForm.no"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right"
+ class="mb10">
+ <el-button type="primary"
+ @click="showNewModal">鏂板宸ュ簭</el-button>
+ <el-button type="danger"
+ @click="handleDelete"
+ :disabled="selectedRows.length === 0"
+ plain>鍒犻櫎宸ュ簭</el-button>
+ </div>
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"></PIMTable>
+ </div>
+ <new-process v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ @completed="getList" />
+ <edit-process v-if="isShowEditModal"
+ v-model:visible="isShowEditModal"
+ :record="record"
+ @completed="getList" />
+ </div>
+</template>
+
+<script setup>
+ import { onMounted, ref } from "vue";
+ import NewProcess from "@/views/productionManagement/productionProcess/New.vue";
+ import EditProcess from "@/views/productionManagement/productionProcess/Edit.vue";
+ import { listPage, del } from "@/api/productionManagement/productionProcess.js";
+
+ const data = reactive({
+ searchForm: {
+ name: "",
+ no: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
+ const tableColumn = ref([
+ {
+ label: "宸ュ簭鍚嶇О",
+ prop: "name",
+ },
+ {
+ label: "宸ュ簭缂栧彿",
+ prop: "no",
+ },
+ {
+ label: "宸ヨ祫瀹氶",
+ prop: "salaryQuota",
+ },
+ {
+ label: "澶囨敞",
+ prop: "remark",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 280,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: row => {
+ showEditModal(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const selectedRows = ref([]);
+ const tableLoading = ref(false);
+ const isShowNewModal = ref(false);
+ const isShowEditModal = ref(false);
+ const record = ref({});
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const { proxy } = getCurrentInstance();
+
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined;
+ listPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ }));
+ page.total = res.data.total;
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ });
+ };
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ };
+
+ // 鎵撳紑鏂板寮规
+ const showNewModal = () => {
+ isShowNewModal.value = true;
+ };
+
+ const showEditModal = row => {
+ isShowEditModal.value = true;
+ record.value = row;
+ };
+
+ // 鍒犻櫎
+ function handleDelete() {
+ const no = selectedRows.value.map(item => item.no);
+ const ids = selectedRows.value.map(item => item.id);
+ if (no.length > 2) {
+ proxy.$modal
+ .confirm(
+ '鏄惁纭鍒犻櫎宸ュ簭缂栧彿涓�"' +
+ no[0] +
+ "銆�" +
+ no[1] +
+ '"绛�' +
+ no.length +
+ "鏉℃暟鎹」锛�"
+ )
+ .then(function () {
+ return del(ids);
+ })
+ .then(() => {
+ getList();
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+ } else {
+ proxy.$modal
+ .confirm('鏄惁纭鍒犻櫎宸ュ簭缂栧彿涓�"' + no + '"鐨勬暟鎹」锛�')
+ .then(function () {
+ return del(ids);
+ })
+ .then(() => {
+ getList();
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+ }
+ }
+
+ // 瀵煎嚭
+ // const handleOut = () => {
+ // ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ // confirmButtonText: "纭",
+ // cancelButtonText: "鍙栨秷",
+ // type: "warning",
+ // })
+ // .then(() => {
+ // proxy.download("/salesLedger/scheduling/exportTwo", {}, "宸ュ簭鎺掍骇.xlsx");
+ // })
+ // .catch(() => {
+ // proxy.$modal.msg("宸插彇娑�");
+ // });
+ // };
+
+ onMounted(() => {
+ getList();
+ });
+</script>
+
+<style scoped></style>
diff --git a/src/views/productionManagement/productionReporting/Input.vue b/src/views/productionManagement/productionReporting/Input.vue
new file mode 100644
index 0000000..7959848
--- /dev/null
+++ b/src/views/productionManagement/productionReporting/Input.vue
@@ -0,0 +1,107 @@
+<template>
+ <div>
+ <el-dialog
+ v-model="isShow"
+ title="鎶曞叆"
+ @close="closeModal"
+ >
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="data"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ ></PIMTable>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="closeModal">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref, computed, onMounted} from "vue";
+import { productionProductInputListPage } from "@/api/productionManagement/productionProductInput";
+
+const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ productionProductMainId: {
+ type: Number,
+ required: true,
+ },
+});
+
+const emit = defineEmits(['update:visible', 'completed']);
+
+const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0
+});
+
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ fetchData();
+};
+
+const tableLoading = ref(false);
+
+const tableColumn = [
+ {
+ label: '鎶ュ伐鍗曞彿',
+ prop: 'productNo',
+ },
+ {
+ label: '浜у搧鍨嬪彿',
+ prop: 'model',
+ },
+ {
+ label: '鎶曞叆鏁伴噺',
+ prop: 'quantity',
+ },
+]
+
+const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit('update:visible', val);
+ },
+});
+
+const data = ref([])
+
+const closeModal = () => {
+ isShow.value = false;
+};
+
+const fetchData = () => {
+ tableLoading.value = true;
+ const params = { productMainId: props.productionProductMainId, ...page };
+
+ productionProductInputListPage(params).then(res => {
+ tableLoading.value = false;
+ data.value = res.data.records;
+ page.total = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+
+defineExpose({
+ closeModal,
+ isShow,
+});
+
+onMounted(() => {
+ fetchData()
+})
+</script>
diff --git a/src/views/productionManagement/productionReporting/Output.vue b/src/views/productionManagement/productionReporting/Output.vue
new file mode 100644
index 0000000..4eeac43
--- /dev/null
+++ b/src/views/productionManagement/productionReporting/Output.vue
@@ -0,0 +1,106 @@
+<template>
+ <div>
+ <el-dialog v-model="isShow"
+ title="浜у嚭"
+ @close="closeModal">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="data"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"></PIMTable>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary"
+ @click="closeModal">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { ref, computed, onMounted } from "vue";
+ import { productionProductOutputListPage } from "@/api/productionManagement/productionProductOutput.js";
+
+ const props = defineProps({
+ visible: {
+ type: Boolean,
+ required: true,
+ },
+ productionProductMainId: {
+ type: Number,
+ required: true,
+ },
+ });
+
+ const emit = defineEmits(["update:visible", "completed"]);
+
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ fetchData();
+ };
+
+ const tableLoading = ref(false);
+
+ const tableColumn = [
+ {
+ label: "鎶ュ伐鍗曞彿",
+ prop: "productNo",
+ },
+ {
+ label: "浜у搧鍨嬪彿",
+ prop: "model",
+ },
+ {
+ label: "浜у嚭鏁伴噺",
+ prop: "quantity",
+ },
+ ];
+
+ const isShow = computed({
+ get() {
+ return props.visible;
+ },
+ set(val) {
+ emit("update:visible", val);
+ },
+ });
+
+ const data = ref([]);
+
+ const closeModal = () => {
+ isShow.value = false;
+ };
+
+ const fetchData = () => {
+ tableLoading.value = true;
+ const params = { productMainId: props.productionProductMainId, ...page };
+
+ productionProductOutputListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ data.value = res.data.records;
+ page.total = res.data.total;
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ });
+ };
+
+ defineExpose({
+ closeModal,
+ isShow,
+ });
+
+ onMounted(() => {
+ fetchData();
+ });
+</script>
diff --git a/src/views/productionManagement/productionReporting/components/formDia.vue b/src/views/productionManagement/productionReporting/components/formDia.vue
index 89f6c76..2eb1c0b 100644
--- a/src/views/productionManagement/productionReporting/components/formDia.vue
+++ b/src/views/productionManagement/productionReporting/components/formDia.vue
@@ -13,8 +13,15 @@
<el-input v-model="form.schedulingNum" placeholder="璇疯緭鍏�" clearable disabled/>
</el-form-item>
</el-col>
- <el-col :span="12">
- <el-form-item label="鏈鐢熶骇鏁伴噺锛�" prop="finishedNum">
+ <el-col :span="12">
+ <el-form-item label="寰呯敓浜ф暟閲忥細" prop="pendingNum">
+ <el-input v-model="form.pendingNum" placeholder="璇疯緭鍏�" clearable disabled/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鏈鐢熶骇鏁伴噺锛�" prop="finishedNum">
<el-input-number
v-model="form.finishedNum"
placeholder="璇疯緭鍏�"
@@ -25,13 +32,18 @@
style="width: 100%"
@change="changeNum"
/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍗曚环(鍏�)锛�" prop="unitPrice">
+ <el-input v-model="form.unitPrice" placeholder="璇疯緭鍏�" clearable @input="calculateTotalPrice"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="30">
<el-col :span="12">
- <el-form-item label="寰呯敓浜ф暟閲忥細" prop="pendingNum">
- <el-input v-model="form.pendingNum" placeholder="璇疯緭鍏�" clearable disabled/>
+ <el-form-item label="鎬讳环(鍏�)锛�" prop="totalPrice">
+ <el-input v-model="form.totalPrice" placeholder="璇疯緭鍏�" clearable disabled/>
</el-form-item>
</el-col>
</el-row>
@@ -42,6 +54,9 @@
v-model="form.schedulingUserId"
placeholder="閫夋嫨浜哄憳"
style="width: 100%;"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
>
<el-option
v-for="user in userList"
@@ -95,6 +110,8 @@
finishedNum: "",
schedulingUserId: "",
schedulingDate: "",
+ unitPrice: "",
+ totalPrice: "",
},
rules: {
schedulingNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
@@ -118,6 +135,19 @@
proxy.$modal.msgWarning('鏈鐢熶骇鏁伴噺涓嶅彲澶т簬鎺掍骇鏁伴噺')
}
form.value.pendingNum = form.value.schedulingNum - form.value.finishedNum;
+ calculateTotalPrice();
+}
+
+// 璁$畻鎬讳环
+const calculateTotalPrice = () => {
+ const quantity = Number(form.value.finishedNum ?? 0);
+ const unitPrice = Number(form.value.unitPrice ?? 0);
+
+ if (quantity > 0 && unitPrice > 0) {
+ form.value.totalPrice = (quantity * unitPrice).toFixed(2);
+ } else {
+ form.value.totalPrice = '0.00';
+ }
}
// 鎻愪氦浜у搧琛ㄥ崟
const submitForm = () => {
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index 996a00b..b34b14e 100644
--- a/src/views/productionManagement/productionReporting/index.vue
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -1,427 +1,481 @@
<template>
- <div class="app-container">
- <div class="search_form">
- <el-form :model="searchForm" :inline="true">
- <el-form-item label="瀹㈡埛鍚嶇О:">
- <el-input v-model="searchForm.customerName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
- style="width: 200px;"
- @change="handleQuery" />
- </el-form-item>
- <el-form-item label="椤圭洰鍚嶇О:">
- <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
- style="width: 200px;"
- @change="handleQuery" />
- </el-form-item>
- <el-form-item label="鎺掍骇鏃ユ湡:">
- <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
- </el-form-item>
- <el-form-item label="鐘舵��:">
- <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 140px" clearable>
- <el-option label="寰呯敓浜�" :value="1"></el-option>
- <el-option label="宸叉姤宸�" :value="3"></el-option>
- <el-option label="鐢熶骇涓�" :value="2"></el-option>
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
- </el-form-item>
- </el-form>
- </div>
- <div class="table_list">
- <div style="text-align: right" class="mb10">
- <el-button type="primary" @click="openForm('add')">鐢熶骇鎶ュ伐</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- </div>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- :expandRowKeys="expandedRowKeys"
- @expand-change="expandChange"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- :total="page.total"
- >
- <template #expand="{ row }">
- <el-table
- :data="expandData"
- border
- show-summary
- :summary-method="summarizeMainTable"
- v-loading="childrenLoading"
- >
- <el-table-column
- align="center"
- label="搴忓彿"
- type="index"
- width="60"
- />
- <el-table-column label="鏈鐢熶骇鏁伴噺" prop="finishedNum" align="center" width="400">
- <template #default="scope">
- <el-input-number :step="0.01" :min="0" style="width: 100%"
- v-model="scope.row.finishedNum"
- :disabled="!scope.row.editType"
- :precision="2"
- placeholder="璇疯緭鍏�"
- clearable
- @change="changeNum(scope.row)"
- />
- </template>
- </el-table-column>
-<!-- <el-table-column label="寰呯敓浜ф暟閲�" prop="pendingNum" width="240" align="center"></el-table-column>-->
- <el-table-column label="鐢熶骇浜�" prop="schedulingUserId" width="400">
- <template #default="scope">
- <el-select
- v-model="scope.row.schedulingUserId"
- placeholder="閫夋嫨浜哄憳"
- :disabled="!scope.row.editType"
- style="width: 100%;"
- >
- <el-option
- v-for="user in userList"
- :key="user.userId"
- :label="user.nickName"
- :value="user.userId"
- />
- </el-select>
- </template>
- </el-table-column>
- <el-table-column label="鐢熶骇鏃ユ湡" prop="schedulingDate" width="400">
- <template #default="scope">
- <el-date-picker
- v-model="scope.row.schedulingDate"
- type="date"
- :disabled="!scope.row.editType"
- placeholder="璇烽�夋嫨鏃ユ湡"
- value-format="YYYY-MM-DD"
- format="YYYY-MM-DD"
- clearable
- style="width: 100%"
- />
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔" width="60">
- <template #default="scope">
- <el-button
- link
- type="primary"
- size="small"
- @click="changeEditType(scope.row)"
- v-if="!scope.row.editType"
- >缂栬緫</el-button
- >
- <el-button
- link
- type="primary"
- size="small"
- @click="saveReceiptPayment(scope.row)"
- v-if="scope.row.editType"
- >淇濆瓨</el-button
- >
- </template>
- </el-table-column>
- </el-table>
- </template>
- </PIMTable>
- </div>
- <form-dia ref="formDia" @close="handleQuery"></form-dia>
- </div>
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm"
+ :inline="true">
+ <el-form-item label="鎶ュ伐浜哄憳鍚嶇О:">
+ <el-input v-model="searchForm.nickName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="宸ュ崟鍙�:">
+ <el-input v-model="searchForm.workOrderNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="宸ュ崟鐘舵��:">
+ <el-select v-model="searchForm.workOrderStatus"
+ placeholder="璇烽�夋嫨宸ュ崟鐘舵��"
+ style="width: 140px"
+ clearable>
+ <el-option label="寰呯‘璁�"
+ :value="1"></el-option>
+ <el-option label="寰呯敓浜�"
+ :value="2"></el-option>
+ <el-option label="鐢熶骇涓�"
+ :value="3"></el-option>
+ <el-option label="宸茬敓浜�"
+ :value="4"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right"
+ class="mb10">
+ <!-- <el-button type="primary"
+ @click="openForm('add')">鐢熶骇鎶ュ伐</el-button> -->
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </div>
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ :expandRowKeys="expandedRowKeys"
+ @expand-change="expandChange"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total">
+ <template #expand="{ row }">
+ <el-table :data="expandData"
+ border
+ show-summary
+ :summary-method="summarizeMainTable"
+ v-loading="childrenLoading">
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="鏈鐢熶骇鏁伴噺"
+ prop="finishedNum"
+ align="center"
+ width="400">
+ <template #default="scope">
+ <el-input-number :step="0.01"
+ :min="0"
+ style="width: 100%"
+ v-model="scope.row.finishedNum"
+ :disabled="!scope.row.editType"
+ :precision="2"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="changeNum(scope.row)" />
+ </template>
+ </el-table-column>
+ <!-- <el-table-column label="寰呯敓浜ф暟閲�" prop="pendingNum" width="240" align="center"></el-table-column>-->
+ <el-table-column label="鐢熶骇浜�"
+ prop="schedulingUserId"
+ width="400">
+ <template #default="scope">
+ <el-select v-model="scope.row.schedulingUserId"
+ placeholder="閫夋嫨浜哄憳"
+ :disabled="!scope.row.editType"
+ style="width: 100%;">
+ <el-option v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId" />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢熶骇鏃ユ湡"
+ prop="schedulingDate"
+ width="400">
+ <template #default="scope">
+ <el-date-picker v-model="scope.row.schedulingDate"
+ type="date"
+ :disabled="!scope.row.editType"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ clearable
+ style="width: 100%" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔"
+ width="60">
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ size="small"
+ @click="changeEditType(scope.row)"
+ v-if="!scope.row.editType"
+ :disabled="scope.row.parentStatus === 3">缂栬緫</el-button>
+ <el-button link
+ type="primary"
+ size="small"
+ @click="saveReceiptPayment(scope.row)"
+ v-if="scope.row.editType">淇濆瓨</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </PIMTable>
+ </div>
+ <form-dia ref="formDia"
+ @close="handleQuery"></form-dia>
+ <input-modal v-if="isShowInput"
+ v-model:visible="isShowInput"
+ :production-product-main-id="isShowingId" />
+ <output-modal v-if="isShowOutput"
+ v-model:visible="isShowOutput"
+ :production-product-main-id="isShowingId" />
+ </div>
</template>
<script setup>
-import {onMounted, ref} from "vue";
-import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
-import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
-import {ElMessageBox} from "element-plus";
-import dayjs from "dayjs";
-import {
- productionReportUpdate,
- workListPage,
- workListPageById
-} from "@/api/productionManagement/productionReporting.js";
-import {userListNoPageByTenantId} from "@/api/system/user.js";
+ import { onMounted, ref } from "vue";
+ import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
+ import { ElMessageBox } from "element-plus";
+ import {
+ productionReportUpdate,
+ workListPageById,
+ productionReportDelete,
+ } from "@/api/productionManagement/productionReporting.js";
+ import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
+ import { userListNoPageByTenantId } from "@/api/system/user.js";
+ import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
+ import OutputModal from "@/views/productionManagement/productionReporting/Output.vue";
-const data = reactive({
- searchForm: {
- staffName: "",
- entryDate: null, // 褰曞叆鏃ユ湡
- entryDateStart: undefined,
- entryDateEnd: undefined,
- },
-});
-const { searchForm } = toRefs(data);
-const expandedRowKeys = ref([]);
-const expandData = ref([]);
-const userList = ref([])
-const tableColumn = ref([
- {
- type: "expand",
- dataType: "slot",
- slot: "expand",
- },
- {
- label: "鐘舵��",
- prop: "status",
- dataType: "tag",
- formatData: (params) => {
- if (params == 3) {
- return "宸叉姤宸�";
- } else if (params == 1) {
- return "寰呯敓浜�";
- } else {
- return '鐢熶骇涓�';
- }
- },
- formatType: (params) => {
- if (params == 3) {
- return "success";
- } else if (params == 1) {
- return "primary";
- } else {
- return 'warning';
- }
- },
- },
- {
- label: "鎺掍骇鏃ユ湡",
- prop: "schedulingDate",
- width: 120,
- },
- {
- label: "鎺掍骇浜�",
- prop: "schedulingUserName",
- },
- {
- label: "鍚堝悓鍙�",
- prop: "salesContractNo",
- width: 200,
- },
- {
- label: "瀹㈡埛鍚堝悓鍙�",
- prop: "customerContractNo",
- width: 200,
- },
- {
- label: "瀹㈡埛鍚嶇О",
- prop: "customerName",
- width: 200,
- },
- {
- label: "椤圭洰鍚嶇О",
- prop: "projectName",
- width:300
- },
- {
- label: "浜у搧澶х被",
- prop: "productCategory",
- width: 150,
- },
- {
- label: "瑙勬牸鍨嬪彿",
- prop: "specificationModel",
- width: 150,
- },
- {
- label: "鍗曚綅",
- prop: "unit",
- },
- {
- label: "宸ュ簭",
- prop: "process",
- },
- {
- label: "鎺掍骇鏁伴噺",
- prop: "schedulingNum",
- width: 100,
- },
- {
- label: "鐢熶骇鏁伴噺",
- prop: "finishedNum",
- width: 100,
- },
- {
- label: "寰呯敓浜ф暟閲�",
- prop: "pendingFinishNum",
- width: 100,
- },
-]);
-const tableData = ref([]);
-const selectedRows = ref([]);
-const tableLoading = ref(false);
-const childrenLoading = ref(false);
-const page = reactive({
- current: 1,
- size: 100,
- total: 0,
-});
-const formDia = ref()
-const { proxy } = getCurrentInstance()
+ const data = reactive({
+ searchForm: {
+ nickName: "",
+ workOrderNo: "",
+ workOrderStatus: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
+ const expandedRowKeys = ref([]);
+ const expandData = ref([]);
+ const userList = ref([]);
+ const tableColumn = ref([
+ {
+ label: "鎶ュ伐鍗曞彿",
+ prop: "productNo",
+ width: 120,
+ },
+ {
+ label: "鎶ュ伐浜哄憳",
+ prop: "nickName",
+ width: 120,
+ },
+ {
+ label: "宸ュ崟缂栧彿",
+ prop: "workOrderNo",
+ width: 120,
+ },
+ {
+ label: "鎶ュ伐鐘舵��",
+ prop: "status",
+ dataType: "tag",
+ formatData: params => {
+ if (params == 3) {
+ return "宸叉姤宸�";
+ } else if (params == 1) {
+ return "寰呯敓浜�";
+ } else {
+ return "鐢熶骇涓�";
+ }
+ },
+ formatType: params => {
+ if (params == 3) {
+ return "success";
+ } else if (params == 1) {
+ return "primary";
+ } else {
+ return "warning";
+ }
+ },
+ },
+ {
+ label: "宸ュ崟鐘舵��",
+ prop: "workOrderStatus",
+ dataType: "tag",
+ formatData: params => {
+ switch (params) {
+ case "1":
+ return "寰呯‘璁�";
+ case "2":
+ return "寰呯敓浜�";
+ case "3":
+ return "鐢熶骇涓�";
+ case "4":
+ return "宸茬敓浜�";
+ default:
+ return "";
+ }
+ },
+ formatType: params => {
+ switch (params) {
+ case "1":
+ return "primary";
+ case "2":
+ return "info";
+ case "3":
+ return "warning";
+ case "4":
+ return "success";
+ default:
+ return "";
+ }
+ },
+ },
+ {
+ label: "鐢熶骇鏃堕棿",
+ prop: "createTime",
+ width: 120,
+ formatData: params => {
+ const date = new Date(params);
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
+ 2,
+ "0"
+ )}-${String(date.getDate()).padStart(2, "0")}`;
+ },
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 230,
+ operation: [
+ {
+ name: "鏌ョ湅鎶曞叆",
+ type: "text",
+ clickFun: row => {
+ showInput(row);
+ },
+ },
+ {
+ name: "鏌ョ湅浜у嚭",
+ type: "text",
+ clickFun: row => {
+ showOutput(row);
+ },
+ },
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ clickFun: row => {
+ deleteReport(row);
+ },
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const selectedRows = ref([]);
+ const tableLoading = ref(false);
+ const childrenLoading = ref(false);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+ const formDia = ref();
+ const { proxy } = getCurrentInstance();
-// 鏌ヨ鍒楄〃
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-const handleQuery = () => {
- page.current = 1;
- getList();
-};
-const changeDaterange = (value) => {
- if (value) {
- searchForm.value.entryDateStart = value[0];
- searchForm.value.entryDateEnd = value[1];
- } else {
- searchForm.value.entryDateStart = undefined;
- searchForm.value.entryDateEnd = undefined;
- }
- handleQuery();
-};
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-const getList = () => {
- tableLoading.value = true;
- const params = { ...searchForm.value, ...page };
- params.entryDate = undefined
- expandedRowKeys.value = []
- workListPage(params).then(res => {
- tableLoading.value = false;
- tableData.value = res.data.records.map(item => ({
- ...item,
- pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
- }));
- page.total = res.data.total;
- }).catch(err => {
- tableLoading.value = false;
- })
-};
-// 灞曞紑琛�
-const expandChange = (row, expandedRows) => {
- userListNoPageByTenantId().then((res) => {
- userList.value = res.data;
- });
- if (expandedRows.length > 0) {
- nextTick(() => {
- expandedRowKeys.value = [];
- try {
- childrenLoading.value = true;
- workListPageById({ id: row.id }).then((res) => {
- childrenLoading.value = false;
- const index = tableData.value.findIndex((item) => item.id === row.id);
- if (index > -1) {
- expandData.value = res.data.map(item => ({
- ...item,
- pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
- parentStatus: row.status // 鏂板鐖惰〃鐘舵��
- }));
- }
- expandedRowKeys.value.push(row.id);
- });
- } catch (error) {
- childrenLoading.value = false;
- console.log(error);
- }
- })
- } else {
- expandedRowKeys.value = [];
- }
-};
-const changeNum = (row) => {
- // 鎵惧埌鐖惰〃鏍兼暟鎹�
- const parentRow = tableData.value.find(item => item.id === expandedRowKeys.value[0]);
- // 璁$畻鎵�鏈夊瓙琛ㄦ牸 finishedNum 鐨勬�诲拰
- const totalFinishedNum = expandData.value.reduce((sum, item) => sum + (Number(item.finishedNum) || 0), 0);
- // 鐖惰〃鏍肩殑鎺掍骇鏁伴噺
- const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
-
- if (totalFinishedNum > schedulingNum) {
- // 鍥為��鏈杈撳叆
- row.finishedNum = schedulingNum - (totalFinishedNum - Number(row.finishedNum));
- proxy.$modal.msgWarning('鎵�鏈夋湰娆$敓浜ф暟閲忎箣鍜屼笉鍙ぇ浜庢帓浜ф暟閲�');
- }
- row.pendingNum = row.schedulingNum - row.finishedNum;
-}
-// 缂栬緫淇敼鐘舵��
-const changeEditType = (row) => {
- row.editType = !row.editType;
-};
-// 淇濆瓨璁板綍
-const saveReceiptPayment = (row) => {
- productionReportUpdate(row).then((res) => {
- row.editType = !row.editType;
- getList();
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- });
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-const summarizeMainTable = (param) => {
- return proxy.summarizeTable(param, [
- "finishedNum"
- ]);
-};
-// 鎵撳紑寮规
-const openForm = (type, row) => {
- if (selectedRows.value.length !== 1) {
- proxy.$message.error("璇烽�夋嫨涓�鏉℃暟鎹�");
- return;
- }
- if (selectedRows.value[0].pendingFinishNum == 0) {
- proxy.$message.warning("鏃犻渶鍐嶆姤宸�");
- return;
- }
- nextTick(() => {
- const rowInfo = type === 'add' ? selectedRows.value[0] : row
- formDia.value?.openDialog(type, rowInfo)
- })
-};
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+ const changeDaterange = value => {
+ if (value) {
+ searchForm.value.entryDateStart = value[0];
+ searchForm.value.entryDateEnd = value[1];
+ } else {
+ searchForm.value.entryDateStart = undefined;
+ searchForm.value.entryDateEnd = undefined;
+ }
+ handleQuery();
+ };
+ const deleteReport = row => {
+ ElMessageBox.confirm("纭畾鍒犻櫎璇ユ姤宸ュ悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ productionReportDelete({ id: row.id }).then(res => {
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ } else {
+ ElMessageBox.alert(res.msg || "鍒犻櫎澶辫触", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ }
+ });
+ });
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined;
+ expandedRowKeys.value = [];
+ productionProductMainListPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ pendingFinishNum:
+ (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
+ }));
+ page.total = res.data.total;
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ });
+ };
+ // 灞曞紑琛�
+ const expandChange = (row, expandedRows) => {
+ userListNoPageByTenantId().then(res => {
+ userList.value = res.data;
+ });
+ if (expandedRows.length > 0) {
+ nextTick(() => {
+ expandedRowKeys.value = [];
+ try {
+ childrenLoading.value = true;
+ workListPageById({ id: row.id }).then(res => {
+ childrenLoading.value = false;
+ const index = tableData.value.findIndex(item => item.id === row.id);
+ if (index > -1) {
+ expandData.value = res.data.map(item => ({
+ ...item,
+ pendingNum:
+ (Number(item.schedulingNum) || 0) -
+ (Number(item.finishedNum) || 0),
+ parentStatus: row.status, // 鏂板鐖惰〃鐘舵��
+ }));
+ }
+ expandedRowKeys.value.push(row.id);
+ });
+ } catch (error) {
+ childrenLoading.value = false;
+ console.log(error);
+ }
+ });
+ } else {
+ expandedRowKeys.value = [];
+ }
+ };
+ const changeNum = row => {
+ // 鎵惧埌鐖惰〃鏍兼暟鎹�
+ const parentRow = tableData.value.find(
+ item => item.id === expandedRowKeys.value[0]
+ );
+ // 璁$畻鎵�鏈夊瓙琛ㄦ牸 finishedNum 鐨勬�诲拰
+ const totalFinishedNum = expandData.value.reduce(
+ (sum, item) => sum + (Number(item.finishedNum) || 0),
+ 0
+ );
+ // 鐖惰〃鏍肩殑鎺掍骇鏁伴噺
+ const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
-// 鍒犻櫎
-const handleDelete = () => {
- let ids = [];
- if (selectedRows.value.length > 0) {
- ids = selectedRows.value.map((item) => item.id);
- } else {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- staffJoinDel(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/salesLedger/work/export", {}, "鐢熶骇鎶ュ伐.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-onMounted(() => {
- getList();
-});
+ if (totalFinishedNum > schedulingNum) {
+ // 鍥為��鏈杈撳叆
+ row.finishedNum =
+ schedulingNum - (totalFinishedNum - Number(row.finishedNum));
+ proxy.$modal.msgWarning("鎵�鏈夋湰娆$敓浜ф暟閲忎箣鍜屼笉鍙ぇ浜庢帓浜ф暟閲�");
+ }
+ row.pendingNum = row.schedulingNum - row.finishedNum;
+ };
+ // 缂栬緫淇敼鐘舵��
+ const changeEditType = row => {
+ row.editType = !row.editType;
+ };
+ // 淇濆瓨璁板綍
+ const saveReceiptPayment = row => {
+ productionReportUpdate(row).then(res => {
+ row.editType = !row.editType;
+ getList();
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ });
+ };
+ // 琛ㄦ牸閫夋嫨鏁版嵁
+ const handleSelectionChange = selection => {
+ selectedRows.value = selection;
+ };
+ const summarizeMainTable = param => {
+ return proxy.summarizeTable(param, ["finishedNum"]);
+ };
+ // 鎵撳紑寮规
+ const openForm = (type, row) => {
+ if (selectedRows.value.length !== 1) {
+ proxy.$message.error("璇烽�夋嫨涓�鏉℃暟鎹�");
+ return;
+ }
+ if (selectedRows.value[0].pendingFinishNum == 0) {
+ proxy.$message.warning("鏃犻渶鍐嶆姤宸�");
+ return;
+ }
+ nextTick(() => {
+ const rowInfo = type === "add" ? selectedRows.value[0] : row;
+ formDia.value?.openDialog(type, rowInfo);
+ });
+ };
+
+ // 鎵撳紑鎶曞叆妯℃�佹
+ const isShowInput = ref(false);
+ const isShowingId = ref(0);
+ const showInput = row => {
+ isShowInput.value = true;
+ isShowingId.value = row.id;
+ };
+
+ // 鎵撳紑浜у嚭妯℃�佹
+ const isShowOutput = ref(false);
+ const showOutput = row => {
+ isShowOutput.value = true;
+ isShowingId.value = row.id;
+ };
+
+ // 瀵煎嚭
+ const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ proxy.download("/salesLedger/work/export", {}, "鐢熶骇鎶ュ伐.xlsx");
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+ };
+ onMounted(() => {
+ getList();
+ });
</script>
<style scoped></style>
diff --git a/src/views/productionManagement/workOrder/index.vue b/src/views/productionManagement/workOrder/index.vue
new file mode 100644
index 0000000..34b368d
--- /dev/null
+++ b/src/views/productionManagement/workOrder/index.vue
@@ -0,0 +1,587 @@
+<template>
+ <div class="app-container">
+ <div class="search_form">
+ <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">
+ <el-button type="primary"
+ @click="handleQuery">鎼滅储</el-button>
+ </div>
+ </div>
+ </div>
+ <div class="table_list">
+ <PIMTable rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :tableLoading="tableLoading"
+ @pagination="pagination"></PIMTable>
+ </div>
+ <el-dialog v-model="editDialogVisible"
+ title="缂栬緫鏃堕棿"
+ width="500px">
+ <el-form :model="editrow"
+ label-width="120px">
+ <el-form-item label="璁″垝寮�濮嬫椂闂�">
+ <el-date-picker v-model="editrow.planStartTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="璁″垝缁撴潫鏃堕棿">
+ <el-date-picker v-model="editrow.planEndTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="瀹為檯寮�濮嬫椂闂�">
+ <el-date-picker v-model="editrow.actualStartTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="瀹為檯缁撴潫鏃堕棿">
+ <el-date-picker v-model="editrow.actualEndTime"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ value-format="YYYY-MM-DD"
+ style="width: 300px" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ @click="handleUpdate">纭畾</el-button>
+ <el-button @click="editDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="transferCardVisible"
+ title="娴佽浆鍗�"
+ width="1000px">
+ <div class="transfer-card-title">宸ュ崟娴佽浆鍗�</div>
+ <div class="transfer-card-container">
+ <div class="transfer-card-info">
+ <div class="info-group">
+ <div class="info-item">
+ <span class="info-label">宸ュ崟缂栧彿</span>
+ <span class="info-value">{{ transferCardRowData.workOrderNo }}</span>
+ </div>
+ <!-- <div class="info-item">
+ <span class="info-label">浜у搧缂栧彿</span>
+ <span class="info-value">{{ transferCardRowData.productNo }}</span>
+ </div> -->
+ <div class="info-item">
+ <span class="info-label">浜у搧鍚嶇О</span>
+ <span class="info-value">{{ transferCardRowData.productName }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">浜у搧瑙勬牸</span>
+ <span class="info-value">{{ transferCardRowData.model }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">宸ュ崟鐘舵��</span>
+ <span class="info-value">{{
+ transferCardRowData.status === 1 ? '寰呯‘璁�' :
+ transferCardRowData.status === 2 ? '寰呯敓浜�' :
+ transferCardRowData.status === 3 ? '鐢熶骇涓�' :
+ transferCardRowData.status === 4 ? '宸茬敓浜�' :
+ transferCardRowData.status
+ }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">璁″垝寮�濮嬫椂闂�</span>
+ <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">璁″垝缁撴潫鏃堕棿</span>
+ <span class="info-value">{{ transferCardRowData.planEndTime }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">澶囨敞</span>
+ <span class="info-value">{{ transferCardRowData.remark }}</span>
+ </div>
+ </div>
+ <div class="info-group">
+ <div class="info-item">
+ <span class="info-label"> </span>
+ <span class="info-value"> </span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">璁″垝鏁伴噺</span>
+ <span class="info-value">{{ transferCardRowData.planQuantity }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">鑹搧鏁伴噺</span>
+ <span class="info-value">0</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">涓嶈壇鍝佹暟</span>
+ <span class="info-value">0</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">瀹為檯寮�濮嬫椂闂�</span>
+ <span class="info-value">{{ transferCardRowData.actualStartTime }}</span>
+ </div>
+ <div class="info-item">
+ <span class="info-label">瀹為檯缁撴潫鏃堕棿</span>
+ <span class="info-value">{{ transferCardRowData.actualEndTime }}</span>
+ </div>
+ </div>
+ </div>
+ <div class="transfer-card-qr">
+ <div class="qr-container">
+ <img :src="transferCardQrUrl"
+ alt="娴佽浆鍗′簩缁寸爜"
+ style="width: 200px; height: 200px;" />
+ <!-- <div class="qr-tip"
+ style="margin-top: 10px; text-align: center;">娴佽浆鍗′簩缁寸爜</div> -->
+ </div>
+ </div>
+ </div>
+ <div class="print-button-container"
+ style=" text-align: center;
+ margin-bottom: 40px;">
+ <el-button type="primary"
+ style="margin-top: 20px;"
+ @click="printTransferCard">鎵撳嵃娴佽浆鍗�</el-button>
+ </div>
+ </el-dialog>
+ <el-dialog v-model="reportDialogVisible"
+ title="鎶ュ伐"
+ width="500px">
+ <el-form :model="reportForm"
+ label-width="120px">
+ <el-form-item label="寰呯敓浜ф暟閲�">
+ <el-input v-model="reportForm.planQuantity"
+ readonly
+ style="width: 300px" />
+ </el-form-item>
+ <el-form-item label="鏈鐢熶骇鏁伴噺">
+ <el-input v-model.number="reportForm.quantity"
+ type="number"
+ min="1"
+ style="width: 300px"
+ placeholder="璇疯緭鍏ユ湰娆$敓浜ф暟閲�" />
+ </el-form-item>
+ <el-form-item label="鐝粍淇℃伅">
+ <el-input v-model="reportForm.userName"
+ style="width: 300px"
+ readonly
+ placeholder="璇疯緭鍏ョ彮缁勪俊鎭�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary"
+ @click="handleReport">纭畾</el-button>
+ <el-button @click="reportDialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import { onMounted, ref } from "vue";
+ import { ElMessageBox } from "element-plus";
+ import dayjs from "dayjs";
+ import {
+ productWorkOrderPage,
+ updateProductWorkOrder,
+ addProductMain,
+ } from "@/api/productionManagement/workOrder.js";
+ import { getUserProfile } from "@/api/system/user.js";
+ import QRCode from "qrcode";
+ import { getCurrentInstance, reactive, toRefs } from "vue";
+ const { proxy } = getCurrentInstance();
+
+ const tableColumn = ref([
+ {
+ label: "宸ュ崟缂栧彿",
+ prop: "workOrderNo",
+ width: "140",
+ },
+ {
+ label: "鐢熶骇璁㈠崟鍙�",
+ prop: "productOrderNpsNo",
+ width: "140",
+ },
+ {
+ label: "浜у搧鍚嶇О",
+ prop: "productName",
+ width: "140",
+ },
+ {
+ label: "瑙勬牸",
+ prop: "model",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "宸ュ簭鍚嶇О",
+ prop: "processName",
+ },
+ {
+ label: "寰呯敓浜ф暟閲�",
+ prop: "planQuantity",
+ width: "140",
+ },
+ {
+ label: "璁″垝鐢熶骇鏁伴噺",
+ prop: "quantity",
+ width: "140",
+ },
+ {
+ label: "璁″垝寮�濮嬫椂闂�",
+ prop: "planStartTime",
+ width: "140",
+ },
+ {
+ label: "璁″垝缁撴潫鏃堕棿",
+ prop: "planEndTime",
+ width: "140",
+ },
+ {
+ label: "瀹為檯寮�濮嬫椂闂�",
+ prop: "actualStartTime",
+ width: "140",
+ },
+ {
+ label: "瀹為檯缁撴潫鏃堕棿",
+ prop: "actualEndTime",
+ width: "140",
+ },
+ {
+ label: "鎿嶄綔",
+ width: "200",
+ align: "center",
+ dataType: "action",
+ fixed: "right",
+ operation: [
+ {
+ name: "缂栬緫",
+ clickFun: row => {
+ handleEdit(row);
+ },
+ },
+ {
+ name: "娴佽浆鍗�",
+ clickFun: row => {
+ showTransferCard(row);
+ },
+ },
+ {
+ name: "鎶ュ伐",
+ clickFun: row => {
+ showReportDialog(row);
+ },
+ disabled: row => row.planQuantity <= 0,
+ },
+ ],
+ },
+ ]);
+ const tableData = ref([]);
+ const tableLoading = ref(false);
+ const qrCodeUrl = ref("");
+ const qrRowData = ref(null);
+ const editDialogVisible = ref(false);
+ const transferCardVisible = ref(false);
+ const transferCardData = ref([]);
+ const transferCardQrUrl = ref("");
+ const transferCardRowData = ref(null);
+ const reportDialogVisible = ref(false);
+ const reportForm = reactive({
+ planQuantity: 0,
+ quantity: 0,
+ userName: "",
+ workOrderId: "",
+ reportWork: "",
+ productProcessRouteItemId: "",
+ userId: "",
+ productMainId: null,
+ });
+ const currentReportRowData = ref(null);
+ const page = reactive({
+ current: 1,
+ size: 100,
+ total: 0,
+ });
+
+ const data = reactive({
+ searchForm: {
+ workOrderNo: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
+ let editrow = ref(null);
+
+ // 鏌ヨ鍒楄〃
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ const handleQuery = () => {
+ page.current = 1;
+ getList();
+ };
+ const pagination = obj => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
+ };
+ const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ productWorkOrderPage(params)
+ .then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ })
+ .catch(() => {
+ tableLoading.value = false;
+ });
+ };
+
+ const showTransferCard = async row => {
+ transferCardRowData.value = row;
+ const qrContent =
+ proxy.javaApi + "/work-order?orderRow=" + JSON.stringify(row);
+ console.log(qrContent, "qrContent");
+
+ transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
+ transferCardVisible.value = true;
+ };
+
+ const printTransferCard = () => {
+ window.print();
+ };
+
+ const handleEdit = row => {
+ editrow.value = JSON.parse(JSON.stringify(row));
+ editDialogVisible.value = true;
+ };
+
+ const handleUpdate = () => {
+ updateProductWorkOrder(editrow.value)
+ .then(res => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ editDialogVisible.value = false;
+ getList();
+ })
+ .catch(() => {
+ ElMessageBox.alert("淇敼澶辫触", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ });
+ };
+
+ const showReportDialog = row => {
+ currentReportRowData.value = row;
+ reportForm.planQuantity = row.planQuantity;
+ reportForm.quantity = row.quantity;
+ reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
+ reportForm.workOrderId = row.id;
+ reportForm.reportWork = row.reportWork;
+ reportForm.productMainId = row.productMainId;
+ // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅
+
+ reportDialogVisible.value = true;
+ };
+
+ const handleReport = () => {
+ if (reportForm.planQuantity <= 0) {
+ ElMessageBox.alert("寰呯敓浜ф暟閲忎负0锛屾棤娉曟姤宸�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ return;
+ }
+ if (!reportForm.quantity || reportForm.quantity <= 0) {
+ ElMessageBox.alert("璇疯緭鍏ユ湁鏁堢殑鏈鐢熶骇鏁伴噺", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ return;
+ }
+ if (reportForm.quantity > reportForm.planQuantity) {
+ ElMessageBox.alert("鏈鐢熶骇鏁伴噺涓嶈兘瓒呰繃寰呯敓浜ф暟閲�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ return;
+ }
+ // console.log(reportForm);
+ addProductMain(reportForm).then(res => {
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鎶ュ伐鎴愬姛");
+ reportDialogVisible.value = false;
+ getList();
+ } else {
+ ElMessageBox.alert(res.msg || "鎶ュ伐澶辫触", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ });
+ }
+ });
+ };
+
+ onMounted(() => {
+ getList();
+ getUserProfile()
+ .then(res => {
+ if (res.code === 200) {
+ reportForm.userName = res.data.userName;
+ reportForm.userId = res.data.userId;
+ }
+ })
+ .catch(err => {
+ console.error("鑾峰彇鐢ㄦ埛淇℃伅澶辫触", err);
+ });
+ });
+</script>
+
+<style scoped lang="scss">
+ .search_form {
+ margin-bottom: 20px;
+ .search-row {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+ .search-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+ }
+ }
+
+ .transfer-card-title {
+ font-size: 24px;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 20px;
+ }
+
+ .transfer-card-container {
+ display: flex;
+ gap: 20px;
+ height: 350px;
+ .transfer-card-info {
+ flex: 1;
+ overflow: auto;
+ .info-group {
+ width: 50%;
+ float: left;
+ }
+ .info-item {
+ display: flex;
+ margin-bottom: 15px;
+ .info-label {
+ width: 120px;
+ font-weight: bold;
+ margin-right: 20px;
+ }
+ .info-value {
+ flex: 1;
+ }
+ }
+ }
+ .transfer-card-qr {
+ width: 240px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ }
+ }
+</style>
+
+<style lang="scss">
+ @media print {
+ @page {
+ size: landscape;
+ }
+ body * {
+ visibility: hidden;
+ }
+ .el-dialog__wrapper,
+ .el-dialog,
+ .el-dialog__body,
+ .transfer-card-title,
+ .transfer-card-container,
+ .transfer-card-container *,
+ .info-item,
+ .info-label,
+ .info-value {
+ visibility: visible;
+ }
+ .print-button-container {
+ visibility: hidden;
+ }
+ .el-dialog__wrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ margin: 0;
+ }
+ .el-dialog {
+ width: 100% !important;
+ max-width: 800px;
+ margin: 0 auto !important;
+ }
+ .el-dialog__header,
+ .el-dialog__footer {
+ display: none;
+ }
+ .el-dialog__body {
+ padding: 20px;
+ }
+ .transfer-card-container {
+ height: auto;
+ display: flex;
+ gap: 20px;
+ }
+ .transfer-card-info {
+ flex: 1;
+ .info-group {
+ width: 100%;
+ float: none;
+ margin-bottom: 20px;
+ }
+ .info-item {
+ display: flex;
+ margin-bottom: 10px;
+ .info-label {
+ width: 100px;
+ font-weight: bold;
+ margin-right: 15px;
+ white-space: nowrap;
+ }
+ .info-value {
+ flex: 1;
+ word-break: break-word;
+ }
+ }
+ }
+ .transfer-card-qr {
+ width: 160px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ }
+ .qr-container img {
+ width: 140px !important;
+ height: 140px !important;
+ }
+ }
+</style>
diff --git a/src/views/qualityManagement/metricBinding/index.vue b/src/views/qualityManagement/metricBinding/index.vue
new file mode 100644
index 0000000..80eb66f
--- /dev/null
+++ b/src/views/qualityManagement/metricBinding/index.vue
@@ -0,0 +1,432 @@
+<template>
+ <div class="app-container metric-binding">
+ <!-- 宸︿晶锛氭娴嬫爣鍑嗗垪琛紙鍙锛� -->
+ <div class="left-panel">
+ <PIMTable
+ rowKey="id"
+ :column="standardColumns"
+ :tableData="standardTableData"
+ :page="page"
+ :isSelection="false"
+ :rowClassName="rowClassNameCenter"
+ :tableLoading="tableLoading"
+ @pagination="handlePagination"
+ :total="page.total"
+ >
+ <template #standardNoCell="{ row }">
+ <span class="clickable-link" @click="handleStandardRowClick(row)">
+ {{ row.standardNo }}
+ </span>
+ </template>
+
+ <!-- 琛ㄥご鎼滅储 -->
+ <template #standardNoHeader>
+ <el-input
+ v-model="searchForm.standardNo"
+ placeholder="鏍囧噯缂栧彿"
+ clearable
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #standardNameHeader>
+ <el-input
+ v-model="searchForm.standardName"
+ placeholder="鏍囧噯鍚嶇О"
+ clearable
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #inspectTypeHeader>
+ <el-select
+ v-model="searchForm.inspectType"
+ placeholder="绫诲埆"
+ clearable
+ size="small"
+ style="width: 120px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鍘熸潗鏂欐楠�" value="0" />
+ <el-option label="杩囩▼妫�楠�" value="1" />
+ <el-option label="鍑哄巶妫�楠�" value="2" />
+ </el-select>
+ </template>
+ <template #stateHeader>
+ <el-select
+ v-model="searchForm.state"
+ placeholder="鐘舵��"
+ clearable
+ size="small"
+ style="width: 110px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鑽夌" value="0" />
+ <el-option label="閫氳繃" value="1" />
+ <el-option label="鎾ら攢" value="2" />
+ </el-select>
+ </template>
+ </PIMTable>
+ </div>
+
+ <!-- 鍙充晶锛氱粦瀹氬垪琛� -->
+ <div class="right-panel">
+ <div class="right-header">
+ <div class="title">缁戝畾鍏崇郴</div>
+ <div class="desc" v-if="currentStandard">
+ 褰撳墠妫�娴嬫爣鍑嗙紪鍙凤細<span class="link-text">{{ currentStandard.standardNo }}</span>
+ </div>
+ <div class="desc" v-else>璇烽�夋嫨宸︿晶妫�娴嬫爣鍑�</div>
+ </div>
+
+ <div class="right-toolbar">
+ <el-button type="primary" :disabled="!currentStandard" @click="openBindingDialog">娣诲姞缁戝畾</el-button>
+ <el-button type="danger" plain :disabled="!currentStandard" @click="handleBatchUnbind">鍒犻櫎</el-button>
+ </div>
+
+ <el-table
+ v-loading="bindingLoading"
+ :data="bindingTableData"
+ border
+ :row-class-name="() => 'row-center'"
+ class="center-table"
+ style="width: 100%"
+ height="calc(100vh - 220px)"
+ @selection-change="handleBindingSelectionChange"
+ >
+ <el-table-column type="selection" width="48" align="center" />
+ <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="140" />
+ <el-table-column label="鎿嶄綔" width="120" fixed="right" align="center">
+ <template #default="{ row }">
+ <el-button link type="danger" size="small" @click="handleUnbind(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 娣诲姞缁戝畾寮规 -->
+ <el-dialog
+ v-model="bindingDialogVisible"
+ title="娣诲姞缁戝畾"
+ width="520px"
+ @close="closeBindingDialog"
+ >
+ <el-form label-width="100px">
+ <el-form-item label="浜у搧">
+ <el-tree-select
+ v-model="selectedProductIds"
+ multiple
+ collapse-tags
+ collapse-tags-tooltip
+ placeholder="璇烽�夋嫨浜у搧锛堝彲澶氶�夛級"
+ clearable
+ check-strictly
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="closeBindingDialog">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitBinding">纭畾</el-button>
+ </span>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+import { ElMessageBox } from 'element-plus'
+import PIMTable from '@/components/PIMTable/PIMTable.vue'
+import { productTreeList } from '@/api/basicData/product.js'
+import {
+ qualityTestStandardListPage
+} from '@/api/qualityManagement/metricMaintenance.js'
+import {
+ qualityTestStandardBindingList,
+ qualityTestStandardBindingAdd,
+ qualityTestStandardBindingDel
+} from '@/api/qualityManagement/qualityTestStandardBinding.js'
+
+const { proxy } = getCurrentInstance()
+
+// 宸︿晶鏍囧噯鍒楄〃锛氭暣琛屽唴瀹瑰眳涓紙閰嶅悎鏍峰紡锛�
+const rowClassNameCenter = () => 'row-center'
+
+const data = reactive({
+ searchForm: {
+ standardNo: '',
+ standardName: '',
+ state: '',
+ inspectType: ''
+ }
+})
+const { searchForm } = toRefs(data)
+
+// 宸︿晶
+const standardTableData = ref([])
+const tableLoading = ref(false)
+const page = reactive({ current: 1, size: 10, total: 0 })
+
+const standardColumns = ref([
+ { label: '鏍囧噯缂栧彿', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, headerSlot: 'standardNoHeader' },
+ { label: '鏍囧噯鍚嶇О', prop: 'standardName', minWidth: 180, headerSlot: 'standardNameHeader' },
+ {
+ label: '妫�娴嬬被鍨�',
+ prop: 'inspectType',
+ headerSlot: 'inspectTypeHeader',
+ dataType: 'tag',
+ formatData: (val) => {
+ const map = { 0: '鍘熸潗鏂欐楠�', 1: '杩囩▼妫�楠�', 2: '鍑哄巶妫�楠�' }
+ return map[val] || val
+ }
+ },
+ // {
+ // label: '鐘舵��',
+ // prop: 'state',
+ // headerSlot: 'stateHeader',
+ // dataType: 'tag',
+ // formatData: (val) => {
+ // const map = { 0: '鑽夌', 1: '閫氳繃', 2: '鎾ら攢' }
+ // return map[val] || val
+ // },
+ // formatType: (val) => {
+ // if (val == 1) return 'success'
+ // if (val == 2) return 'warning'
+ // return 'info'
+ // }
+ // }
+])
+
+const currentStandard = ref(null)
+
+// 鍙充晶缁戝畾
+const bindingTableData = ref([])
+const bindingLoading = ref(false)
+const bindingSelectedRows = ref([])
+const bindingDialogVisible = ref(false)
+
+// 浜у搧鏍戯紙鐢ㄤ簬缁戝畾閫夋嫨锛�
+const productOptions = ref([])
+const selectedProductIds = ref([])
+
+const getProductOptions = async () => {
+ // 閬垮厤閲嶅璇锋眰
+ if (productOptions.value?.length) return
+ const res = await productTreeList()
+ productOptions.value = convertIdToValue(Array.isArray(res) ? res : [])
+}
+
+function convertIdToValue(data) {
+ return (data || []).map((item) => {
+ const { id, children, ...rest } = item
+ const newItem = {
+ ...rest,
+ value: id
+ }
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children)
+ }
+ return newItem
+ })
+}
+
+const handleQuery = () => {
+ page.current = 1
+ getStandardList()
+}
+
+const handlePagination = (obj) => {
+ page.current = obj.page
+ page.size = obj.limit
+ getStandardList()
+}
+
+const getStandardList = () => {
+ tableLoading.value = true
+ qualityTestStandardListPage({
+ ...searchForm.value,
+ current: page.current,
+ size: page.size,
+ state: 1
+ })
+ .then((res) => {
+ const records = res?.data?.records || []
+ standardTableData.value = records
+ page.total = res?.data?.total || records.length
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+}
+
+const handleStandardRowClick = (row) => {
+ currentStandard.value = row
+ loadBindingList()
+}
+
+const loadBindingList = () => {
+ if (!currentStandard.value?.id) {
+ bindingTableData.value = []
+ return
+ }
+ bindingLoading.value = true
+ qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
+ .then((res) => {
+ bindingTableData.value = res?.data || []
+ })
+ .finally(() => {
+ bindingLoading.value = false
+ })
+}
+
+const handleBindingSelectionChange = (selection) => {
+ bindingSelectedRows.value = selection
+}
+
+const openBindingDialog = () => {
+ if (!currentStandard.value?.id) return
+ selectedProductIds.value = []
+ getProductOptions()
+ bindingDialogVisible.value = true
+}
+
+const closeBindingDialog = () => {
+ bindingDialogVisible.value = false
+}
+
+const submitBinding = async () => {
+ const testStandardId = currentStandard.value?.id
+ if (!testStandardId) return
+ const ids = (selectedProductIds.value || []).filter(Boolean)
+ if (!ids.length) {
+ proxy.$message.warning('璇烽�夋嫨浜у搧')
+ return
+ }
+ const payload = ids.map((pid) => ({
+ productId: pid,
+ testStandardId
+ }))
+ await qualityTestStandardBindingAdd(payload)
+ proxy.$message.success('娣诲姞鎴愬姛')
+ bindingDialogVisible.value = false
+ loadBindingList()
+}
+
+const handleUnbind = async (row) => {
+ if (!row?.id) return
+ try {
+ await ElMessageBox.confirm('纭鍒犻櫎璇ョ粦瀹氾紵', '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardBindingDel([row.qualityTestStandardBindingId])
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadBindingList()
+}
+
+const handleBatchUnbind = async () => {
+ if (!bindingSelectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const ids = bindingSelectedRows.value.map((i) => i.qualityTestStandardBindingId)
+ try {
+ await ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardBindingDel(ids)
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadBindingList()
+}
+
+onMounted(() => {
+ getStandardList()
+})
+</script>
+
+<style scoped>
+.metric-binding {
+ display: flex;
+ gap: 16px;
+}
+
+.left-panel,
+.right-panel {
+ flex: 1;
+ background: #ffffff;
+ padding: 16px;
+ box-sizing: border-box;
+}
+
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.toolbar-right {
+ flex-shrink: 0;
+}
+
+.right-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.right-header .title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.right-header .desc {
+ font-size: 13px;
+ color: #666;
+}
+
+.right-toolbar {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.link-text {
+ color: #409eff;
+ cursor: default;
+}
+
+.clickable-link {
+ color: #409eff;
+ cursor: pointer;
+}
+
+.clickable-link:hover {
+ text-decoration: underline;
+}
+
+:deep(.row-center td) {
+ text-align: center !important;
+}
+
+/* el-table 琛ㄥご/鍐呭缁熶竴灞呬腑锛坮ow-class-name 涓嶄綔鐢ㄤ簬琛ㄥご锛� */
+:deep(.center-table .el-table__header-wrapper th .cell) {
+ text-align: center !important;
+}
+:deep(.center-table .el-table__body-wrapper td .cell) {
+ text-align: center !important;
+}
+</style>
diff --git a/src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue b/src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue
new file mode 100644
index 0000000..4c958a0
--- /dev/null
+++ b/src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue
@@ -0,0 +1,78 @@
+<template>
+ <FormDialog
+ v-model="dialogVisible"
+ :title="computedTitle"
+ :operation-type="operationType"
+ width="520px"
+ @close="emit('close')"
+ @cancel="handleCancel"
+ @confirm="handleConfirm"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="100px"
+ >
+ <el-form-item label="鍙傛暟椤�" prop="parameterItem">
+ <el-input v-model="form.parameterItem" placeholder="璇疯緭鍏ュ弬鏁伴」" />
+ </el-form-item>
+ <el-form-item label="鍗曚綅" prop="unit">
+ <el-input v-model="form.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍊�" prop="standardValue">
+ <el-input v-model="form.standardValue" placeholder="璇疯緭鍏ユ爣鍑嗗��" />
+ </el-form-item>
+ <el-form-item label="鍐呮帶鍊�" prop="controlValue">
+ <el-input v-model="form.controlValue" placeholder="璇疯緭鍏ュ唴鎺у��" />
+ </el-form-item>
+ <el-form-item label="榛樿鍊�" prop="defaultValue">
+ <el-input v-model="form.defaultValue" placeholder="璇疯緭鍏ラ粯璁ゅ��" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+
+const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ operationType: { type: String, default: 'add' }, // add | edit
+ form: { type: Object, required: true }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'cancel', 'confirm'])
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit('update:modelValue', val)
+})
+
+const formRef = ref(null)
+
+const rules = {
+ parameterItem: [{ required: true, message: '璇疯緭鍏ュ弬鏁伴」', trigger: 'blur' }],
+ unit: [{ required: true, message: '璇疯緭鍏ュ崟浣�', trigger: 'blur' }]
+}
+
+const computedTitle = computed(() => (props.operationType === 'edit' ? '缂栬緫鏍囧噯鍙傛暟' : '鏂板鏍囧噯鍙傛暟'))
+
+const handleConfirm = () => {
+ formRef.value?.validate?.((valid) => {
+ if (valid) emit('confirm')
+ })
+}
+
+const handleCancel = () => {
+ emit('cancel')
+ dialogVisible.value = false
+}
+
+const resetFields = () => {
+ formRef.value?.resetFields?.()
+}
+
+defineExpose({ resetFields })
+</script>
diff --git a/src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue b/src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
new file mode 100644
index 0000000..38d535a
--- /dev/null
+++ b/src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
@@ -0,0 +1,129 @@
+<template>
+ <FormDialog
+ v-model="dialogVisible"
+ :title="computedTitle"
+ :operation-type="operationType"
+ :width="width"
+ @close="emit('close')"
+ @cancel="handleCancel"
+ @confirm="handleConfirm"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="100px"
+ >
+ <el-form-item label="鏍囧噯缂栧彿" prop="standardNo">
+ <el-input v-model="form.standardNo" placeholder="璇疯緭鍏ユ爣鍑嗙紪鍙�" />
+ </el-form-item>
+ <el-form-item label="鏍囧噯鍚嶇О" prop="standardName">
+ <el-input v-model="form.standardName" placeholder="璇疯緭鍏ユ爣鍑嗗悕绉�" />
+ </el-form-item>
+ <el-form-item label="绫诲埆" prop="inspectType">
+ <el-select v-model="form.inspectType" placeholder="璇烽�夋嫨绫诲埆" style="width: 100%">
+ <el-option label="鍘熸潗鏂欐楠�" value="0" />
+ <el-option label="杩囩▼妫�楠�" value="1" />
+ <el-option label="鍑哄巶妫�楠�" value="2" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="宸ュ簭" prop="processId">
+ <el-select v-model="form.processId" placeholder="璇烽�夋嫨宸ュ簭" style="width: 100%">
+ <el-option
+ v-for="item in processOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="state">
+ <el-select v-model="form.state" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%">
+ <el-option label="鑽夌" value="0" />
+ <el-option label="閫氳繃" value="1" />
+ <el-option label="鎾ら攢" value="2" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ v-model="form.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉�"
+ />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+
+const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ operationType: {
+ type: String,
+ default: 'add'
+ },
+ form: {
+ type: Object,
+ required: true
+ },
+ rules: {
+ type: Object,
+ default: () => ({})
+ },
+ processOptions: {
+ type: Array,
+ default: () => []
+ },
+ width: {
+ type: String,
+ default: '500px'
+ }
+})
+
+const emit = defineEmits(['update:modelValue', 'close', 'cancel', 'confirm'])
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit('update:modelValue', val)
+})
+
+const formRef = ref(null)
+
+const computedTitle = computed(() => {
+ if (props.operationType === 'edit') return '缂栬緫妫�娴嬫爣鍑�'
+ if (props.operationType === 'copy') return '澶嶅埗妫�娴嬫爣鍑�'
+ return '鏂板妫�娴嬫爣鍑�'
+})
+
+const handleConfirm = () => {
+ if (!formRef.value) {
+ emit('confirm')
+ return
+ }
+ formRef.value.validate((valid) => {
+ if (valid) {
+ emit('confirm')
+ }
+ })
+}
+
+const handleCancel = () => {
+ emit('cancel')
+ dialogVisible.value = false
+}
+
+const resetFields = () => {
+ formRef.value?.resetFields?.()
+}
+
+defineExpose({
+ resetFields
+})
+</script>
diff --git a/src/views/qualityManagement/metricMaintenance/index.vue b/src/views/qualityManagement/metricMaintenance/index.vue
index 016a4c1..876d10f 100644
--- a/src/views/qualityManagement/metricMaintenance/index.vue
+++ b/src/views/qualityManagement/metricMaintenance/index.vue
@@ -1,415 +1,754 @@
<template>
- <div class="app-container product-view">
- <div class="left">
- <div>
- <el-input
- v-model="search"
- style="width: 210px"
- placeholder="杈撳叆鍏抽敭瀛楄繘琛屾悳绱�"
- @change="searchFilter"
- @clear="searchFilter"
+ <div class="app-container metric-maintenance">
+ <!-- 宸︿晶锛氭娴嬫爣鍑嗗垪琛� -->
+ <div class="left-panel">
+ <div class="toolbar">
+ <div class="toolbar-left"></div>
+ <div class="toolbar-right">
+ <el-button type="primary" @click="openStandardDialog('add')">鏂板</el-button>
+ <el-button type="success" plain @click="handleBatchAudit(1)">鎵瑰噯</el-button>
+ <el-button type="warning" plain @click="handleBatchAudit(2)">鎾ら攢</el-button>
+ <el-button type="danger" plain @click="handleBatchDelete">鍒犻櫎</el-button>
+ </div>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="standardColumns"
+ :tableData="standardTableData"
+ :page="page"
+ :isSelection="true"
+ :tableLoading="tableLoading"
+ :rowClassName="rowClassNameCenter"
+ @selection-change="handleSelectionChange"
+ @pagination="handlePagination"
+ :total="page.total"
+ >
+ <template #standardNoCell="{ row }">
+ <span class="clickable-link" @click="handleStandardRowClick(row)">
+ {{ row.standardNo }}
+ </span>
+ </template>
+
+ <!-- 琛ㄥご鎼滅储鎻掓Ы -->
+ <template #standardNoHeader>
+ <el-input
+ v-model="searchForm.standardNo"
+ placeholder="鏍囧噯缂栧彿"
clearable
- prefix-icon="Search"
- />
- </div>
- <div ref="containerRef">
- <el-tree
- ref="tree"
- v-loading="treeLoad"
- :data="list"
- @node-click="handleNodeClick"
- :expand-on-click-node="false"
- default-expand-all
- :default-expanded-keys="expandedKeys"
- :draggable="true"
- :filter-node-method="filterNode"
- :props="{ children: 'children', label: 'label' }"
- highlight-current
- node-key="id"
- style="
- height: calc(100vh - 190px);
- overflow-y: scroll;
- scrollbar-width: none;
- "
- >
- <template #default="{ node, data }">
- <div class="custom-tree-node">
- <span class="tree-node-content">
- <el-icon class="orange-icon">
- <component :is="data.children && data.children.length > 0
- ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
- </el-icon>
- {{ data.label }}
- </span>
- </div>
- </template>
- </el-tree>
- </div>
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #standardNameHeader>
+ <el-input
+ v-model="searchForm.standardName"
+ placeholder="鏍囧噯鍚嶇О"
+ clearable
+ size="small"
+ @change="handleQuery"
+ @clear="handleQuery"
+ />
+ </template>
+ <template #inspectTypeHeader>
+ <el-select
+ v-model="searchForm.inspectType"
+ placeholder="绫诲埆"
+ clearable
+ size="small"
+ style="width: 120px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鍘熸潗鏂欐楠�" value="0" />
+ <el-option label="杩囩▼妫�楠�" value="1" />
+ <el-option label="鍑哄巶妫�楠�" value="2" />
+ </el-select>
+ </template>
+ <template #stateHeader>
+ <el-select
+ v-model="searchForm.state"
+ placeholder="鐘舵��"
+ clearable
+ size="small"
+ style="width: 110px"
+ @change="handleQuery"
+ @clear="handleQuery"
+ >
+ <el-option label="鑽夌" value="0" />
+ <el-option label="閫氳繃" value="1" />
+ <el-option label="鎾ら攢" value="2" />
+ </el-select>
+ </template>
+ </PIMTable>
</div>
- <div class="right">
- <div style="margin-bottom: 10px">
- <el-button type="primary" @click="openModelDia('add')">
- 鏂板妫�娴嬫寚鏍�
+
+ <!-- 鍙充晶锛氭爣鍑嗗弬鏁板垪琛� -->
+ <div class="right-panel">
+ <div class="right-header">
+ <div class="title">鏍囧噯鍙傛暟</div>
+ <div class="desc" v-if="currentStandard">
+ 鎮ㄥ綋鍓嶉�夋嫨鐨勬娴嬫爣鍑嗙紪鍙锋槸锛�
+ <span class="link-text">{{ currentStandard.standardNo }}</span>
+ </div>
+ <div class="desc" v-else>璇峰厛鍦ㄥ乏渚ч�夋嫨涓�涓娴嬫爣鍑�</div>
+ </div>
+
+ <div class="right-toolbar">
+ <el-button type="primary" :disabled="!currentStandard || isStandardReadonly" @click="openParamDialog('add')">
+ 鏂板
</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button
- type="danger"
- @click="handleDelete"
- style="margin-left: 10px"
- plain
- >
+ <el-button type="danger" plain :disabled="!currentStandard || isStandardReadonly" @click="handleParamBatchDelete">
鍒犻櫎
</el-button>
</div>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- :total="page.total"
- ></PIMTable>
- </div>
- <el-dialog
- v-model="modelDia"
- title="妫�娴嬫寚鏍�"
- width="400px"
- @close="closeModelDia"
- >
- <el-form
- :model="modelForm"
- label-width="140px"
- label-position="top"
- :rules="modelRules"
- ref="modelFormRef"
+
+ <el-table
+ v-loading="detailLoading"
+ :data="detailTableData"
+ border
+ :row-class-name="() => 'row-center'"
+ class="center-table"
+ style="width: 100%"
+ height="calc(100vh - 220px)"
+ @selection-change="handleParamSelectionChange"
>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鎸囨爣锛�" prop="parameterItem">
- <el-input
- v-model="modelForm.parameterItem"
- placeholder="璇疯緭鍏ユ寚鏍�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鍗曚綅锛�" prop="unit">
- <el-input
- v-model="modelForm.unit"
- placeholder="璇疯緭鍏ュ崟浣�"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鏍囧噯鍊硷細" prop="standardValue">
- <el-input
- v-model="modelForm.standardValue"
- placeholder="璇疯緭鍏ユ爣鍑嗗��"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="24">
- <el-form-item label="鍐呮帶鍊硷細" prop="controlValue">
- <el-input
- v-model="modelForm.controlValue"
- placeholder="璇疯緭鍏ュ唴鎺у��"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitModelForm">纭</el-button>
- <el-button @click="closeModelDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
+ <el-table-column type="selection" width="48" align="center" />
+ <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+ <el-table-column prop="parameterItem" label="鍙傛暟椤�" min-width="120" />
+ <el-table-column prop="unit" label="鍗曚綅" width="80" />
+ <el-table-column prop="standardValue" label="鏍囧噯鍊�" min-width="120" />
+ <el-table-column prop="controlValue" label="鍐呮帶鍊�" min-width="120" />
+ <el-table-column prop="defaultValue" label="榛樿鍊�" min-width="120" />
+ <el-table-column label="鎿嶄綔" width="140" fixed="right" align="center">
+ <template #default="{ row }">
+ <el-button link type="primary" size="small" :disabled="isStandardReadonly" @click="openParamDialog('edit', row)">
+ 缂栬緫
+ </el-button>
+ <el-button link type="danger" size="small" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 鏂板 / 缂栬緫妫�娴嬫爣鍑� -->
+ <StandardFormDialog
+ ref="standardFormDialogRef"
+ v-model="standardDialogVisible"
+ :operation-type="standardOperationType"
+ :form="standardForm"
+ :rules="standardRules"
+ :process-options="processOptions"
+ @confirm="submitStandardForm"
+ @close="closeStandardDialog"
+ @cancel="closeStandardDialog"
+ />
+
+ <ParamFormDialog
+ ref="paramFormDialogRef"
+ v-model="paramDialogVisible"
+ :operation-type="paramOperationType"
+ :form="paramForm"
+ @confirm="submitParamForm"
+ @close="closeParamDialog"
+ @cancel="closeParamDialog"
+ />
</div>
</template>
<script setup>
-import {ref} from "vue";
-import {addOrEditProductModel, delProductModel, modelListPage, productTreeList} from "@/api/basicData/product.js";
-import ImportExcel from "@/views/basicData/product/ImportExcel/index.vue";
-import {ElMessageBox} from "element-plus";
+import { Search } from '@element-plus/icons-vue'
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, computed } from 'vue'
+import { ElMessageBox } from 'element-plus'
import {
- qualityTestStandardAdd, qualityTestStandardDel,
qualityTestStandardListPage,
- qualityTestStandardUpdate
-} from "@/api/qualityManagement/metricMaintenance.js";
-const { proxy } = getCurrentInstance();
-// 鏍�
-const search = ref("");
-const treeLoad = ref(false);
-const list = ref([]);
-const expandedKeys = ref([]);
-const currentId = ref("");
-const currentParentId = ref("");
-// 鎸囨爣琛ㄦ牸
-const tableData = ref([]);
-const tableLoading = ref(false);
+ qualityTestStandardAdd,
+ qualityTestStandardUpdate,
+ qualityTestStandardDel,
+ qualityTestStandardCopyParam,
+ qualityTestStandardAudit,
+ qualityTestStandardParamList,
+ qualityTestStandardParamAdd,
+ qualityTestStandardParamUpdate,
+ qualityTestStandardParamDel
+} from '@/api/qualityManagement/metricMaintenance.js'
+import { productProcessListPage } from '@/api/basicData/productProcess.js'
+import StandardFormDialog from './StandardFormDialog.vue'
+import ParamFormDialog from './ParamFormDialog.vue'
+
+const { proxy } = getCurrentInstance()
+
+// 宸︿晶鏍囧噯鍒楄〃锛氭暣琛屽唴瀹瑰眳涓紙閰嶅悎鏍峰紡锛�
+const rowClassNameCenter = () => 'row-center'
+
+// 鏍囧噯鐘舵�佷负鈥滈�氳繃(1)鈥濇椂锛屽彸渚у弬鏁扮姝㈠鍒犳敼
+const isStandardReadonly = computed(() => {
+ const state = currentStandard.value?.state
+ return state === 1 || state === '1'
+})
+
+// 鎼滅储鏉′欢
+const data = reactive({
+ searchForm: {
+ standardNo: '',
+ standardName: '',
+ remark: '',
+ state: '',
+ inspectType: '',
+ processId: ''
+ },
+ standardForm: {
+ id: undefined,
+ standardNo: '',
+ standardName: '',
+ remark: '',
+ state: '0',
+ inspectType: '',
+ processId: ''
+ },
+ standardRules: {
+ standardNo: [{ required: true, message: '璇疯緭鍏ユ爣鍑嗙紪鍙�', trigger: 'blur' }],
+ standardName: [{ required: true, message: '璇疯緭鍏ユ爣鍑嗗悕绉�', trigger: 'blur' }],
+ inspectType: [{ required: true, message: '璇烽�夋嫨妫�娴嬬被鍨�', trigger: 'change' }],
+ processId: [{ required: true, message: '璇烽�夋嫨宸ュ簭', trigger: 'change' }]
+ }
+})
+
+const { searchForm, standardForm, standardRules } = toRefs(data)
+
+// 宸︿晶琛ㄦ牸
+const standardTableData = ref([])
+const selectedRows = ref([])
+const tableLoading = ref(false)
const page = reactive({
current: 1,
size: 10,
-});
-const tableColumn = ref([
+ total: 0
+})
+
+// 宸ュ簭涓嬫媺
+const processOptions = ref([])
+
+// 鑾峰彇宸ュ簭鍒楄〃
+const getProcessList = async () => {
+ try {
+ const res = await productProcessListPage({ current: 1, size: 1000 })
+ if (res?.code === 200) {
+ const records = res?.data?.records || []
+ processOptions.value = records.map(item => ({
+ label: item.processName || item.name || item.label,
+ value: item.id || item.processId || item.value
+ }))
+ }
+ } catch (error) {
+ console.error('鑾峰彇宸ュ簭鍒楄〃澶辫触:', error)
+ }
+}
+
+// 褰撳墠閫変腑鐨勬爣鍑� & 鍙充晶璇︽儏
+const currentStandard = ref(null)
+const detailTableData = ref([])
+const detailLoading = ref(false)
+const paramSelectedRows = ref([])
+const paramDialogVisible = ref(false)
+const paramOperationType = ref('add') // add | edit
+const paramFormDialogRef = ref(null)
+const paramForm = reactive({
+ id: undefined,
+ parameterItem: '',
+ unit: '',
+ standardValue: '',
+ controlValue: '',
+ defaultValue: ''
+})
+
+// 寮圭獥
+const standardDialogVisible = ref(false)
+const standardOperationType = ref('add') // add | edit | copy
+const standardFormDialogRef = ref(null)
+
+// 鍒楀畾涔�
+const standardColumns = ref([
{
- label: "鎸囨爣",
- prop: "parameterItem",
+ label: '鏍囧噯缂栧彿',
+ prop: 'standardNo',
+ dataType: 'slot',
+ slot: 'standardNoCell',
+ minWidth: 160,
+ headerSlot: 'standardNoHeader'
},
{
- label: "鍗曚綅",
- prop: "unit",
+ label: '鏍囧噯鍚嶇О',
+ prop: 'standardName',
+ minWidth: 180,
+ headerSlot: 'standardNameHeader'
},
{
- label: "鏍囧噯鍊�",
- prop: "standardValue",
+ label: '绫诲埆',
+ prop: 'inspectType',
+ headerSlot: 'inspectTypeHeader',
+ dataType: 'tag',
+ formatData: (val) => {
+ const map = {
+ 0: '鍘熸潗鏂欐楠�',
+ 1: '杩囩▼妫�楠�',
+ 2: '鍑哄巶妫�楠�'
+ }
+ return map[val] || val
+ }
},
{
- label: "鍐呮帶鍊�",
- prop: "controlValue",
+ label: '宸ュ簭',
+ prop: 'processId'
},
{
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
+ label: '鐘舵��',
+ prop: 'state',
+ headerSlot: 'stateHeader',
+ dataType: 'tag',
+ formatData: (val) => {
+ const map = {
+ 0: '鑽夌',
+ 1: '閫氳繃',
+ 2: '鎾ら攢'
+ }
+ return map[val] || val
+ },
+ formatType: (val) => {
+ if (val === '1' || val === 1) return 'success'
+ if (val === '2' || val === 2) return 'warning'
+ return 'info'
+ }
+ },
+ {
+ label: '澶囨敞',
+ prop: 'remark',
+ minWidth: 160
+ },
+ {
+ dataType: 'action',
+ label: '鎿嶄綔',
+ align: 'center',
+ fixed: 'right',
+ width: 220,
operation: [
{
- name: "缂栬緫",
- type: "text",
+ name: '缂栬緫',
+ type: 'text',
clickFun: (row) => {
- openModelDia("edit", row);
- },
+ openStandardDialog('edit', row)
+ }
},
- ],
- },
-]);
-const selectedRows = ref([]);
-// 鎸囨爣寮规
-const modelDia = ref(false);
-const modelOperationType = ref("");
-const data = reactive({
- modelForm: {
- parameterItem: "",
- unit: "",
- standardValue: "",
- controlValue: "",
- },
- modelRules: {
- parameterItem: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- standardValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- controlValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
- },
-});
-const { modelForm, modelRules } = toRefs(data);
-
-// 鏌ヨ浜у搧鏍�
-const getProductTreeList = () => {
- treeLoad.value = true;
- productTreeList().then((res) => {
- list.value = res;
- list.value.forEach((a) => {
- expandedKeys.value.push(a.label);
- });
- treeLoad.value = false;
- }).catch((err) => {
- treeLoad.value = false;
- });
-};
-// 杩囨护浜у搧鏍�
-const searchFilter = () => {
- proxy.$refs.tree.filter(search.value);
-};
-// 閫夋嫨浜у搧
-const handleNodeClick = (val, node, el) => {
- // 鍙湁鍙跺瓙鑺傜偣鎵嶆墽琛屼互涓嬮�昏緫
- currentId.value = val.id;
- currentParentId.value = val.parentId;
- getModelList();
-};
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-// 鏌ヨ鎸囨爣鏁版嵁
-const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getModelList();
-};
-const getModelList = () => {
- tableLoading.value = true;
- qualityTestStandardListPage({
- productId: currentId.value,
- current: page.current,
- size: page.size,
- }).then((res) => {
- tableData.value = res.data.records;
- page.total = res.data.total;
- tableLoading.value = false;
- });
-};
-// 璋冪敤tree杩囨护鏂规硶 涓枃鑻辫繃婊�
-const filterNode = (value, data, node) => {
- if (!value) {
- //濡傛灉鏁版嵁涓虹┖锛屽垯杩斿洖true,鏄剧ず鎵�鏈夌殑鏁版嵁椤�
- return true;
- }
- // 鏌ヨ鍒楄〃鏄惁鏈夊尮閰嶆暟鎹紝灏嗗�煎皬鍐欙紝鍖归厤鑻辨枃鏁版嵁
- let val = value.toLowerCase();
- return chooseNode(val, data, node); // 璋冪敤杩囨护浜屽眰鏂规硶
-};
-// 杩囨护鐖惰妭鐐� / 瀛愯妭鐐� (濡傛灉杈撳叆鐨勫弬鏁版槸鐖惰妭鐐逛笖鑳藉尮閰嶏紝鍒欒繑鍥炶鑺傜偣浠ュ強鍏朵笅鐨勬墍鏈夊瓙鑺傜偣锛涘鏋滃弬鏁版槸瀛愯妭鐐癸紝鍒欒繑鍥炶鑺傜偣鐨勭埗鑺傜偣銆俷ame鏄腑鏂囧瓧绗︼紝enName鏄嫳鏂囧瓧绗�.
-const chooseNode = (value, data, node) => {
- if (data.label.indexOf(value) !== -1) {
- return true;
- }
- const level = node.level;
- // 濡傛灉浼犲叆鐨勮妭鐐规湰韬氨鏄竴绾ц妭鐐瑰氨涓嶇敤鏍¢獙浜�
- if (level === 1) {
- return false;
- }
- // 鍏堝彇褰撳墠鑺傜偣鐨勭埗鑺傜偣
- let parentData = node.parent;
- // 閬嶅巻褰撳墠鑺傜偣鐨勭埗鑺傜偣
- let index = 0;
- while (index < level - 1) {
- // 濡傛灉鍖归厤鍒扮洿鎺ヨ繑鍥烇紝姝ゅname鍊兼槸涓枃瀛楃锛宔nName鏄嫳鏂囧瓧绗︺�傚垽鏂尮閰嶄腑鑻辨枃杩囨护
- if (parentData.data.label.indexOf(value) !== -1) {
- return true;
- }
- // 鍚﹀垯鐨勮瘽鍐嶅線涓婁竴灞傚仛鍖归厤
- parentData = parentData.parent;
- index++;
- }
- // 娌″尮閰嶅埌杩斿洖false
- return false;
-};
-// 鎵撳紑鎸囨爣寮规
-const openModelDia = (type, data) => {
- modelOperationType.value = type;
- modelDia.value = true;
- modelForm.value.model = "";
- modelForm.value.model = "";
- modelForm.value.id = "";
- if (type === "edit") {
- modelForm.value = { ...data };
- }
-};
-// 瀵煎嚭
-const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- }).then(() => {
- proxy.download("/quality/qualityTestStandard/export", {}, "妫�娴嬫寚鏍�.xlsx");
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-// 鍒犻櫎鎸囨爣
-const handleDelete = () => {
- let ids = [];
- if (selectedRows.value.length > 0) {
- ids = selectedRows.value.map((item) => item.id);
- } else {
- proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
- return;
- }
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- }).then(() => {
- tableLoading.value = true;
- qualityTestStandardDel(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getModelList();
- }).finally(() => {
- tableLoading.value = false;
- });
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-
-// 鎻愪氦瑙勬牸鍨嬪彿淇敼
-const submitModelForm = () => {
- proxy.$refs.modelFormRef.validate((valid) => {
- if (valid) {
- modelForm.value.productId = Number(currentId.value);
- if(modelOperationType.value === 'add') {
- qualityTestStandardAdd(modelForm.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeModelDia();
- getModelList();
- });
- } else {
- qualityTestStandardUpdate(modelForm.value).then((res) => {
- proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
- closeModelDia();
- getModelList();
- });
+ {
+ name: '澶嶅埗',
+ type: 'text',
+ clickFun: async (row) => {
+ if (!row?.id) return
+ try {
+ await ElMessageBox.confirm('纭澶嶅埗璇ユ爣鍑嗗弬鏁帮紵', '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardCopyParam(row.id)
+ proxy.$message.success('澶嶅埗鎴愬姛')
+ getStandardList()
+ if (currentStandard.value?.id === row.id) {
+ loadDetail(row.id)
+ }
+ }
+ },
+ {
+ name: '鍒犻櫎',
+ type: 'text',
+ clickFun: (row) => {
+ handleDelete(row)
+ }
}
- }
- });
-};
-// 鍏抽棴鍨嬪彿寮规
-const closeModelDia = () => {
- proxy.$refs.modelFormRef.resetFields();
- modelDia.value = false;
-};
-getProductTreeList();
+ ]
+ }
+])
+
+// 鏌ヨ鍒楄〃
+const getStandardList = () => {
+ tableLoading.value = true
+ const params = {
+ ...searchForm.value,
+ current: page.current,
+ size: page.size
+ }
+ qualityTestStandardListPage(params)
+ .then((res) => {
+ const records = res?.data?.records || []
+ standardTableData.value = records
+ page.total = res?.data?.total || records.length
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+}
+
+const handleQuery = () => {
+ page.current = 1
+ getStandardList()
+}
+
+const resetQuery = () => {
+ searchForm.value.standardNo = ''
+ searchForm.value.standardName = ''
+ searchForm.value.remark = ''
+ searchForm.value.state = ''
+ searchForm.value.inspectType = ''
+ searchForm.value.processId = ''
+ handleQuery()
+}
+
+const handlePagination = (obj) => {
+ page.current = obj.page
+ page.size = obj.limit
+ getStandardList()
+}
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection
+}
+
+// 鎵归噺瀹℃牳锛氱姸鎬� 1=鎵瑰噯锛�2=鎾ら攢
+const handleBatchAudit = async (state) => {
+ if (!selectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const text = state === 1 ? '鎵瑰噯' : '鎾ら攢'
+ const payload = selectedRows.value
+ .filter(i => i?.id)
+ .map((item) => ({ id: item.id, state }))
+
+ if (!payload.length) {
+ proxy.$message.warning('璇烽�夋嫨鏈夋晥鏁版嵁')
+ return
+ }
+
+ try {
+ await ElMessageBox.confirm(`纭${text}閫変腑鐨勬爣鍑嗭紵`, '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardAudit(payload)
+ proxy.$message.success(`${text}鎴愬姛`)
+ getStandardList()
+}
+
+// 宸︿晶琛岀偣鍑伙紝鍔犺浇鍙充晶鍙傛暟
+const handleStandardRowClick = (row) => {
+ currentStandard.value = row
+ loadDetail(row.id)
+}
+
+const loadDetail = (standardId) => {
+ if (!standardId) {
+ detailTableData.value = []
+ return
+ }
+ detailLoading.value = true
+ qualityTestStandardParamList({ testStandardId: standardId }).then((res) => {
+ detailTableData.value = res?.data || []
+ })
+ .finally(() => {
+ detailLoading.value = false
+ })
+}
+
+const handleParamSelectionChange = (selection) => {
+ paramSelectedRows.value = selection
+}
+
+const openParamDialog = (type, row) => {
+ if (!currentStandard.value?.id) return
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ paramOperationType.value = type
+ if (type === 'add') {
+ Object.assign(paramForm, {
+ id: undefined,
+ parameterItem: '',
+ unit: '',
+ standardValue: '',
+ controlValue: '',
+ defaultValue: ''
+ })
+ } else if (type === 'edit' && row) {
+ Object.assign(paramForm, row)
+ }
+ paramDialogVisible.value = true
+}
+
+const closeParamDialog = () => {
+ paramDialogVisible.value = false
+ paramFormDialogRef.value?.resetFields?.()
+}
+
+const submitParamForm = async () => {
+ const testStandardId = currentStandard.value?.id
+ if (!testStandardId) return
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ const payload = { ...paramForm, testStandardId }
+ if (paramOperationType.value === 'edit') {
+ await qualityTestStandardParamUpdate(payload)
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ } else {
+ await qualityTestStandardParamAdd(payload)
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ }
+ closeParamDialog()
+ loadDetail(testStandardId)
+}
+
+const handleParamDelete = async (row) => {
+ if (!row?.id) return
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ try {
+ await ElMessageBox.confirm('纭鍒犻櫎璇ュ弬鏁帮紵', '鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardParamDel([row.id])
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadDetail(currentStandard.value?.id)
+}
+
+const handleParamBatchDelete = async () => {
+ if (isStandardReadonly.value) {
+ proxy.$message.warning('璇ユ爣鍑嗗凡閫氳繃锛屽弬鏁颁笉鍙紪杈�')
+ return
+ }
+ if (!paramSelectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const ids = paramSelectedRows.value.map((i) => i.id)
+ try {
+ await ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', { type: 'warning' })
+ } catch {
+ return
+ }
+ await qualityTestStandardParamDel(ids)
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ loadDetail(currentStandard.value?.id)
+}
+
+// 鏂板 / 缂栬緫 / 澶嶅埗
+const openStandardDialog = (type, row) => {
+ standardOperationType.value = type
+ if (type === 'add') {
+ Object.assign(standardForm.value, {
+ id: undefined,
+ standardNo: '',
+ standardName: '',
+ remark: '',
+ state: '0',
+ inspectType: '',
+ processId: ''
+ })
+ } else if (type === 'edit' && row) {
+ Object.assign(standardForm.value, row)
+ } else if (type === 'copy' && row) {
+ const { id, ...rest } = row
+ Object.assign(standardForm.value, {
+ ...rest,
+ id: undefined,
+ standardNo: '',
+ state: '0'
+ })
+ }
+ standardDialogVisible.value = true
+}
+
+const closeStandardDialog = () => {
+ standardDialogVisible.value = false
+ standardFormDialogRef.value?.resetFields?.()
+}
+
+const submitStandardForm = () => {
+ const payload = { ...standardForm.value }
+ const isEdit = standardOperationType.value === 'edit'
+ if (isEdit) {
+ qualityTestStandardUpdate(payload).then(() => {
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ standardDialogVisible.value = false
+ getStandardList()
+ })
+ } else {
+ qualityTestStandardAdd(payload).then(() => {
+ proxy.$message.success('鎻愪氦鎴愬姛')
+ standardDialogVisible.value = false
+ getStandardList()
+ })
+ }
+}
+
+// 鍒犻櫎锛堝崟鏉★級
+const handleDelete = (row) => {
+ const ids = [row.id]
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ tableLoading.value = true
+ qualityTestStandardDel(ids)
+ .then(() => {
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ getStandardList()
+ if (currentStandard.value && currentStandard.value.id === row.id) {
+ currentStandard.value = null
+ detailTableData.value = []
+ }
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+ })
+ .catch(() => {
+ proxy.$modal?.msg('宸插彇娑�')
+ })
+}
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (!selectedRows.value.length) {
+ proxy.$message.warning('璇烽�夋嫨鏁版嵁')
+ return
+ }
+ const ids = selectedRows.value.map((item) => item.id)
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ tableLoading.value = true
+ qualityTestStandardDel(ids)
+ .then(() => {
+ proxy.$message.success('鍒犻櫎鎴愬姛')
+ getStandardList()
+ if (currentStandard.value && ids.includes(currentStandard.value.id)) {
+ currentStandard.value = null
+ detailTableData.value = []
+ }
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+ })
+ .catch(() => {
+ proxy.$modal?.msg('宸插彇娑�')
+ })
+}
+
+onMounted(() => {
+ getProcessList()
+ getStandardList()
+})
</script>
<style scoped>
-.product-view {
+.metric-maintenance {
display: flex;
+ gap: 16px;
}
-.left {
- width: 380px;
- padding: 16px;
- background: #ffffff;
-}
-.right {
- width: calc(100% - 380px);
- padding: 16px;
- margin-left: 20px;
- background: #ffffff;
-}
-.custom-tree-node {
+
+.left-panel,
+.right-panel {
flex: 1;
+ background: #ffffff;
+ padding: 16px;
+ box-sizing: border-box;
+}
+
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.toolbar-left {
display: flex;
align-items: center;
- justify-content: space-between;
- font-size: 14px;
- padding-right: 8px;
+ flex-wrap: wrap;
+ gap: 4px;
}
-.tree-node-content {
+
+.toolbar-right {
+ flex-shrink: 0;
+}
+
+.search-label {
+ margin: 0 4px 0 12px;
+}
+
+.search-label:first-of-type {
+ margin-left: 0;
+}
+
+.right-header {
display: flex;
- align-items: center; /* 鍨傜洿灞呬腑 */
- height: 100%;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: 10px;
}
-.orange-icon {
- color: orange;
- font-size: 18px;
- margin-right: 8px; /* 鍥炬爣涓庢枃瀛椾箣闂村姞鐐归棿璺� */
+
+.right-header .title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.right-header .desc {
+ font-size: 13px;
+ color: #666;
+}
+
+.right-toolbar {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.link-text {
+ color: #409eff;
+ cursor: default;
+}
+
+.clickable-link {
+ color: #409eff;
+ cursor: pointer;
+}
+
+.clickable-link:hover {
+ text-decoration: underline;
+}
+
+:deep(.row-center td) {
+ text-align: center !important;
+}
+
+/* el-table 琛ㄥご/鍐呭缁熶竴灞呬腑锛坮ow-class-name 涓嶄綔鐢ㄤ簬琛ㄥご锛� */
+:deep(.center-table .el-table__header-wrapper th .cell) {
+ text-align: center !important;
+}
+:deep(.center-table .el-table__body-wrapper td .cell) {
+ text-align: center !important;
}
</style>
\ No newline at end of file
diff --git a/src/views/qualityManagement/metricMaintenance/index0.vue b/src/views/qualityManagement/metricMaintenance/index0.vue
new file mode 100644
index 0000000..016a4c1
--- /dev/null
+++ b/src/views/qualityManagement/metricMaintenance/index0.vue
@@ -0,0 +1,415 @@
+<template>
+ <div class="app-container product-view">
+ <div class="left">
+ <div>
+ <el-input
+ v-model="search"
+ style="width: 210px"
+ placeholder="杈撳叆鍏抽敭瀛楄繘琛屾悳绱�"
+ @change="searchFilter"
+ @clear="searchFilter"
+ clearable
+ prefix-icon="Search"
+ />
+ </div>
+ <div ref="containerRef">
+ <el-tree
+ ref="tree"
+ v-loading="treeLoad"
+ :data="list"
+ @node-click="handleNodeClick"
+ :expand-on-click-node="false"
+ default-expand-all
+ :default-expanded-keys="expandedKeys"
+ :draggable="true"
+ :filter-node-method="filterNode"
+ :props="{ children: 'children', label: 'label' }"
+ highlight-current
+ node-key="id"
+ style="
+ height: calc(100vh - 190px);
+ overflow-y: scroll;
+ scrollbar-width: none;
+ "
+ >
+ <template #default="{ node, data }">
+ <div class="custom-tree-node">
+ <span class="tree-node-content">
+ <el-icon class="orange-icon">
+ <component :is="data.children && data.children.length > 0
+ ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
+ </el-icon>
+ {{ data.label }}
+ </span>
+ </div>
+ </template>
+ </el-tree>
+ </div>
+ </div>
+ <div class="right">
+ <div style="margin-bottom: 10px">
+ <el-button type="primary" @click="openModelDia('add')">
+ 鏂板妫�娴嬫寚鏍�
+ </el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button
+ type="danger"
+ @click="handleDelete"
+ style="margin-left: 10px"
+ plain
+ >
+ 鍒犻櫎
+ </el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ ></PIMTable>
+ </div>
+ <el-dialog
+ v-model="modelDia"
+ title="妫�娴嬫寚鏍�"
+ width="400px"
+ @close="closeModelDia"
+ >
+ <el-form
+ :model="modelForm"
+ label-width="140px"
+ label-position="top"
+ :rules="modelRules"
+ ref="modelFormRef"
+ >
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鎸囨爣锛�" prop="parameterItem">
+ <el-input
+ v-model="modelForm.parameterItem"
+ placeholder="璇疯緭鍏ユ寚鏍�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input
+ v-model="modelForm.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鏍囧噯鍊硷細" prop="standardValue">
+ <el-input
+ v-model="modelForm.standardValue"
+ placeholder="璇疯緭鍏ユ爣鍑嗗��"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="24">
+ <el-form-item label="鍐呮帶鍊硷細" prop="controlValue">
+ <el-input
+ v-model="modelForm.controlValue"
+ placeholder="璇疯緭鍏ュ唴鎺у��"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitModelForm">纭</el-button>
+ <el-button @click="closeModelDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {addOrEditProductModel, delProductModel, modelListPage, productTreeList} from "@/api/basicData/product.js";
+import ImportExcel from "@/views/basicData/product/ImportExcel/index.vue";
+import {ElMessageBox} from "element-plus";
+import {
+ qualityTestStandardAdd, qualityTestStandardDel,
+ qualityTestStandardListPage,
+ qualityTestStandardUpdate
+} from "@/api/qualityManagement/metricMaintenance.js";
+const { proxy } = getCurrentInstance();
+// 鏍�
+const search = ref("");
+const treeLoad = ref(false);
+const list = ref([]);
+const expandedKeys = ref([]);
+const currentId = ref("");
+const currentParentId = ref("");
+// 鎸囨爣琛ㄦ牸
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 10,
+});
+const tableColumn = ref([
+ {
+ label: "鎸囨爣",
+ prop: "parameterItem",
+ },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ },
+ {
+ label: "鏍囧噯鍊�",
+ prop: "standardValue",
+ },
+ {
+ label: "鍐呮帶鍊�",
+ prop: "controlValue",
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ openModelDia("edit", row);
+ },
+ },
+ ],
+ },
+]);
+const selectedRows = ref([]);
+// 鎸囨爣寮规
+const modelDia = ref(false);
+const modelOperationType = ref("");
+const data = reactive({
+ modelForm: {
+ parameterItem: "",
+ unit: "",
+ standardValue: "",
+ controlValue: "",
+ },
+ modelRules: {
+ parameterItem: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ standardValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ controlValue: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ },
+});
+const { modelForm, modelRules } = toRefs(data);
+
+// 鏌ヨ浜у搧鏍�
+const getProductTreeList = () => {
+ treeLoad.value = true;
+ productTreeList().then((res) => {
+ list.value = res;
+ list.value.forEach((a) => {
+ expandedKeys.value.push(a.label);
+ });
+ treeLoad.value = false;
+ }).catch((err) => {
+ treeLoad.value = false;
+ });
+};
+// 杩囨护浜у搧鏍�
+const searchFilter = () => {
+ proxy.$refs.tree.filter(search.value);
+};
+// 閫夋嫨浜у搧
+const handleNodeClick = (val, node, el) => {
+ // 鍙湁鍙跺瓙鑺傜偣鎵嶆墽琛屼互涓嬮�昏緫
+ currentId.value = val.id;
+ currentParentId.value = val.parentId;
+ getModelList();
+};
+// 琛ㄦ牸閫夋嫨鏁版嵁
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+// 鏌ヨ鎸囨爣鏁版嵁
+const pagination = (obj) => {
+ page.current = obj.page;
+ page.size = obj.limit;
+ getModelList();
+};
+const getModelList = () => {
+ tableLoading.value = true;
+ qualityTestStandardListPage({
+ productId: currentId.value,
+ current: page.current,
+ size: page.size,
+ }).then((res) => {
+ tableData.value = res.data.records;
+ page.total = res.data.total;
+ tableLoading.value = false;
+ });
+};
+// 璋冪敤tree杩囨护鏂规硶 涓枃鑻辫繃婊�
+const filterNode = (value, data, node) => {
+ if (!value) {
+ //濡傛灉鏁版嵁涓虹┖锛屽垯杩斿洖true,鏄剧ず鎵�鏈夌殑鏁版嵁椤�
+ return true;
+ }
+ // 鏌ヨ鍒楄〃鏄惁鏈夊尮閰嶆暟鎹紝灏嗗�煎皬鍐欙紝鍖归厤鑻辨枃鏁版嵁
+ let val = value.toLowerCase();
+ return chooseNode(val, data, node); // 璋冪敤杩囨护浜屽眰鏂规硶
+};
+// 杩囨护鐖惰妭鐐� / 瀛愯妭鐐� (濡傛灉杈撳叆鐨勫弬鏁版槸鐖惰妭鐐逛笖鑳藉尮閰嶏紝鍒欒繑鍥炶鑺傜偣浠ュ強鍏朵笅鐨勬墍鏈夊瓙鑺傜偣锛涘鏋滃弬鏁版槸瀛愯妭鐐癸紝鍒欒繑鍥炶鑺傜偣鐨勭埗鑺傜偣銆俷ame鏄腑鏂囧瓧绗︼紝enName鏄嫳鏂囧瓧绗�.
+const chooseNode = (value, data, node) => {
+ if (data.label.indexOf(value) !== -1) {
+ return true;
+ }
+ const level = node.level;
+ // 濡傛灉浼犲叆鐨勮妭鐐规湰韬氨鏄竴绾ц妭鐐瑰氨涓嶇敤鏍¢獙浜�
+ if (level === 1) {
+ return false;
+ }
+ // 鍏堝彇褰撳墠鑺傜偣鐨勭埗鑺傜偣
+ let parentData = node.parent;
+ // 閬嶅巻褰撳墠鑺傜偣鐨勭埗鑺傜偣
+ let index = 0;
+ while (index < level - 1) {
+ // 濡傛灉鍖归厤鍒扮洿鎺ヨ繑鍥烇紝姝ゅname鍊兼槸涓枃瀛楃锛宔nName鏄嫳鏂囧瓧绗︺�傚垽鏂尮閰嶄腑鑻辨枃杩囨护
+ if (parentData.data.label.indexOf(value) !== -1) {
+ return true;
+ }
+ // 鍚﹀垯鐨勮瘽鍐嶅線涓婁竴灞傚仛鍖归厤
+ parentData = parentData.parent;
+ index++;
+ }
+ // 娌″尮閰嶅埌杩斿洖false
+ return false;
+};
+// 鎵撳紑鎸囨爣寮规
+const openModelDia = (type, data) => {
+ modelOperationType.value = type;
+ modelDia.value = true;
+ modelForm.value.model = "";
+ modelForm.value.model = "";
+ modelForm.value.id = "";
+ if (type === "edit") {
+ modelForm.value = { ...data };
+ }
+};
+// 瀵煎嚭
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ proxy.download("/quality/qualityTestStandard/export", {}, "妫�娴嬫寚鏍�.xlsx");
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鍒犻櫎鎸囨爣
+const handleDelete = () => {
+ let ids = [];
+ if (selectedRows.value.length > 0) {
+ ids = selectedRows.value.map((item) => item.id);
+ } else {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ tableLoading.value = true;
+ qualityTestStandardDel(ids).then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getModelList();
+ }).finally(() => {
+ tableLoading.value = false;
+ });
+ }).catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鎻愪氦瑙勬牸鍨嬪彿淇敼
+const submitModelForm = () => {
+ proxy.$refs.modelFormRef.validate((valid) => {
+ if (valid) {
+ modelForm.value.productId = Number(currentId.value);
+ if(modelOperationType.value === 'add') {
+ qualityTestStandardAdd(modelForm.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeModelDia();
+ getModelList();
+ });
+ } else {
+ qualityTestStandardUpdate(modelForm.value).then((res) => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeModelDia();
+ getModelList();
+ });
+ }
+ }
+ });
+};
+// 鍏抽棴鍨嬪彿寮规
+const closeModelDia = () => {
+ proxy.$refs.modelFormRef.resetFields();
+ modelDia.value = false;
+};
+getProductTreeList();
+</script>
+
+<style scoped>
+.product-view {
+ display: flex;
+}
+.left {
+ width: 380px;
+ padding: 16px;
+ background: #ffffff;
+}
+.right {
+ width: calc(100% - 380px);
+ padding: 16px;
+ margin-left: 20px;
+ background: #ffffff;
+}
+.custom-tree-node {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ padding-right: 8px;
+}
+.tree-node-content {
+ display: flex;
+ align-items: center; /* 鍨傜洿灞呬腑 */
+ height: 100%;
+}
+.orange-icon {
+ color: orange;
+ font-size: 18px;
+ margin-right: 8px; /* 鍥炬爣涓庢枃瀛椾箣闂村姞鐐归棿璺� */
+}
+</style>
\ No newline at end of file
--
Gitblit v1.9.3