From cdf8190c92a536dabdbd3dfd6758cf67320ff6df Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期五, 16 一月 2026 17:47:21 +0800
Subject: [PATCH] Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-management into dev_New
---
src/views/procurementManagement/paymentHistory/index.vue | 87
src/views/productionManagement/productionProcess/index.vue | 96
src/views/productionManagement/processRoute/index.vue | 12
src/views/productionManagement/workOrder/index.vue | 108
src/views/productionManagement/processRoute/processRouteItem/index.vue | 1133 +++++++----
src/api/productionManagement/productionOrder.js | 28
src/api/inventoryManagement/stockIn.js | 87
src/views/procurementManagement/paymentEntry/index.vue | 2
src/components/PageHeader/index.vue | 53
src/views/financialManagement/revenueManagement/Modal.vue | 192 +
src/views/productionManagement/productionProcess/Edit.vue | 32
src/views/productionManagement/productStructure/index.vue | 301 ++
src/api/inventoryManagement/stockManage.js | 27
src/api/productionManagement/processRouteItem.js | 41
src/views/productionManagement/processRoute/New.vue | 171
src/api/productionManagement/processRoute.js | 8
src/api/productionManagement/productStructure.js | 4
src/api/productionManagement/productBom.js | 47
src/components/Dialog/FileListDialog.vue | 7
src/views/financialManagement/financialStatements/index.vue | 194 +
src/views/financialManagement/expenseManagement/Modal.vue | 192 +
src/views/salesManagement/receiptPayment/index.vue | 2
src/api/productionManagement/productionProcess.js | 18
src/components/PIMTable/PIMTable.vue | 2
src/views/financialManagement/loanManagement/index.vue | 9
src/views/financialManagement/revenueManagement/index.vue | 249 ++
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue | 25
src/components/QRCodeGenerator/index.vue | 859 ++++----
src/views/productionManagement/productionReporting/Input.vue | 10
src/api/productionManagement/productProcessRoute.js | 62
src/views/financialManagement/expenseManagement/index.vue | 245 ++
src/views/productionManagement/productStructure/Detail/index.vue | 109
/dev/null | 555 -----
src/layout/components/NotificationCenter/index.vue | 4
src/views/productionManagement/productionReporting/index.vue | 109
src/api/inventoryManagement/stockOut.js | 21
src/main.js | 3
src/views/productionManagement/productionOrder/index.vue | 234 ++
src/views/productionManagement/processRoute/Edit.vue | 232 +
src/views/salesManagement/receiptPaymentHistory/index.vue | 91
src/views/collaborativeApproval/noticeManagement/index.vue | 3
src/views/productionManagement/productionProcess/New.vue | 3
42 files changed, 3,674 insertions(+), 1,993 deletions(-)
diff --git a/src/api/inventoryManagement/stockIn.js b/src/api/inventoryManagement/stockIn.js
index 55cef01..3481415 100644
--- a/src/api/inventoryManagement/stockIn.js
+++ b/src/api/inventoryManagement/stockIn.js
@@ -9,6 +9,50 @@
});
};
+// 鏌ヨ鐢熶骇鍏ュ簱淇℃伅鍒楄〃
+export const getStockInPageByProduction = (params) => {
+ return request({
+ url: "/stockin/listPageByProduction",
+ method: "get",
+ params,
+ });
+};
+
+// 鏌ヨ鐢熶骇鍏ュ簱淇℃伅鍒楄〃
+export const getStockInPageByProductProduction = (params) => {
+ return request({
+ url: "/stockin/listPageByProductProduction",
+ method: "get",
+ params,
+ });
+};
+
+// 鍑哄簱鍙拌处-鏌ヨ鑷畾涔夊叆搴撲俊鎭垪琛�
+export const getStockInPageByCustom = (params) => {
+ return request({
+ url: "/stockmanagement/listPageByCustom",
+ method: "get",
+ params,
+ });
+};
+// 鍏ュ簱绠$悊-鏌ヨ鑷畾涔夊叆搴撲俊鎭垪琛�
+export const getInPageByCustom = (params) => {
+ return request({
+ url: "/stockin/listPageByCustom",
+ method: "get",
+ params,
+ });
+};
+
+// 鍑哄簱鍙拌处-鏌ヨ鐢熶骇鍑哄簱淇℃伅鍒楄〃
+export const getStockInPageByProduct = (params) => {
+ return request({
+ url: "/stockmanagement/listPageByProduct",
+ method: "get",
+ params,
+ });
+};
+
// 淇敼鍏ュ簱瀛樹俊鎭�
export const updateStockIn = (data) => {
return request({
@@ -26,6 +70,14 @@
data,
});
};
+// 淇敼鏉愭枡搴撳瓨淇℃伅
+export const updateManagementByCustom = (data) => {
+ return request({
+ url: "/stockin/updateManagementByCustom ",
+ method: "post",
+ data,
+ });
+};
// 鏂板鍟嗗搧鍏ュ簱淇℃伅
export function addSutockIn(data) {
@@ -36,6 +88,32 @@
})
}
+// 鏂板鑷畾涔夊叆搴撲俊鎭�
+export function addStockInCustom(data) {
+ return request({
+ url: '/stockin/addCustom',
+ method: 'post',
+ data: data
+ })
+}
+
+// 缂栬緫鑷畾涔夊叆搴撲俊鎭�
+export function updateStockInCustom(data) {
+ return request({
+ url: '/stockin/updateCustom',
+ method: 'post',
+ data: data
+ })
+}
+// 缂栬緫鎴愬搧鍏ュ簱淇℃伅
+export function updateProduct(data) {
+ return request({
+ url: '/stockin/update',
+ method: 'post',
+ data: data
+ })
+}
+
// 鍒犻櫎鍏ュ簱淇℃伅
export function delStockIn(ids) {
return request({
@@ -45,6 +123,15 @@
})
}
+// 鍒犻櫎鑷畾涔夊叆搴撲俊鎭�
+export function delStockInCustom(ids) {
+ return request({
+ url: '/stockin/delteCustom',
+ method: 'post',
+ data: ids
+ })
+}
+
// 瀵煎嚭鍏ュ簱淇℃伅
export function exportStockIn(query) {
return request({
diff --git a/src/api/inventoryManagement/stockManage.js b/src/api/inventoryManagement/stockManage.js
index bb2081b..e2a4ebf 100644
--- a/src/api/inventoryManagement/stockManage.js
+++ b/src/api/inventoryManagement/stockManage.js
@@ -9,6 +9,31 @@
});
};
+// 鏌ヨ鐢熶骇鍏ュ簱搴撳瓨淇℃伅鍒楄〃
+export const getStockManagePageByProduction = (params) => {
+ return request({
+ url: "/stockin/listPageCopyByProduction",
+ method: "get",
+ params,
+ });
+};
+// 鏌ヨ鎴愬搧搴撳瓨淇℃伅鍒楄〃
+export const getStockManageProduction = (params) => {
+ return request({
+ url: "/stockin/listPageProductionStock",
+ method: "get",
+ params,
+ });
+};
+// 鏌ヨ鑷畾涔夊叆搴撳簱瀛樹俊鎭垪琛�
+export const getStockManagePageByCustom = (params) => {
+ return request({
+ url: "/stockin/listPageCopyByCustom",
+ method: "get",
+ params,
+ });
+};
+
// 淇敼搴撳瓨淇℃伅
export const updateStockManage = (data) => {
@@ -38,7 +63,7 @@
})
}
-//鍑哄簱鎺ュ彛
+// 鍑哄簱绠$悊-棰嗙敤鎺ュ彛
export const stockOut = (data) => {
return request({
url: '/stockmanagement/stockout',
diff --git a/src/api/inventoryManagement/stockOut.js b/src/api/inventoryManagement/stockOut.js
index 5d410d9..f1b36d9 100644
--- a/src/api/inventoryManagement/stockOut.js
+++ b/src/api/inventoryManagement/stockOut.js
@@ -1,9 +1,18 @@
import request from "@/utils/request";
-//鏌ヨ鍑哄簱鍒楄〃
+// 鍑哄簱鍙拌处-閲囪喘鍑哄簱鏌ヨ鍑哄簱鍒楄〃
export const getStockOutPage = (params) => {
return request({
url: "/stockmanagement/listPage",
+ method: "get",
+ params,
+ });
+};
+
+// 鍑哄簱鍙拌处-鐢熶骇鍑哄簱鏌ヨ鍑哄簱鍒楄〃
+export const getStockOutSemiProductPage = (params) => {
+ return request({
+ url: "/stockmanagement/listPageBySemiProduct",
method: "get",
params,
});
@@ -35,13 +44,3 @@
data: ids
})
}
-
-//瀵煎嚭鍑哄簱淇℃伅
-export const exportStockOut = (query) => {
- return request({
- url: '/stockmanagement/export',
- method: 'get',
- params: query,
- responseType: 'blob'
- })
-}
\ No newline at end of file
diff --git a/src/api/productionManagement/processRoute.js b/src/api/productionManagement/processRoute.js
index 4d16775..c13b2fc 100644
--- a/src/api/productionManagement/processRoute.js
+++ b/src/api/productionManagement/processRoute.js
@@ -31,4 +31,12 @@
method: 'put',
data: data,
})
+}
+
+// 鑾峰彇璇︽儏
+export function getById(id) {
+ return request({
+ url: `/processRoute/${id}`,
+ method: 'get',
+ })
}
\ No newline at end of file
diff --git a/src/api/productionManagement/processRouteItem.js b/src/api/productionManagement/processRouteItem.js
index ad4861c..9e81406 100644
--- a/src/api/productionManagement/processRouteItem.js
+++ b/src/api/productionManagement/processRouteItem.js
@@ -3,17 +3,36 @@
// 鍒楄〃鏌ヨ
export function findProcessRouteItemList(query) {
- return request({
- url: "/processRouteItem/list",
- method: "get",
- params: 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
+ return request({
+ url: "/processRouteItem",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鎺掑簭鎺ュ彛
+export function sortProcessRouteItem(data) {
+ return request({
+ url: "/processRouteItem/sort",
+ method: "post",
+ data: data,
+ });
+}
+
+// 鎵归噺鍒犻櫎鎺ュ彛
+export function batchDeleteProcessRouteItem(ids) {
+ // 灏唅d鏁扮粍杞崲涓洪�楀彿鍒嗛殧鐨勫瓧绗︿覆锛屾嫾鎺ュ埌URL鍚庨潰
+ const idsStr = Array.isArray(ids) ? ids.join(",") : ids;
+ return request({
+ url: `/processRouteItem/batchDelete/${idsStr}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/productionManagement/productBom.js b/src/api/productionManagement/productBom.js
new file mode 100644
index 0000000..893755b
--- /dev/null
+++ b/src/api/productionManagement/productBom.js
@@ -0,0 +1,47 @@
+// 浜у搧BOM椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function listPage(query) {
+ return request({
+ url: "/productBom/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏂板
+export function add(data) {
+ return request({
+ url: "/productBom/add",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼
+export function update(data) {
+ return request({
+ url: "/productBom/update",
+ method: "put",
+ data: data,
+ });
+}
+
+// 鎵归噺鍒犻櫎
+export function batchDelete(ids) {
+ return request({
+ url: "/productBom/batchDelete",
+ method: "delete",
+ data: ids,
+ });
+}
+
+// 鏍规嵁浜у搧鍨嬪彿ID鏌ヨBOM
+export function getByModel(productModelId) {
+ return request({
+ url: "/productBom/getByModel",
+ method: "get",
+ params: { productModelId },
+ });
+}
diff --git a/src/api/productionManagement/productProcessRoute.js b/src/api/productionManagement/productProcessRoute.js
index d756e26..e8d5da5 100644
--- a/src/api/productionManagement/productProcessRoute.js
+++ b/src/api/productionManagement/productProcessRoute.js
@@ -3,26 +3,52 @@
// 鍒楄〃鏌ヨ
export function findProductProcessRouteItemList(query) {
- return request({
- url: "/productProcessRoute/list",
- method: "get",
- params: query,
- });
+ return request({
+ url: "/productProcessRoute/list",
+ method: "get",
+ params: query,
+ });
}
export function addOrUpdateProductProcessRouteItem(data) {
- return request({
- url: "/productProcessRoute/updateRouteItem",
- method: "post",
- data: 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
+// 鐢熶骇璁㈠崟涓嬶細鏂板宸ヨ壓璺嚎椤圭洰
+export function addRouteItem(data) {
+ return request({
+ url: "/productProcessRoute/addRouteItem",
+ method: "post",
+ data,
+ });
+}
+
+// 鑾峰彇鐢熶骇璁㈠崟鍏宠仈鐨勫伐鑹鸿矾绾夸富淇℃伅
+export function listMain(orderId) {
+ return request({
+ url: "/productProcessRoute/listMain",
+ method: "get",
+ params: { orderId },
+ });
+}
+
+// 鍒犻櫎宸ヨ壓璺嚎椤圭洰锛堣矾鐢卞悗鎷兼帴 id锛�
+export function deleteRouteItem(id) {
+ return request({
+ url: `/productProcessRoute/deleteRouteItem/${id}`,
+ method: "delete",
+ });
+}
+
+// 鐢熶骇璁㈠崟涓嬶細鎺掑簭宸ヨ壓璺嚎椤圭洰
+export function sortRouteItem(data) {
+ return request({
+ url: "/productProcessRoute/sortRouteItem",
+ method: "post",
+ data,
+ });
+}
diff --git a/src/api/productionManagement/productStructure.js b/src/api/productionManagement/productStructure.js
index 074e27a..e69e14a 100644
--- a/src/api/productionManagement/productStructure.js
+++ b/src/api/productionManagement/productStructure.js
@@ -4,7 +4,7 @@
// 鍒嗛〉鏌ヨ
export function queryList(id) {
return request({
- url: "/productStructure/listByproductModelId/" + id,
+ url: "/productStructure/listBybomId/" + id,
method: "get",
});
}
@@ -15,4 +15,4 @@
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 0c37be1..9f110a7 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -10,7 +10,6 @@
});
}
-
export function productOrderListPage(query) {
return request({
url: "/productOrder/page",
@@ -19,6 +18,33 @@
});
}
+// 鐢熶骇璁㈠崟-鎸変骇鍝佸瀷鍙锋煡璇㈠彲鐢ㄥ伐鑹鸿矾绾垮垪琛�
+export function listProcessRoute(query) {
+ return request({
+ url: "/productOrder/listProcessRoute",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鐢熶骇璁㈠崟-缁戝畾宸ヨ壓璺嚎
+export function bindingRoute(data) {
+ return request({
+ url: "/productOrder/bindingRoute",
+ method: "post",
+ data,
+ });
+}
+
+// 鐢熶骇璁㈠崟-鏌ヨ浜у搧缁撴瀯鍒楄〃
+export function listProcessBom(query) {
+ return request({
+ url: "/productOrder/listProcessBom",
+ method: "get",
+ params: query,
+ });
+}
+
// 鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
export function schedulingList(query) {
return request({
diff --git a/src/api/productionManagement/productionProcess.js b/src/api/productionManagement/productionProcess.js
index e3cd929..d3a453c 100644
--- a/src/api/productionManagement/productionProcess.js
+++ b/src/api/productionManagement/productionProcess.js
@@ -48,4 +48,22 @@
url: "/productProcess/list",
method: "get",
});
+}
+
+// 瀵煎叆鏁版嵁
+export function importData(data) {
+ return request({
+ url: "/productProcess/importData",
+ method: "post",
+ data: data,
+ });
+}
+
+// 涓嬭浇妯℃澘
+export function downloadTemplate() {
+ return request({
+ url: "/productProcess/downloadTemplate",
+ method: "post",
+ responseType: "blob",
+ });
}
\ No newline at end of file
diff --git a/src/components/Dialog/FileListDialog.vue b/src/components/Dialog/FileListDialog.vue
index ebe81dd..0721a55 100644
--- a/src/components/Dialog/FileListDialog.vue
+++ b/src/components/Dialog/FileListDialog.vue
@@ -229,10 +229,9 @@
const handleUpload = async () => {
if (props.uploadMethod) {
- const newItem = await props.uploadMethod()
- if (newItem) {
- addAttachment(newItem)
- }
+ // 濡傛灉鎻愪緵浜嗚嚜瀹氫箟涓婁紶鏂规硶锛岀敱鐖剁粍浠惰礋璐f洿鏂板垪琛紙閫氳繃 setList锛�
+ // 杩欓噷涓嶅啀鑷姩娣诲姞锛岄伩鍏嶄笌鐖剁粍浠剁殑 setList 閲嶅
+ await props.uploadMethod()
}
emit('upload')
}
diff --git a/src/components/PIMTable/PIMTable.vue b/src/components/PIMTable/PIMTable.vue
index 01462f0..1480893 100644
--- a/src/components/PIMTable/PIMTable.vue
+++ b/src/components/PIMTable/PIMTable.vue
@@ -40,7 +40,7 @@
:fixed="item.fixed"
:label="item.label"
:prop="item.prop"
- :show-overflow-tooltip="item.dataType !== 'action'"
+ :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
:align="item.align"
:sortable="!!item.sortable"
:type="item.type"
diff --git a/src/components/PageHeader/index.vue b/src/components/PageHeader/index.vue
new file mode 100644
index 0000000..d8fc6fa
--- /dev/null
+++ b/src/components/PageHeader/index.vue
@@ -0,0 +1,53 @@
+<template>
+ <div class="page-header-wrapper">
+ <el-page-header @back="handleBack" :content="content">
+ <template #icon v-if="$slots.icon">
+ <slot name="icon"></slot>
+ </template>
+ <template #title v-if="$slots.title">
+ <slot name="title"></slot>
+ </template>
+ <template #content v-if="$slots.content">
+ <slot name="content"></slot>
+ </template>
+ <template #extra>
+ <slot name="extra">
+ <slot name="right-button"></slot>
+ </slot>
+ </template>
+ </el-page-header>
+ </div>
+</template>
+
+<script setup>
+import { useRouter } from 'vue-router'
+
+const props = defineProps({
+ content: {
+ type: String,
+ default: ''
+ }
+})
+
+const emit = defineEmits(['back'])
+
+const router = useRouter()
+
+const handleBack = () => {
+ emit('back')
+ // 榛樿杩斿洖鍒颁笂涓�绾�
+ router.back()
+}
+</script>
+
+<style scoped>
+.page-header-wrapper {
+ margin-bottom: 16px;
+}
+
+.page-header-wrapper :deep(.el-page-header__extra) {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+</style>
diff --git a/src/components/QRCodeGenerator/index.vue b/src/components/QRCodeGenerator/index.vue
index 1708130..fd44f44 100644
--- a/src/components/QRCodeGenerator/index.vue
+++ b/src/components/QRCodeGenerator/index.vue
@@ -1,70 +1,79 @@
<template>
<div class="qr-code-generator">
<!-- 浜岀淮鐮佺敓鎴愯〃鍗� -->
- <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form">
+ <el-form :model="form"
+ :rules="rules"
+ ref="formRef"
+ label-width="120px"
+ class="qr-form">
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="鏍囪瘑绫诲瀷" prop="type">
- <el-select v-model="form.type" placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷" style="width: 100%">
- <el-option label="浜岀淮鐮�" value="qrcode"></el-option>
- <el-option label="闃蹭吉鐮�" value="security"></el-option>
+ <el-form-item label="鏍囪瘑绫诲瀷"
+ prop="type">
+ <el-select v-model="form.type"
+ placeholder="璇烽�夋嫨鏍囪瘑绫诲瀷"
+ style="width: 100%">
+ <el-option label="浜岀淮鐮�"
+ value="qrcode"></el-option>
+ <el-option label="闃蹭吉鐮�"
+ value="security"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="鍐呭" prop="content">
- <el-input
- v-model="form.content"
- placeholder="璇疯緭鍏ヨ缂栫爜鐨勫唴瀹�"
- :type="form.type === 'security' ? 'textarea' : 'text'"
- :rows="form.type === 'security' ? 3 : 1"
- ></el-input>
+ <el-form-item label="鍐呭"
+ prop="content">
+ <el-input v-model="form.content"
+ placeholder="璇疯緭鍏ヨ缂栫爜鐨勫唴瀹�"
+ :type="form.type === 'security' ? 'textarea' : 'text'"
+ :rows="form.type === 'security' ? 3 : 1"></el-input>
</el-form-item>
</el-col>
</el-row>
-
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="灏哄" prop="size">
- <el-input-number
- v-model="form.size"
- :min="100"
- :max="500"
- :step="50"
- style="width: 100%"
- ></el-input-number>
+ <el-form-item label="灏哄"
+ prop="size">
+ <el-input-number v-model="form.size"
+ :min="100"
+ :max="500"
+ :step="50"
+ style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="杈硅窛" prop="margin">
- <el-input-number
- v-model="form.margin"
- :min="0"
- :max="10"
- :step="1"
- style="width: 100%"
- ></el-input-number>
+ <el-form-item label="杈硅窛"
+ prop="margin">
+ <el-input-number v-model="form.margin"
+ :min="0"
+ :max="10"
+ :step="1"
+ style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-row>
-
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="鍓嶆櫙鑹�" prop="foregroundColor">
- <el-color-picker v-model="form.foregroundColor" style="width: 100%"></el-color-picker>
+ <el-form-item label="鍓嶆櫙鑹�"
+ prop="foregroundColor">
+ <el-color-picker v-model="form.foregroundColor"
+ style="width: 100%"></el-color-picker>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="鑳屾櫙鑹�" prop="backgroundColor">
- <el-color-picker v-model="form.backgroundColor" style="width: 100%"></el-color-picker>
+ <el-form-item label="鑳屾櫙鑹�"
+ prop="backgroundColor">
+ <el-color-picker v-model="form.backgroundColor"
+ style="width: 100%"></el-color-picker>
</el-form-item>
</el-col>
</el-row>
-
<el-row :gutter="20">
<el-col :span="24">
<el-form-item>
- <el-button type="primary" @click="generateCode" :loading="generating">
+ <el-button type="primary"
+ @click="generateCode"
+ :loading="generating">
鐢熸垚{{ form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�' }}
</el-button>
<el-button @click="resetForm">閲嶇疆</el-button>
@@ -72,18 +81,17 @@
</el-col>
</el-row>
</el-form>
-
<!-- 鐢熸垚鐨勭爜鏄剧ず鍖哄煙 -->
- <div v-if="generatedCodeUrl" class="code-display">
+ <div v-if="generatedCodeUrl"
+ class="code-display">
<el-divider content-position="center">
{{ form.type === 'qrcode' ? '鐢熸垚鐨勪簩缁寸爜' : '鐢熸垚鐨勯槻浼爜' }}
</el-divider>
-
<div class="code-container">
<div class="code-image">
- <img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'" />
+ <img :src="generatedCodeUrl"
+ :alt="form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'" />
</div>
-
<div class="code-info">
<p><strong>鍐呭锛�</strong>{{ form.content }}</p>
<p><strong>绫诲瀷锛�</strong>{{ form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�' }}</p>
@@ -91,60 +99,71 @@
<p><strong>鐢熸垚鏃堕棿锛�</strong>{{ generateTime }}</p>
</div>
</div>
-
<div class="code-actions">
- <el-button type="success" @click="downloadCode" icon="Download">
+ <el-button type="success"
+ @click="downloadCode"
+ icon="Download">
涓嬭浇鍥剧墖
</el-button>
- <el-button type="primary" @click="copyToClipboard" icon="CopyDocument">
+ <el-button type="primary"
+ @click="copyToClipboard"
+ icon="CopyDocument">
澶嶅埗鍐呭
</el-button>
- <el-button @click="printCode" icon="Printer">
+ <el-button @click="printCode"
+ icon="Printer">
鎵撳嵃
</el-button>
</div>
</div>
-
<!-- 鎵归噺鐢熸垚瀵硅瘽妗� -->
- <el-dialog v-model="batchDialogVisible" title="鎵归噺鐢熸垚" width="600px">
- <el-form :model="batchForm" label-width="120px">
+ <el-dialog v-model="batchDialogVisible"
+ title="鎵归噺鐢熸垚"
+ width="600px">
+ <el-form :model="batchForm"
+ label-width="120px">
<el-form-item label="鐢熸垚鏁伴噺">
- <el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number>
+ <el-input-number v-model="batchForm.quantity"
+ :min="1"
+ :max="100"
+ style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item label="鍓嶇紑">
- <el-input v-model="batchForm.prefix" placeholder="璇疯緭鍏ュ墠缂�锛屽锛歅ROD_"></el-input>
+ <el-input v-model="batchForm.prefix"
+ placeholder="璇疯緭鍏ュ墠缂�锛屽锛歅ROD_"></el-input>
</el-form-item>
<el-form-item label="璧峰缂栧彿">
- <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number>
+ <el-input-number v-model="batchForm.startNumber"
+ :min="1"
+ style="width: 100%"></el-input-number>
</el-form-item>
</el-form>
-
<template #footer>
<div class="dialog-footer">
+ <el-button type="primary"
+ @click="generateBatchCodes">寮�濮嬬敓鎴�</el-button>
<el-button @click="batchDialogVisible = false">鍙栨秷</el-button>
- <el-button type="primary" @click="generateBatchCodes">寮�濮嬬敓鎴�</el-button>
</div>
</template>
</el-dialog>
-
<!-- 鎵归噺鐢熸垚缁撴灉 -->
- <div v-if="batchCodes.length > 0" class="batch-results">
+ <div v-if="batchCodes.length > 0"
+ class="batch-results">
<el-divider content-position="center">鎵归噺鐢熸垚缁撴灉</el-divider>
-
<div class="batch-grid">
- <div
- v-for="(code, index) in batchCodes"
- :key="index"
- class="batch-item"
- >
- <img :src="code.url" :alt="code.content" />
+ <div v-for="(code, index) in batchCodes"
+ :key="index"
+ class="batch-item">
+ <img :src="code.url"
+ :alt="code.content" />
<p class="batch-content">{{ code.content }}</p>
- <el-button size="small" @click="downloadSingleCode(code)">涓嬭浇</el-button>
+ <el-button size="small"
+ @click="downloadSingleCode(code)">涓嬭浇</el-button>
</div>
</div>
-
<div class="batch-actions">
- <el-button type="success" @click="downloadAllCodes">涓嬭浇鍏ㄩ儴</el-button>
+ <el-button type="success"
+ @click="downloadAllCodes">涓嬭浇鍏ㄩ儴</el-button>
<el-button @click="clearBatchCodes">娓呯┖缁撴灉</el-button>
</div>
</div>
@@ -152,390 +171,396 @@
</template>
<script setup>
-import { ref, reactive, computed, onMounted } from 'vue'
-import QRCode from 'qrcode'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { Download, CopyDocument, Printer } from '@element-plus/icons-vue'
+ import { ref, reactive, computed, onMounted } from "vue";
+ import QRCode from "qrcode";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import { Download, CopyDocument, Printer } from "@element-plus/icons-vue";
-// 瀹氫箟缁勪欢鍚嶇О
-defineOptions({
- name: 'QRCodeGenerator'
-})
+ // 瀹氫箟缁勪欢鍚嶇О
+ defineOptions({
+ name: "QRCodeGenerator",
+ });
-// 琛ㄥ崟鏁版嵁
-const form = reactive({
- type: 'qrcode',
- content: '',
- size: 200,
- margin: 2,
- foregroundColor: '#000000',
- backgroundColor: '#FFFFFF'
-})
+ // 琛ㄥ崟鏁版嵁
+ const form = reactive({
+ type: "qrcode",
+ content: "",
+ size: 200,
+ margin: 2,
+ foregroundColor: "#000000",
+ backgroundColor: "#FFFFFF",
+ });
-// 琛ㄥ崟楠岃瘉瑙勫垯
-const rules = {
- type: [{ required: true, message: '璇烽�夋嫨鏍囪瘑绫诲瀷', trigger: 'change' }],
- content: [{ required: true, message: '璇疯緭鍏ュ唴瀹�', trigger: 'blur' }]
-}
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ const rules = {
+ type: [{ required: true, message: "璇烽�夋嫨鏍囪瘑绫诲瀷", trigger: "change" }],
+ content: [{ required: true, message: "璇疯緭鍏ュ唴瀹�", trigger: "blur" }],
+ };
-// 鍝嶅簲寮忔暟鎹�
-const formRef = ref()
-const generating = ref(false)
-const generatedCodeUrl = ref('')
-const generateTime = ref('')
-const batchDialogVisible = ref(false)
-const batchForm = reactive({
- quantity: 10,
- prefix: '',
- startNumber: 1
-})
-const batchCodes = ref([])
+ // 鍝嶅簲寮忔暟鎹�
+ const formRef = ref();
+ const generating = ref(false);
+ const generatedCodeUrl = ref("");
+ const generateTime = ref("");
+ const batchDialogVisible = ref(false);
+ const batchForm = reactive({
+ quantity: 10,
+ prefix: "",
+ startNumber: 1,
+ });
+ const batchCodes = ref([]);
-// 鐢熸垚浜岀淮鐮佹垨闃蹭吉鐮�
-const generateCode = async () => {
- try {
- await formRef.value.validate()
-
- if (!form.content.trim()) {
- ElMessage.warning('璇疯緭鍏ヨ缂栫爜鐨勫唴瀹�')
- return
- }
-
- generating.value = true
-
- if (form.type === 'qrcode') {
- // 鐢熸垚浜岀淮鐮�
- generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
- width: form.size,
- margin: form.margin,
- color: {
- dark: form.foregroundColor,
- light: form.backgroundColor
- },
- errorCorrectionLevel: 'M'
- })
- } else {
- // 鐢熸垚闃蹭吉鐮侊紙浣跨敤浜岀淮鐮佹妧鏈紝浣嗗唴瀹规牸寮忎笉鍚岋級
- const securityContent = generateSecurityCode(form.content)
- generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
- width: form.size,
- margin: form.margin,
- color: {
- dark: form.foregroundColor,
- light: form.backgroundColor
- },
- errorCorrectionLevel: 'H' // 闃蹭吉鐮佷娇鐢ㄦ渶楂樼籂閿欑骇鍒�
- })
- }
-
- generateTime.value = new Date().toLocaleString()
- ElMessage.success('鐢熸垚鎴愬姛锛�')
-
- } catch (error) {
- console.error('鐢熸垚澶辫触:', error)
- ElMessage.error('鐢熸垚澶辫触锛�' + error.message)
- } finally {
- generating.value = false
- }
-}
+ // 鐢熸垚浜岀淮鐮佹垨闃蹭吉鐮�
+ const generateCode = async () => {
+ try {
+ await formRef.value.validate();
-// 鐢熸垚闃蹭吉鐮佸唴瀹�
-const generateSecurityCode = (content) => {
- const timestamp = Date.now()
- const random = Math.random().toString(36).substr(2, 8)
- return `SEC_${content}_${timestamp}_${random}`
-}
+ if (!form.content.trim()) {
+ ElMessage.warning("璇疯緭鍏ヨ缂栫爜鐨勫唴瀹�");
+ return;
+ }
-// 涓嬭浇鐢熸垚鐨勭爜
-const downloadCode = () => {
- if (!generatedCodeUrl.value) {
- ElMessage.warning('璇峰厛鐢熸垚鐮�')
- return
- }
-
- const a = document.createElement('a')
- a.href = generatedCodeUrl.value
- a.download = `${form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'}_${new Date().getTime()}.png`
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
- ElMessage.success('涓嬭浇鎴愬姛锛�')
-}
+ generating.value = true;
-// 澶嶅埗鍐呭鍒板壀璐存澘
-const copyToClipboard = async () => {
- try {
- await navigator.clipboard.writeText(form.content)
- ElMessage.success('鍐呭宸插鍒跺埌鍓创鏉�')
- } catch (error) {
- // 闄嶇骇鏂规
- const textArea = document.createElement('textarea')
- textArea.value = form.content
- document.body.appendChild(textArea)
- textArea.select()
- document.execCommand('copy')
- document.body.removeChild(textArea)
- ElMessage.success('鍐呭宸插鍒跺埌鍓创鏉�')
- }
-}
-
-// 鎵撳嵃鐮�
-const printCode = () => {
- if (!generatedCodeUrl.value) {
- ElMessage.warning('璇峰厛鐢熸垚鐮�')
- return
- }
-
- const printWindow = window.open('', '_blank')
- printWindow.document.write(`
- <html>
- <head>
- <title>鎵撳嵃${form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'}</title>
- <style>
- body { text-align: center; padding: 20px; }
- img { max-width: 100%; height: auto; }
- .info { margin: 20px 0; }
- </style>
- </head>
- <body>
- <h2>${form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'}</h2>
- <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'}" />
- <div class="info">
- <p><strong>鍐呭锛�</strong>${form.content}</p>
- <p><strong>鐢熸垚鏃堕棿锛�</strong>${generateTime.value}</p>
- </div>
- </body>
- </html>
- `)
- printWindow.document.close()
- printWindow.print()
-}
-
-// 閲嶇疆琛ㄥ崟
-const resetForm = () => {
- formRef.value.resetFields()
- generatedCodeUrl.value = ''
- generateTime.value = ''
- batchCodes.value = []
-}
-
-// 鎵归噺鐢熸垚
-const generateBatchCodes = async () => {
- if (!batchForm.prefix.trim()) {
- ElMessage.warning('璇疯緭鍏ュ墠缂�')
- return
- }
-
- batchCodes.value = []
- generating.value = true
-
- try {
- for (let i = 0; i < batchForm.quantity; i++) {
- const number = batchForm.startNumber + i
- const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}`
-
- let codeUrl
- if (form.type === 'qrcode') {
- codeUrl = await QRCode.toDataURL(content, {
+ if (form.type === "qrcode") {
+ // 鐢熸垚浜岀淮鐮�
+ generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
width: form.size,
margin: form.margin,
color: {
dark: form.foregroundColor,
- light: form.backgroundColor
- }
- })
+ light: form.backgroundColor,
+ },
+ errorCorrectionLevel: "M",
+ });
} else {
- const securityContent = generateSecurityCode(content)
- codeUrl = await QRCode.toDataURL(securityContent, {
+ // 鐢熸垚闃蹭吉鐮侊紙浣跨敤浜岀淮鐮佹妧鏈紝浣嗗唴瀹规牸寮忎笉鍚岋級
+ const securityContent = generateSecurityCode(form.content);
+ generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
width: form.size,
margin: form.margin,
color: {
dark: form.foregroundColor,
- light: form.backgroundColor
- }
- })
+ light: form.backgroundColor,
+ },
+ errorCorrectionLevel: "H", // 闃蹭吉鐮佷娇鐢ㄦ渶楂樼籂閿欑骇鍒�
+ });
}
-
- batchCodes.value.push({
- content,
- url: codeUrl
- })
+
+ generateTime.value = new Date().toLocaleString();
+ ElMessage.success("鐢熸垚鎴愬姛锛�");
+ } catch (error) {
+ console.error("鐢熸垚澶辫触:", error);
+ ElMessage.error("鐢熸垚澶辫触锛�" + error.message);
+ } finally {
+ generating.value = false;
}
-
- ElMessage.success(`鎵归噺鐢熸垚瀹屾垚锛屽叡鐢熸垚 ${batchForm.quantity} 涓爜`)
- batchDialogVisible.value = false
-
- } catch (error) {
- console.error('鎵归噺鐢熸垚澶辫触:', error)
- ElMessage.error('鎵归噺鐢熸垚澶辫触锛�' + error.message)
- } finally {
- generating.value = false
- }
-}
+ };
-// 涓嬭浇鍗曚釜鎵归噺鐢熸垚鐨勭爜
-const downloadSingleCode = (code) => {
- const a = document.createElement('a')
- a.href = code.url
- a.download = `${code.content}.png`
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
-}
+ // 鐢熸垚闃蹭吉鐮佸唴瀹�
+ const generateSecurityCode = content => {
+ const timestamp = Date.now();
+ const random = Math.random().toString(36).substr(2, 8);
+ return `SEC_${content}_${timestamp}_${random}`;
+ };
-// 涓嬭浇鎵�鏈夋壒閲忕敓鎴愮殑鐮�
-const downloadAllCodes = async () => {
- if (batchCodes.value.length === 0) {
- ElMessage.warning('娌℃湁鍙笅杞界殑鐮�')
- return
- }
-
- try {
- // 浣跨敤JSZip鎵撳寘涓嬭浇
- const JSZip = await import('jszip')
- const zip = new JSZip.default()
-
- batchCodes.value.forEach((code, index) => {
- // 灏哹ase64杞崲涓篵lob
- const base64Data = code.url.split(',')[1]
- const byteCharacters = atob(base64Data)
- const byteNumbers = new Array(byteCharacters.length)
- for (let i = 0; i < byteCharacters.length; i++) {
- byteNumbers[i] = byteCharacters.charCodeAt(i)
+ // 涓嬭浇鐢熸垚鐨勭爜
+ const downloadCode = () => {
+ if (!generatedCodeUrl.value) {
+ ElMessage.warning("璇峰厛鐢熸垚鐮�");
+ return;
+ }
+
+ const a = document.createElement("a");
+ a.href = generatedCodeUrl.value;
+ a.download = `${
+ form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"
+ }_${new Date().getTime()}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ ElMessage.success("涓嬭浇鎴愬姛锛�");
+ };
+
+ // 澶嶅埗鍐呭鍒板壀璐存澘
+ const copyToClipboard = async () => {
+ try {
+ await navigator.clipboard.writeText(form.content);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ } catch (error) {
+ // 闄嶇骇鏂规
+ const textArea = document.createElement("textarea");
+ textArea.value = form.content;
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textArea);
+ ElMessage.success("鍐呭宸插鍒跺埌鍓创鏉�");
+ }
+ };
+
+ // 鎵撳嵃鐮�
+ const printCode = () => {
+ if (!generatedCodeUrl.value) {
+ ElMessage.warning("璇峰厛鐢熸垚鐮�");
+ return;
+ }
+
+ const printWindow = window.open("", "_blank");
+ printWindow.document.write(`
+ <html>
+ <head>
+ <title>鎵撳嵃${form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"}</title>
+ <style>
+ body { text-align: center; padding: 20px; }
+ img { max-width: 100%; height: auto; }
+ .info { margin: 20px 0; }
+ </style>
+ </head>
+ <body>
+ <h2>${form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"}</h2>
+ <img src="${generatedCodeUrl.value}" alt="${
+ form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"
+ }" />
+ <div class="info">
+ <p><strong>鍐呭锛�</strong>${form.content}</p>
+ <p><strong>鐢熸垚鏃堕棿锛�</strong>${generateTime.value}</p>
+ </div>
+ </body>
+ </html>
+ `);
+ printWindow.document.close();
+ printWindow.print();
+ };
+
+ // 閲嶇疆琛ㄥ崟
+ const resetForm = () => {
+ formRef.value.resetFields();
+ generatedCodeUrl.value = "";
+ generateTime.value = "";
+ batchCodes.value = [];
+ };
+
+ // 鎵归噺鐢熸垚
+ const generateBatchCodes = async () => {
+ if (!batchForm.prefix.trim()) {
+ ElMessage.warning("璇疯緭鍏ュ墠缂�");
+ return;
+ }
+
+ batchCodes.value = [];
+ generating.value = true;
+
+ try {
+ for (let i = 0; i < batchForm.quantity; i++) {
+ const number = batchForm.startNumber + i;
+ const content = `${batchForm.prefix}${number
+ .toString()
+ .padStart(6, "0")}`;
+
+ let codeUrl;
+ if (form.type === "qrcode") {
+ codeUrl = await QRCode.toDataURL(content, {
+ width: form.size,
+ margin: form.margin,
+ color: {
+ dark: form.foregroundColor,
+ light: form.backgroundColor,
+ },
+ });
+ } else {
+ const securityContent = generateSecurityCode(content);
+ codeUrl = await QRCode.toDataURL(securityContent, {
+ width: form.size,
+ margin: form.margin,
+ color: {
+ dark: form.foregroundColor,
+ light: form.backgroundColor,
+ },
+ });
+ }
+
+ batchCodes.value.push({
+ content,
+ url: codeUrl,
+ });
}
- const byteArray = new Uint8Array(byteNumbers)
-
- zip.file(`${code.content}.png`, byteArray)
- })
-
- const content = await zip.generateAsync({ type: 'blob' })
- const a = document.createElement('a')
- a.href = URL.createObjectURL(content)
- a.download = `鎵归噺${form.type === 'qrcode' ? '浜岀淮鐮�' : '闃蹭吉鐮�'}_${new Date().getTime()}.zip`
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
- URL.revokeObjectURL(a.href)
-
- ElMessage.success('鎵归噺涓嬭浇瀹屾垚锛�')
- } catch (error) {
- console.error('鎵归噺涓嬭浇澶辫触:', error)
- ElMessage.error('鎵归噺涓嬭浇澶辫触锛岃閫愪釜涓嬭浇')
- }
-}
-// 娓呯┖鎵归噺鐢熸垚缁撴灉
-const clearBatchCodes = () => {
- batchCodes.value = []
-}
+ ElMessage.success(`鎵归噺鐢熸垚瀹屾垚锛屽叡鐢熸垚 ${batchForm.quantity} 涓爜`);
+ batchDialogVisible.value = false;
+ } catch (error) {
+ console.error("鎵归噺鐢熸垚澶辫触:", error);
+ ElMessage.error("鎵归噺鐢熸垚澶辫触锛�" + error.message);
+ } finally {
+ generating.value = false;
+ }
+ };
-// 鏆撮湶鏂规硶缁欑埗缁勪欢
-defineExpose({
- generateCode,
- downloadCode,
- resetForm,
- form
-})
+ // 涓嬭浇鍗曚釜鎵归噺鐢熸垚鐨勭爜
+ const downloadSingleCode = code => {
+ const a = document.createElement("a");
+ a.href = code.url;
+ a.download = `${code.content}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ };
+
+ // 涓嬭浇鎵�鏈夋壒閲忕敓鎴愮殑鐮�
+ const downloadAllCodes = async () => {
+ if (batchCodes.value.length === 0) {
+ ElMessage.warning("娌℃湁鍙笅杞界殑鐮�");
+ return;
+ }
+
+ try {
+ // 浣跨敤JSZip鎵撳寘涓嬭浇
+ const JSZip = await import("jszip");
+ const zip = new JSZip.default();
+
+ batchCodes.value.forEach((code, index) => {
+ // 灏哹ase64杞崲涓篵lob
+ const base64Data = code.url.split(",")[1];
+ const byteCharacters = atob(base64Data);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+
+ zip.file(`${code.content}.png`, byteArray);
+ });
+
+ const content = await zip.generateAsync({ type: "blob" });
+ const a = document.createElement("a");
+ a.href = URL.createObjectURL(content);
+ a.download = `鎵归噺${
+ form.type === "qrcode" ? "浜岀淮鐮�" : "闃蹭吉鐮�"
+ }_${new Date().getTime()}.zip`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(a.href);
+
+ ElMessage.success("鎵归噺涓嬭浇瀹屾垚锛�");
+ } catch (error) {
+ console.error("鎵归噺涓嬭浇澶辫触:", error);
+ ElMessage.error("鎵归噺涓嬭浇澶辫触锛岃閫愪釜涓嬭浇");
+ }
+ };
+
+ // 娓呯┖鎵归噺鐢熸垚缁撴灉
+ const clearBatchCodes = () => {
+ batchCodes.value = [];
+ };
+
+ // 鏆撮湶鏂规硶缁欑埗缁勪欢
+ defineExpose({
+ generateCode,
+ downloadCode,
+ resetForm,
+ form,
+ });
</script>
<style scoped>
-.qr-code-generator {
- padding: 20px;
-}
+ .qr-code-generator {
+ padding: 20px;
+ }
-.qr-form {
- background: #f8f9fa;
- padding: 20px;
- border-radius: 8px;
- margin-bottom: 20px;
-}
+ .qr-form {
+ background: #f8f9fa;
+ padding: 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ }
-.code-display {
- margin-top: 30px;
-}
+ .code-display {
+ margin-top: 30px;
+ }
-.code-container {
- display: flex;
- justify-content: center;
- align-items: flex-start;
- gap: 40px;
- margin: 20px 0;
-}
-
-.code-image img {
- border: 2px solid #e0e0e0;
- border-radius: 8px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-}
-
-.code-info {
- text-align: left;
- min-width: 200px;
-}
-
-.code-info p {
- margin: 8px 0;
- color: #666;
-}
-
-.code-actions {
- text-align: center;
- margin: 20px 0;
-}
-
-.code-actions .el-button {
- margin: 0 10px;
-}
-
-.batch-results {
- margin-top: 30px;
-}
-
-.batch-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 20px;
- margin: 20px 0;
-}
-
-.batch-item {
- text-align: center;
- padding: 15px;
- border: 1px solid #e0e0e0;
- border-radius: 8px;
- background: #fff;
-}
-
-.batch-item img {
- width: 100px;
- height: 100px;
- margin-bottom: 10px;
-}
-
-.batch-content {
- font-size: 12px;
- color: #666;
- margin: 10px 0;
- word-break: break-all;
-}
-
-.batch-actions {
- text-align: center;
- margin: 20px 0;
-}
-
-.batch-actions .el-button {
- margin: 0 10px;
-}
-
-@media (max-width: 768px) {
.code-container {
- flex-direction: column;
- align-items: center;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ gap: 40px;
+ margin: 20px 0;
}
-
+
+ .code-image img {
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ .code-info {
+ text-align: left;
+ min-width: 200px;
+ }
+
+ .code-info p {
+ margin: 8px 0;
+ color: #666;
+ }
+
+ .code-actions {
+ text-align: center;
+ margin: 20px 0;
+ }
+
+ .code-actions .el-button {
+ margin: 0 10px;
+ }
+
+ .batch-results {
+ margin-top: 30px;
+ }
+
.batch-grid {
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: 20px;
+ margin: 20px 0;
}
-}
+
+ .batch-item {
+ text-align: center;
+ padding: 15px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #fff;
+ }
+
+ .batch-item img {
+ width: 100px;
+ height: 100px;
+ margin-bottom: 10px;
+ }
+
+ .batch-content {
+ font-size: 12px;
+ color: #666;
+ margin: 10px 0;
+ word-break: break-all;
+ }
+
+ .batch-actions {
+ text-align: center;
+ margin: 20px 0;
+ }
+
+ .batch-actions .el-button {
+ margin: 0 10px;
+ }
+
+ @media (max-width: 768px) {
+ .code-container {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .batch-grid {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ }
+ }
</style>
diff --git a/src/layout/components/NotificationCenter/index.vue b/src/layout/components/NotificationCenter/index.vue
index df42650..6098153 100644
--- a/src/layout/components/NotificationCenter/index.vue
+++ b/src/layout/components/NotificationCenter/index.vue
@@ -107,8 +107,8 @@
}
const params = {
consigneeId: consigneeId,
- pageNum: pageNum.value,
- pageSize: pageSize.value,
+ current: pageNum.value,
+ size: pageSize.value,
status: activeTab.value === 'read' ? 1 : 0
}
const res = await listMessage(params)
diff --git a/src/main.js b/src/main.js
index abaac2e..0b3f714 100644
--- a/src/main.js
+++ b/src/main.js
@@ -52,6 +52,8 @@
import DictTag from "@/components/DictTag";
// 琛ㄦ牸缁勪欢
import PIMTable from "@/components/PIMTable/PIMTable.vue";
+// 椤甸潰澶撮儴缁勪欢
+import PageHeader from "@/components/PageHeader/index.vue";
import { getToken } from "@/utils/auth";
import {
@@ -93,6 +95,7 @@
app.component("RightToolbar", RightToolbar);
app.component("Editor", Editor);
app.component("PIMTable", PIMTable);
+app.component("PageHeader", PageHeader);
app.use(router);
app.use(store);
diff --git a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
index a269375..01650d4 100644
--- a/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
+++ b/src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -17,19 +17,18 @@
<el-row>
<el-col :span="24">
<el-form-item label="鐢宠閮ㄩ棬锛�" prop="approveDeptName">
- <el-input v-model="form.approveDeptName" placeholder="璇疯緭鍏�" clearable/>
-<!-- <el-select-->
-<!-- disabled-->
-<!-- v-model="form.approveDeptId"-->
-<!-- placeholder="閫夋嫨閮ㄩ棬"-->
-<!-- >-->
-<!-- <el-option-->
-<!-- v-for="user in productOptions"-->
-<!-- :key="user.deptId"-->
-<!-- :label="user.deptName"-->
-<!-- :value="user.deptId"-->
-<!-- />-->
-<!-- </el-select>-->
+<!-- <el-input v-model="form.approveDeptName" placeholder="璇疯緭鍏�" clearable/>-->
+ <el-select
+ v-model="form.approveDeptId"
+ placeholder="閫夋嫨閮ㄩ棬"
+ >
+ <el-option
+ v-for="user in productOptions"
+ :key="user.deptId"
+ :label="user.deptName"
+ :value="user.deptId"
+ />
+ </el-select>
</el-form-item>
</el-col>
</el-row>
diff --git a/src/views/collaborativeApproval/noticeManagement/index.vue b/src/views/collaborativeApproval/noticeManagement/index.vue
index 6b0ea98..d92adea 100644
--- a/src/views/collaborativeApproval/noticeManagement/index.vue
+++ b/src/views/collaborativeApproval/noticeManagement/index.vue
@@ -855,6 +855,9 @@
color: #606266;
line-height: 1.6;
font-size: 14px;
+ word-break: break-all;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
}
.card-footer {
diff --git a/src/views/financialManagement/expenseManagement/Form.vue b/src/views/financialManagement/expenseManagement/Form.vue
deleted file mode 100644
index 9cfe5da..0000000
--- a/src/views/financialManagement/expenseManagement/Form.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<template>
- <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
- <el-form-item label="鏀嚭鏃ユ湡" prop="expenseDate">
- <el-date-picker
- style="width: 100%"
- v-model="form.expenseDate"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- clearable
- />
- </el-form-item>
- <el-form-item label="鏀嚭绫诲瀷" prop="expenseType">
- <el-select
- v-model="form.expenseType"
- placeholder="璇烽�夋嫨"
- clearable
- >
- <el-option :label="item.label" :value="item.value" v-for="(item,index) in expense_types" :key="index" />
- </el-select>
- </el-form-item>
- <el-form-item label="渚涘簲鍟嗗悕绉�" prop="supplierName">
- <el-input v-model="form.supplierName" placeholder="璇疯緭鍏�" />
- </el-form-item>
- <el-form-item label="鏀嚭閲戦" prop="expenseMoney">
- <el-input-number :step="0.01" :min="0" style="width: 100%"
- v-model="form.expenseMoney"
- placeholder="璇疯緭鍏�"
- />
- </el-form-item>
- <el-form-item label="鏀嚭鎻忚堪" prop="expenseDescribed">
- <el-input v-model="form.expenseDescribed" placeholder="璇疯緭鍏�" />
- </el-form-item>
- <el-form-item label="浠樻鏂瑰紡" prop="expenseMethod">
- <el-select
- v-model="form.expenseMethod"
- placeholder="璇烽�夋嫨"
- clearable
- >
- <el-option :label="item.label" :value="item.value" v-for="(item,index) in checkout_payment" :key="index" />
- </el-select>
- </el-form-item>
- <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNumber">
- <el-input v-model="form.invoiceNumber" placeholder="璇疯緭鍏�" />
- </el-form-item>
- <el-form-item label="澶囨敞" prop="note">
- <el-input
- v-model="form.note"
- placeholder="澶囨敞"
- />
- </el-form-item>
-
- </el-form>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getAccountExpense } from "@/api/financialManagement/expenseManagement";
-import {ref} from "vue";
-const { proxy } = getCurrentInstance();
-
-
-defineOptions({
- name: "鏂板鏀嚭",
-});
-const { expense_types } = proxy.useDict("expense_types");
-const { checkout_payment } = proxy.useDict("checkout_payment");
-const formRef = ref(null);
-const formRules = {
- supplierName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
- expenseMoney: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
- expenseDescribed: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
- expenseDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
- expenseType: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
- expenseMethod: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
-}
-
-const { form, resetForm } = useFormData({
- expenseDate: undefined, // 鏀嚭鏃ユ湡
- expenseType: undefined, // 鏀嚭绫诲瀷
- supplierName: undefined, // 瀹㈡埛鍚嶇О
- expenseMoney: undefined, // 鏀嚭閲戦
- expenseDescribed: undefined, // 鏀嚭鎻忚堪
- expenseMethod: undefined, // 鏀舵鏂瑰紡
- invoiceNumber: undefined, // 鍙戠エ鍙风爜
- note: undefined, // 澶囨敞
-});
-
-const loadForm = async (id) => {
- const { code, data } = await getAccountExpense(id);
- if (code == 200) {
- form.expenseDate = data.expenseDate;
- form.expenseType = data.expenseType;
- form.supplierName = data.supplierName;
- form.expenseMoney = data.expenseMoney;
- form.expenseDescribed = data.expenseDescribed;
- form.expenseMethod = data.expenseMethod;
- form.invoiceNumber = data.invoiceNumber;
- form.note = data.note;
- }
-};
-
-// 娓呴櫎琛ㄥ崟鏍¢獙鐘舵��
-const clearValidate = () => {
- formRef.value?.clearValidate();
-};
-
-// 閲嶇疆琛ㄥ崟鏁版嵁鍜屾牎楠岀姸鎬�
-const resetFormAndValidate = () => {
- resetForm();
- clearValidate();
-};
-
-defineExpose({
- form,
- loadForm,
- resetForm,
- clearValidate,
- resetFormAndValidate,
- formRef,
-});
-</script>
diff --git a/src/views/financialManagement/expenseManagement/Modal.vue b/src/views/financialManagement/expenseManagement/Modal.vue
index 8e5b171..4d743c1 100644
--- a/src/views/financialManagement/expenseManagement/Modal.vue
+++ b/src/views/financialManagement/expenseManagement/Modal.vue
@@ -1,20 +1,75 @@
<template>
- <el-dialog :title="modalOptions.title" v-model="visible" @close="close" width="30%">
- <Form ref="formRef"></Form>
- <template #footer>
- <el-button type="primary" @click="sendForm" :loading="loading">
- {{ modalOptions.confirmText }}
- </el-button>
- <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
- </template>
- </el-dialog>
+ <FormDialog
+ v-model="dialogVisible"
+ :title="dialogTitle"
+ :operationType="operationType"
+ width="50%"
+ @confirm="sendForm"
+ @close="close"
+ @cancel="close"
+ >
+ <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
+ <el-form-item label="鏀嚭鏃ユ湡" prop="expenseDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.expenseDate"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鏀嚭绫诲瀷" prop="expenseType">
+ <el-select
+ v-model="form.expenseType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in expense_types" :key="index" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟嗗悕绉�" prop="supplierName">
+ <el-input v-model="form.supplierName" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item label="鏀嚭閲戦" prop="expenseMoney">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
+ v-model="form.expenseMoney"
+ placeholder="璇疯緭鍏�"
+ />
+ </el-form-item>
+ <el-form-item label="鏀嚭鎻忚堪" prop="expenseDescribed">
+ <el-input v-model="form.expenseDescribed" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item label="浠樻鏂瑰紡" prop="expenseMethod">
+ <el-select
+ v-model="form.expenseMethod"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in checkout_payment" :key="index" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNumber">
+ <el-input v-model="form.invoiceNumber" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="note">
+ <el-input
+ v-model="form.note"
+ placeholder="澶囨敞"
+ />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
</template>
<script setup>
-import { useModal } from "@/hooks/useModal";
-import { add, update } from "@/api/financialManagement/expenseManagement";
-import Form from "./Form.vue";
+import { add, update, getAccountExpense } from "@/api/financialManagement/expenseManagement";
import { ElMessage } from "element-plus";
+import useFormData from "@/hooks/useFormData";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { ref } from "vue";
+
const { proxy } = getCurrentInstance()
defineOptions({
@@ -23,43 +78,96 @@
const emits = defineEmits(["success"]);
-const formRef = ref();
-const {
- id,
- visible,
- loading,
- openModal,
- modalOptions,
- handleConfirm,
- closeModal,
-} = useModal({ title: "鏀嚭" });
+const formRef = ref(null);
+const dialogVisible = ref(false);
+const operationType = ref("add"); // add | edit
+const id = ref(undefined);
+const submitting = ref(false);
+
+const dialogTitle = (type) => {
+ if (type === "edit") return "缂栬緫鏀嚭";
+ return "鏂板鏀嚭";
+};
+
+const { expense_types } = proxy.useDict("expense_types");
+const { checkout_payment } = proxy.useDict("checkout_payment");
+
+const formRules = {
+ supplierName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ expenseMoney: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ expenseDescribed: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ expenseDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ expenseType: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ expenseMethod: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+}
+
+const { form, resetForm } = useFormData({
+ expenseDate: undefined, // 鏀嚭鏃ユ湡
+ expenseType: undefined, // 鏀嚭绫诲瀷
+ supplierName: undefined, // 渚涘簲鍟嗗悕绉�
+ expenseMoney: undefined, // 鏀嚭閲戦
+ expenseDescribed: undefined, // 鏀嚭鎻忚堪
+ expenseMethod: undefined, // 浠樻鏂瑰紡
+ invoiceNumber: undefined, // 鍙戠エ鍙风爜
+ note: undefined, // 澶囨敞
+});
const sendForm = () => {
- proxy.$refs.formRef.$refs.formRef.validate(async valid => {
- if (valid) {
- const {code} = id.value
- ? await update({id: id.value, ...formRef.value.form})
- : await add(formRef.value.form);
- if (code == 200) {
- emits("success");
- ElMessage({message: "鎿嶄綔鎴愬姛", type: "success"});
- close();
- } else {
- loading.value = false;
- }
- }
- })
+ if (submitting.value) return;
+ formRef.value?.validate(async (valid) => {
+ if (valid) {
+ submitting.value = true;
+ try {
+ const { code } = id.value
+ ? await update({ id: id.value, ...form })
+ : await add(form);
+ if (code == 200) {
+ emits("success");
+ ElMessage({ message: "鎿嶄綔鎴愬姛", type: "success" });
+ close();
+ }
+ } finally {
+ submitting.value = false;
+ }
+ }
+ })
};
const close = () => {
- formRef.value.resetFormAndValidate();
- closeModal();
+ resetForm();
+ formRef.value?.clearValidate();
+ id.value = undefined;
+ dialogVisible.value = false;
};
-const loadForm = async (id) => {
- openModal(id);
- await nextTick();
- formRef.value.loadForm(id);
+const loadForm = async (rowId) => {
+ operationType.value = "edit";
+ id.value = rowId;
+ dialogVisible.value = true;
+ if (rowId) {
+ const { code, data } = await getAccountExpense(rowId);
+ if (code == 200) {
+ form.expenseDate = data.expenseDate;
+ form.expenseType = data.expenseType;
+ form.supplierName = data.supplierName;
+ form.expenseMoney = data.expenseMoney;
+ form.expenseDescribed = data.expenseDescribed;
+ form.expenseMethod = data.expenseMethod;
+ form.invoiceNumber = data.invoiceNumber;
+ form.note = data.note;
+ }
+ } else {
+ resetForm();
+ formRef.value?.clearValidate();
+ }
+};
+
+const openModal = () => {
+ operationType.value = "add";
+ id.value = undefined;
+ resetForm();
+ formRef.value?.clearValidate();
+ dialogVisible.value = true;
};
defineExpose({
diff --git a/src/views/financialManagement/expenseManagement/index.vue b/src/views/financialManagement/expenseManagement/index.vue
index a45c32d..801fa1f 100644
--- a/src/views/financialManagement/expenseManagement/index.vue
+++ b/src/views/financialManagement/expenseManagement/index.vue
@@ -34,8 +34,8 @@
<el-button
type="danger"
icon="Delete"
- :disabled="multipleList.length <= 0"
- @click="deleteRow(multipleList.map((item) => item.id))"
+ :disabled="multipleList.length <= 0 || hasBusinessIdInSelection"
+ @click="handleBatchDelete"
>
鎵归噺鍒犻櫎
</el-button>
@@ -55,12 +55,17 @@
@pagination="changePage"
>
<template #operation="{ row }">
- <el-button type="primary" text @click="edit(row.id)" icon="editPen">
+ <el-button
+ type="primary"
+ link
+ :disabled="!!row.businessId"
+ @click="edit(row.id)"
+ >
缂栬緫
</el-button>
<el-button
type="primary"
- text
+ link
@click="openFilesFormDia(row)"
>
闄勪欢
@@ -69,18 +74,27 @@
</PIMTable>
</div>
<Modal ref="modalRef" @success="getTableData"></Modal>
- <files-dia ref="filesDia"></files-dia>
+ <FileListDialog
+ ref="fileListRef"
+ v-model="fileListDialogVisible"
+ :show-upload-button="true"
+ :show-delete-button="true"
+ :upload-method="handleUpload"
+ :delete-method="handleFileDelete"
+ />
</div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { listPage, delAccountExpense } from "@/api/financialManagement/expenseManagement";
-import { onMounted, getCurrentInstance } from "vue";
+import { listPage, delAccountExpense, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/expenseManagement";
+import { onMounted, getCurrentInstance, ref, computed } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
-import FilesDia from "../revenueManagement/filesDia.vue";
+import FileListDialog from "@/components/Dialog/FileListDialog.vue";
+import request from "@/utils/request";
+import { getToken } from "@/utils/auth";
defineOptions({
name: "鏀嚭绠$悊",
@@ -92,7 +106,10 @@
const modalRef = ref();
const { checkout_payment } = proxy.useDict("checkout_payment");
const { expense_types } = proxy.useDict("expense_types");
-const filesDia = ref()
+const fileListRef = ref(null);
+const fileListDialogVisible = ref(false);
+const currentFileRow = ref(null);
+const accountType = ref('鏀嚭');
const {
filters,
@@ -111,7 +128,6 @@
[
{
label: "鏀嚭鏃ユ湡",
- align: "center",
prop: "expenseDate",
},
{
@@ -129,19 +145,16 @@
},
{
label: "渚涘簲鍟嗗悕绉�",
- align: "center",
prop: "supplierName",
},
{
label: "鏀嚭閲戦",
- align: "center",
prop: "expenseMoney",
},
{
label: "鏀嚭鎻忚堪",
- align: "center",
prop: "expenseDescribed",
},
@@ -149,6 +162,7 @@
label: "浠樻鏂瑰紡",
align: "center",
prop: "expenseMethod",
+ width: '120',
dataType: "tag",
formatData: (params) => {
if (checkout_payment.value.find((m) => m.value == params)) {
@@ -160,24 +174,20 @@
},
{
label: "鍙戠エ鍙风爜",
- align: "center",
prop: "invoiceNumber",
},
{
label: "澶囨敞",
- align: "center",
prop: "note",
},
{
label: "褰曞叆浜�",
- align: "center",
prop: "inputUser",
},
{
label: "褰曞叆鏃ユ湡",
- align: "center",
prop: "inputTime",
},
@@ -187,7 +197,7 @@
dataType: "slot",
slot: "operation",
align: "center",
- width: "200px",
+ width: "160px",
},
]
);
@@ -197,10 +207,21 @@
multipleList.value = selectionList;
};
+// 鍒ゆ柇閫変腑鐨勯」涓槸鍚︽湁 businessId
+const hasBusinessIdInSelection = computed(() => {
+ return multipleList.value.some(item => item.businessId);
+});
+
const add = () => {
modalRef.value.openModal();
};
const edit = (id) => {
+ // 妫�鏌ュ綋鍓嶈鏄惁鏈� businessId
+ const row = dataList.value.find(item => item.id === id);
+ if (row && row.businessId) {
+ proxy.$modal.msgWarning("璇ヨ褰曞凡鍏宠仈涓氬姟锛屼笉鑳界紪杈�");
+ return;
+ }
modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
@@ -209,6 +230,25 @@
onCurrentChange(page);
};
const deleteRow = (id) => {
+ // 濡傛灉鏄暟缁勶紝妫�鏌ユ槸鍚︽湁 businessId
+ if (Array.isArray(id)) {
+ const hasBusinessId = id.some(itemId => {
+ const row = dataList.value.find(item => item.id === itemId);
+ return row && row.businessId;
+ });
+ if (hasBusinessId) {
+ proxy.$modal.msgWarning("閫変腑鐨勮褰曚腑鍖呭惈宸插叧鑱斾笟鍔$殑璁板綍锛屼笉鑳藉垹闄�");
+ return;
+ }
+ } else {
+ // 鍗曚釜鍒犻櫎锛屾鏌ユ槸鍚︽湁 businessId
+ const row = dataList.value.find(item => item.id === id);
+ if (row && row.businessId) {
+ proxy.$modal.msgWarning("璇ヨ褰曞凡鍏宠仈涓氬姟锛屼笉鑳藉垹闄�");
+ return;
+ }
+ }
+
ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ暟鎹�, 鏄惁缁х画?", "鎻愮ず", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
@@ -223,6 +263,23 @@
getTableData();
}
});
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (multipleList.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+
+ // 妫�鏌ユ槸鍚︽湁 businessId
+ if (hasBusinessIdInSelection.value) {
+ proxy.$modal.msgWarning("閫変腑鐨勮褰曚腑鍖呭惈宸插叧鑱斾笟鍔$殑璁板綍锛屼笉鑳藉垹闄�");
+ return;
+ }
+
+ const ids = multipleList.value.map((item) => item.id);
+ deleteRow(ids);
};
const changeDaterange = (value) => {
@@ -252,10 +309,154 @@
});
};
// 鎵撳紑闄勪欢寮规
-const openFilesFormDia = (row) => {
- nextTick(() => {
- filesDia.value?.openDialog( row,'鏀嚭')
- })
+const openFilesFormDia = async (row) => {
+ currentFileRow.value = row;
+ accountType.value = '鏀嚭';
+ try {
+ const res = await fileListPage({
+ accountId: row.id,
+ accountType: accountType.value,
+ current: 1,
+ size: 100
+ });
+ if (res.code === 200 && fileListRef.value) {
+ // 灏嗘暟鎹浆鎹负 FileListDialog 闇�瑕佺殑鏍煎紡
+ const fileList = (res.data?.records || []).map(item => ({
+ name: item.name,
+ url: item.url,
+ id: item.id,
+ ...item
+ }));
+ fileListRef.value.open(fileList);
+ fileListDialogVisible.value = true;
+ }
+ } catch (error) {
+ proxy.$modal.msgError("鑾峰彇闄勪欢鍒楄〃澶辫触");
+ }
+};
+
+// 涓婁紶闄勪欢
+const handleUpload = async () => {
+ if (!currentFileRow.value) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨鏁版嵁");
+ return null;
+ }
+
+ return new Promise((resolve) => {
+ // 鍒涘缓涓�涓殣钘忕殑鏂囦欢杈撳叆鍏冪礌
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.style.display = 'none';
+ input.onchange = async (e) => {
+ const file = e.target.files[0];
+ if (!file) {
+ resolve(null);
+ return;
+ }
+
+ try {
+ // 浣跨敤 FormData 涓婁紶鏂囦欢
+ const formData = new FormData();
+ formData.append('file', file);
+
+ const uploadRes = await request({
+ url: '/file/upload',
+ method: 'post',
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: `Bearer ${getToken()}`
+ }
+ });
+
+ if (uploadRes.code === 200) {
+ // 淇濆瓨闄勪欢淇℃伅
+ const fileData = {
+ accountId: currentFileRow.value.id,
+ accountType: accountType.value,
+ name: uploadRes.data.originalName || file.name,
+ url: uploadRes.data.tempPath || uploadRes.data.url
+ };
+
+ const saveRes = await fileAdd(fileData);
+ if (saveRes.code === 200) {
+ proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
+ // 閲嶆柊鍔犺浇鏂囦欢鍒楄〃
+ const listRes = await fileListPage({
+ accountId: currentFileRow.value.id,
+ accountType: accountType.value,
+ current: 1,
+ size: 100
+ });
+ if (listRes.code === 200 && fileListRef.value) {
+ const fileList = (listRes.data?.records || []).map(item => ({
+ name: item.name,
+ url: item.url,
+ id: item.id,
+ ...item
+ }));
+ fileListRef.value.setList(fileList);
+ }
+ // 杩斿洖鏂版枃浠朵俊鎭�
+ resolve({
+ name: fileData.name,
+ url: fileData.url,
+ id: saveRes.data?.id
+ });
+ } else {
+ proxy.$modal.msgError(saveRes.msg || "鏂囦欢淇濆瓨澶辫触");
+ resolve(null);
+ }
+ } else {
+ proxy.$modal.msgError(uploadRes.msg || "鏂囦欢涓婁紶澶辫触");
+ resolve(null);
+ }
+ } catch (error) {
+ proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+ resolve(null);
+ } finally {
+ document.body.removeChild(input);
+ }
+ };
+
+ document.body.appendChild(input);
+ input.click();
+ });
+};
+
+// 鍒犻櫎闄勪欢
+const handleFileDelete = async (row) => {
+ try {
+ const res = await fileDel([row.id]);
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ // 閲嶆柊鍔犺浇鏂囦欢鍒楄〃
+ if (currentFileRow.value && fileListRef.value) {
+ const listRes = await fileListPage({
+ accountId: currentFileRow.value.id,
+ accountType: accountType.value,
+ current: 1,
+ size: 100
+ });
+ if (listRes.code === 200) {
+ const fileList = (listRes.data?.records || []).map(item => ({
+ name: item.name,
+ url: item.url,
+ id: item.id,
+ ...item
+ }));
+ fileListRef.value.setList(fileList);
+ }
+ }
+ return true; // 杩斿洖 true 琛ㄧず鍒犻櫎鎴愬姛锛岀粍浠朵細鏇存柊鍒楄〃
+ } else {
+ proxy.$modal.msgError(res.msg || "鍒犻櫎澶辫触");
+ return false;
+ }
+ } catch (error) {
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ return false;
+ }
};
onMounted(() => {
diff --git a/src/views/financialManagement/financialStatements/index.vue b/src/views/financialManagement/financialStatements/index.vue
index e5f9b23..88aa5d2 100644
--- a/src/views/financialManagement/financialStatements/index.vue
+++ b/src/views/financialManagement/financialStatements/index.vue
@@ -1,16 +1,16 @@
<template>
<div style="padding: 20px;">
- <!-- 椤甸潰鏍囬鍜屾棩鏈熺瓫閫� -->
+ <!-- 椤甸潰鏍囬鍜屾湀浠界瓫閫� -->
<div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
<el-date-picker
v-model="dateRange"
- type="daterange"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
+ type="monthrange"
+ format="YYYY-MM"
+ value-format="YYYY-MM"
range-separator="鑷�"
- start-placeholder="寮�濮嬫棩鏈�"
- end-placeholder="缁撴潫鏃ユ湡"
- clearable
+ start-placeholder="寮�濮嬫湀浠�"
+ end-placeholder="缁撴潫鏈堜唤"
+ :disabled-date="disabledDate"
@change="handleDateChange"
class="w-full md:w-auto"
style="margin-right: 30px;"
@@ -130,7 +130,7 @@
</template>
<script setup>
-import { ref, computed, onMounted, reactive } from 'vue';
+import { ref, computed, onMounted, reactive, nextTick, getCurrentInstance } from 'vue';
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements";
@@ -138,6 +138,7 @@
// 鏃ユ湡鑼冨洿
const dateRange = ref(null);
+const { proxy } = getCurrentInstance();
const chartStyle = {
width: '100%',
height: '100%', // 璁剧疆鍥捐〃瀹瑰櫒鐨勯珮搴�
@@ -172,22 +173,35 @@
return `<div>${axisLabel}</div><div>${rows}</div>`
}
})
-const months = ['1鏈�','2鏈�','3鏈�','4鏈�','5鏈�','6鏈�','7鏈�','8鏈�','9鏈�','10鏈�','11鏈�','12鏈�'];
const lineSeries0 = ref([])
const lineSeries1 = ref([])
+
+// 鏍规嵁鏈堜唤鑼冨洿鐢熸垚 x 杞存暟鎹�
+const generateMonthLabels = (startMonth, endMonth) => {
+ const labels = [];
+ let current = dayjs(startMonth);
+ const end = dayjs(endMonth);
+
+ while (current.isBefore(end) || current.isSame(end, 'month')) {
+ labels.push(`${current.month() + 1}鏈坄);
+ current = current.add(1, 'month');
+ }
+
+ return labels;
+};
const xAxis0 = ref([
{
type: 'category',
axisTick: { show: true, alignWithLabel: true },
- data: months,
+ data: [],
},
]);
const xAxis1 = ref([
{
type: 'category',
axisTick: { show: true, alignWithLabel: true },
- data: months,
+ data: [],
},
]);
const yAxis0 = [
@@ -232,9 +246,10 @@
left: '60%',
orient: 'vertical',
icon: 'circle',
- data: pieData0.value.map(item => item.name),
+ data: (pieData0.value || []).filter(item => item && item.name).map(item => item.name),
formatter: function(name) {
- const item = pieData0.value.find(i => i.name === name);
+ if (!name) return '';
+ const item = pieData0.value.find(i => i && i.name === name);
if (!item) return name;
return `${name} | ${item.percent} ${item.amount}`;
},
@@ -250,9 +265,10 @@
left: '60%',
orient: 'vertical',
icon: 'circle',
- data: pieData1.value.map(item => item.name),
+ data: (pieData1.value || []).filter(item => item && item.name).map(item => item.name),
formatter: function(name) {
- const item = pieData1.value.find(i => i.name === name);
+ if (!name) return '';
+ const item = pieData1.value.find(i => i && i.name === name);
if (!item) return name;
return `${name} | ${item.percent} ${item.amount}`;
},
@@ -276,7 +292,7 @@
label: {
show: false
},
- data: pieData0.value,
+ data: (pieData0.value || []).filter(item => item && item.name),
color: pieColors
}
]);
@@ -293,7 +309,7 @@
label: {
show: false
},
- data: pieData1.value,
+ data: (pieData1.value || []).filter(item => item && item.name),
color: pieColors
}
]);
@@ -318,53 +334,81 @@
const pageInfo = ref({
})
+// 鑾峰彇鏈�杩戝叚涓湀鐨勮寖鍥�
+const getLastSixMonths = () => {
+ const endMonth = dayjs().format('YYYY-MM');
+ const startMonth = dayjs().subtract(5, 'month').format('YYYY-MM');
+ return [startMonth, endMonth];
+};
+
const getData = async () => {
- if (!dateRange.value || !dateRange.value.length) {
+ if (!dateRange.value || !Array.isArray(dateRange.value) || dateRange.value.length !== 2) {
return;
}
+ const startDateStr = dateRange.value[0];
+ const endDateStr = dateRange.value[1];
+ if (!startDateStr || !endDateStr) {
+ return;
+ }
+
+ // 楠岃瘉鏃ユ湡鏍煎紡骞惰浆鎹负瀹屾暣鏃ユ湡
+ const startDate = dayjs(startDateStr);
+ const endDate = dayjs(endDateStr);
+ if (!startDate.isValid() || !endDate.isValid()) {
+ console.error('鏃犳晥鐨勬棩鏈熸牸寮�');
+ return;
+ }
+
+ // 鏇存柊 x 杞存暟鎹�
+ const monthLabels = generateMonthLabels(startDateStr, endDateStr);
+ xAxis0.value[0].data = monthLabels;
+ xAxis1.value[0].data = monthLabels;
+
+ // 寮�濮嬫湀浠芥嫾鎺ョ涓�澶╋紝缁撴潫鏈堜唤鎷兼帴鏈�鍚庝竴澶�
+ const entryDateStart = startDate.startOf('month').format('YYYY-MM-DD');
+ const entryDateEnd = endDate.endOf('month').format('YYYY-MM-DD');
+
try {
- const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]});
- if(code === 200) {
- pageInfo.value = data
- pieData0.value = data.incomeType.map(item=>({
- name:item.typeName,
- value:item.account,
- percent:`${item.proportion*100}%`,
- amount:`楼${item.account}`
+ const {code,data} = await reportForms({entryDateStart, entryDateEnd});
+ if(code === 200 && data) {
+ pageInfo.value = data || {};
+ // 瀹夊叏澶勭悊鏁版嵁锛岃繃婊ゆ帀 null 鎴� undefined
+ pieData0.value = (data.incomeType || []).filter(item => item && item.typeName).map(item=>({
+ name:item.typeName || '',
+ value:item.account || 0,
+ percent:`${((item.proportion || 0) * 100).toFixed(2)}%`,
+ amount:`楼${(item.account || 0).toFixed(2)}`
}))
- pieData1.value = data.expenseType.map(item=>({
- name:item.typeName,
- value:item.account,
- percent:`${item.proportion*100}%`,
- amount:`楼${item.account}`
+ pieData1.value = (data.expenseType || []).filter(item => item && item.typeName).map(item=>({
+ name:item.typeName || '',
+ value:item.account || 0,
+ percent:`${((item.proportion || 0) * 100).toFixed(2)}%`,
+ amount:`楼${(item.account || 0).toFixed(2)}`
}))
-
}
} catch (error) {
console.error('鑾峰彇璐㈠姟鎸囨爣鏁版嵁澶辫触锛�', error);
}
try{
- const {code,data} = await reportIncome();
- if(code==200){
- lineSeries0.value = data.map(item=>({
- name:item.typeName,
+ const {code,data} = await reportIncome({entryDateStart, entryDateEnd});
+ if(code==200 && data && Array.isArray(data)){
+ lineSeries0.value = data.filter(item => item && item.typeName).map(item=>({
+ name:item.typeName || '',
type: 'line',
- data:item.account.map(item=>Number(item))
+ data:(item.account || []).map(val => Number(val) || 0)
}))
-
}
}catch (error) {
console.error('鑾峰彇璐㈠姟鎸囨爣鏁版嵁澶辫触锛�', error);
}
try{
- const {code,data} = await reportExpense();
- if(code==200){
- lineSeries1.value = data.map(item=>({
- name:item.typeName,
+ const {code,data} = await reportExpense({entryDateStart, entryDateEnd});
+ if(code==200 && data && Array.isArray(data)){
+ lineSeries1.value = data.filter(item => item && item.typeName).map(item=>({
+ name:item.typeName || '',
type: 'line',
- data:item.account.map(item=>Number(item))
+ data:(item.account || []).map(val => Number(val) || 0)
}))
-
}
}catch (error) {
console.error('鑾峰彇璐㈠姟鎸囨爣鏁版嵁澶辫触锛�', error);
@@ -374,20 +418,66 @@
// 鍒濆鍖�
onMounted(() => {
- // 涓嶈缃粯璁ゆ棩鏈燂紝鐢辩敤鎴锋墜鍔ㄩ�夋嫨
+ // 璁剧疆榛樿鍊间负鏈�杩戝叚涓湀
+ const defaultRange = getLastSixMonths();
+ dateRange.value = defaultRange;
+ // 浣跨敤 nextTick 纭繚缁勪欢瀹屽叏娓叉煋鍚庡啀璋冪敤
+ nextTick(() => {
+ getData();
+ });
});
-// 澶勭悊鏃ユ湡鑼冨洿鍙樺寲
-const handleDateChange = (newRange) => {
- dateRange.value = newRange;
- if (newRange && newRange.length === 2) {
- getData()
+// 闄愬埗鏈堜唤閫夋嫨鑼冨洿锛堟渶澶�12涓湀锛�
+const disabledDate = (time) => {
+ // 濡傛灉娌℃湁閫夋嫨寮�濮嬫湀浠斤紝涓嶇鐢ㄤ换浣曟棩鏈�
+ if (!dateRange.value || !Array.isArray(dateRange.value) || !dateRange.value[0]) {
+ return false;
}
+
+ const startMonth = dayjs(dateRange.value[0]);
+ const currentMonth = dayjs(time);
+
+ // 濡傛灉褰撳墠鏈堜唤鍦ㄥ紑濮嬫湀浠戒箣鍓嶏紝绂佺敤
+ if (currentMonth.isBefore(startMonth, 'month')) {
+ return true;
+ }
+
+ // 璁$畻鏈�澶у厑璁哥殑鏈堜唤锛堝紑濮嬫湀浠� + 11涓湀 = 12涓湀锛�
+ const maxMonth = startMonth.add(11, 'month');
+
+ // 绂佺敤瓒呰繃12涓湀鐨勬湀浠�
+ return currentMonth.isAfter(maxMonth, 'month');
};
-// 閲嶇疆鏃ユ湡鑼冨洿
+// 澶勭悊鏈堜唤鑼冨洿鍙樺寲
+const handleDateChange = (newRange) => {
+ if (!newRange || !Array.isArray(newRange) || newRange.length !== 2) {
+ return;
+ }
+
+ // 楠岃瘉鏈堜唤鑼冨洿涓嶈秴杩�12涓湀
+ const startDate = dayjs(newRange[0]);
+ const endDate = dayjs(newRange[1]);
+ const monthDiff = endDate.diff(startDate, 'month');
+
+ if (monthDiff > 11) {
+ proxy.$modal.msgWarning('鏈�澶氬彧鑳介�夋嫨12涓湀浠�');
+ // 鑷姩璋冩暣涓�12涓湀
+ const adjustedEnd = startDate.add(11, 'month').format('YYYY-MM');
+ dateRange.value = [newRange[0], adjustedEnd];
+ getData();
+ return;
+ }
+
+ dateRange.value = newRange;
+ getData();
+};
+
+// 閲嶇疆鏈堜唤鑼冨洿
const resetDateRange = () => {
- dateRange.value = null;
+ // 閲嶇疆涓烘渶杩戝叚涓湀
+ dateRange.value = getLastSixMonths();
+ getData();
};
</script>
diff --git a/src/views/financialManagement/loanManagement/index.vue b/src/views/financialManagement/loanManagement/index.vue
index 202313a..7580d3b 100644
--- a/src/views/financialManagement/loanManagement/index.vue
+++ b/src/views/financialManagement/loanManagement/index.vue
@@ -72,7 +72,7 @@
缂栬緫
</el-button>
<el-button
- v-if="row.status == 1"
+ :disabled="row.status !== 1"
type="primary"
link
@click="repay(row)"
@@ -122,12 +122,10 @@
{
label: "鍊熸浜哄鍚�",
prop: "borrowerName",
- width: 150,
},
{
label: "鍊熸閲戦锛堝厓锛�",
prop: "borrowAmount",
- width: 150,
formatData: (val) => {
return val ? `楼${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '楼0.00';
},
@@ -135,7 +133,6 @@
{
label: "鍊熸鍒╃巼锛�%锛�",
prop: "interestRate",
- width: 130,
formatData: (val) => {
return val ? `${parseFloat(val).toFixed(2)}%` : '-';
},
@@ -143,18 +140,16 @@
{
label: "鍊熸鏃ユ湡",
prop: "borrowDate",
- width: 120,
},
{
label: "瀹為檯杩樻鏃ユ湡",
prop: "repayDate",
- width: 130,
},
{
label: "鍊熸鐘舵��",
prop: "status",
- width: 100,
dataType: "tag",
+ align: 'center',
formatData: (params) => {
if (params == 1) {
return "寰呰繕娆�";
diff --git a/src/views/financialManagement/revenueManagement/Form.vue b/src/views/financialManagement/revenueManagement/Form.vue
deleted file mode 100644
index 67b175e..0000000
--- a/src/views/financialManagement/revenueManagement/Form.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<template>
- <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
- <el-form-item label="鏀跺叆鏃ユ湡" prop="incomeDate">
- <el-date-picker
- style="width: 100%"
- v-model="form.incomeDate"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- type="date"
- placeholder="璇烽�夋嫨鏃ユ湡"
- clearable
- />
- </el-form-item>
- <el-form-item label="鏀跺叆绫诲瀷" prop="incomeType">
- <el-select
- v-model="form.incomeType"
- placeholder="璇烽�夋嫨"
- clearable
- >
- <el-option :label="item.label" :value="item.value" v-for="(item,index) in income_types" :key="index" />
- </el-select>
- </el-form-item>
- <el-form-item label="瀹㈡埛鍚嶇О" prop="customerName">
- <el-input v-model="form.customerName" placeholder="璇疯緭鍏�" />
- </el-form-item>
- <el-form-item label="鏀跺叆閲戦" prop="incomeMoney">
- <el-input-number :step="0.01" :min="0" style="width: 100%"
- v-model="form.incomeMoney"
- placeholder="璇疯緭鍏�"
- />
- </el-form-item>
- <el-form-item label="鏀跺叆鎻忚堪" prop="incomeDescribed">
- <el-input v-model="form.incomeDescribed" placeholder="璇疯緭鍏�" />
- </el-form-item>
- <el-form-item label="鏀舵鏂瑰紡" prop="incomeMethod">
- <el-select
- v-model="form.incomeMethod"
- placeholder="璇烽�夋嫨"
- clearable
- >
- <el-option :label="item.label" :value="item.value" v-for="(item,index) in payment_methods" :key="index" />
- </el-select>
- </el-form-item>
- <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNumber">
- <el-input v-model="form.invoiceNumber" placeholder="璇疯緭鍏�" />
- </el-form-item>
- <el-form-item label="澶囨敞" prop="note">
- <el-input
- v-model="form.note"
- placeholder="澶囨敞"
- />
- </el-form-item>
-
- </el-form>
-</template>
-
-<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getAccountIncome } from "@/api/financialManagement/revenueManagement";
-import {ref} from "vue";
-const { proxy } = getCurrentInstance();
-
-
-defineOptions({
- name: "鏂板鏀跺叆",
-});
-const { income_types } = proxy.useDict("income_types");
-const { payment_methods } = proxy.useDict("payment_methods");
-const formRef = ref(null);
-const formRules = {
- customerName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
- incomeMoney: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
- incomeDescribed: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
- incomeDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
- incomeType: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
- incomeMethod: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
-}
-
-const { form, resetForm } = useFormData({
- incomeDate: undefined, // 鏀跺叆鏃ユ湡
- incomeType: undefined, // 鏀跺叆绫诲瀷
- customerName: undefined, // 瀹㈡埛鍚嶇О
- incomeMoney: undefined, // 鏀跺叆閲戦
- incomeDescribed: undefined, // 鏀跺叆鎻忚堪
- incomeMethod: undefined, // 鏀舵鏂瑰紡
- invoiceNumber: undefined, // 鍙戠エ鍙风爜
- note: undefined, // 澶囨敞
-});
-
-const loadForm = async (id) => {
- const { code, data } = await getAccountIncome(id);
- if (code == 200) {
- form.incomeDate = data.incomeDate;
- form.incomeType = data.incomeType;
- form.customerName = data.customerName;
- form.incomeMoney = data.incomeMoney;
- form.incomeDescribed = data.incomeDescribed;
- form.incomeMethod = data.incomeMethod;
- form.invoiceNumber = data.invoiceNumber;
- form.note = data.note;
- }
-};
-
-// 娓呴櫎琛ㄥ崟鏍¢獙鐘舵��
-const clearValidate = () => {
- formRef.value?.clearValidate();
-};
-
-// 閲嶇疆琛ㄥ崟鏁版嵁鍜屾牎楠岀姸鎬�
-const resetFormAndValidate = () => {
- resetForm();
- clearValidate();
-};
-
-defineExpose({
- form,
- loadForm,
- resetForm,
- clearValidate,
- resetFormAndValidate,
- formRef,
-});
-</script>
diff --git a/src/views/financialManagement/revenueManagement/Modal.vue b/src/views/financialManagement/revenueManagement/Modal.vue
index 480b4fd..245cdf2 100644
--- a/src/views/financialManagement/revenueManagement/Modal.vue
+++ b/src/views/financialManagement/revenueManagement/Modal.vue
@@ -1,20 +1,75 @@
<template>
- <el-dialog :title="modalOptions.title" v-model="visible" @close="close" width="30%">
- <Form ref="formRef"></Form>
- <template #footer>
- <el-button type="primary" @click="sendForm" :loading="loading">
- {{ modalOptions.confirmText }}
- </el-button>
- <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
- </template>
- </el-dialog>
+ <FormDialog
+ v-model="dialogVisible"
+ :title="dialogTitle"
+ :operationType="operationType"
+ width="30%"
+ @confirm="sendForm"
+ @close="close"
+ @cancel="close"
+ >
+ <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
+ <el-form-item label="鏀跺叆鏃ユ湡" prop="incomeDate">
+ <el-date-picker
+ style="width: 100%"
+ v-model="form.incomeDate"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨鏃ユ湡"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鏀跺叆绫诲瀷" prop="incomeType">
+ <el-select
+ v-model="form.incomeType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in income_types" :key="index" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customerName">
+ <el-input v-model="form.customerName" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item label="鏀跺叆閲戦" prop="incomeMoney">
+ <el-input-number :step="0.01" :min="0" style="width: 100%"
+ v-model="form.incomeMoney"
+ placeholder="璇疯緭鍏�"
+ />
+ </el-form-item>
+ <el-form-item label="鏀跺叆鎻忚堪" prop="incomeDescribed">
+ <el-input v-model="form.incomeDescribed" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item label="鏀舵鏂瑰紡" prop="incomeMethod">
+ <el-select
+ v-model="form.incomeMethod"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option :label="item.label" :value="item.value" v-for="(item,index) in payment_methods" :key="index" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNumber">
+ <el-input v-model="form.invoiceNumber" placeholder="璇疯緭鍏�" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="note">
+ <el-input
+ v-model="form.note"
+ placeholder="澶囨敞"
+ />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
</template>
<script setup>
-import { useModal } from "@/hooks/useModal";
-import { add, update } from "@/api/financialManagement/revenueManagement";
-import Form from "./Form.vue";
+import { add, update, getAccountIncome } from "@/api/financialManagement/revenueManagement";
import { ElMessage } from "element-plus";
+import useFormData from "@/hooks/useFormData";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { ref } from "vue";
+
const { proxy } = getCurrentInstance()
defineOptions({
@@ -23,43 +78,96 @@
const emits = defineEmits(["success"]);
-const formRef = ref();
-const {
- id,
- visible,
- loading,
- openModal,
- modalOptions,
- handleConfirm,
- closeModal,
-} = useModal({ title: "鏀跺叆" });
+const formRef = ref(null);
+const dialogVisible = ref(false);
+const operationType = ref("add"); // add | edit
+const id = ref(undefined);
+const submitting = ref(false);
+
+const dialogTitle = (type) => {
+ if (type === "edit") return "缂栬緫鏀跺叆";
+ return "鏂板鏀跺叆";
+};
+
+const { income_types } = proxy.useDict("income_types");
+const { payment_methods } = proxy.useDict("payment_methods");
+
+const formRules = {
+ customerName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ incomeMoney: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ incomeDescribed: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+ incomeDate: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ incomeType: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+ incomeMethod: [{ required: true, trigger: "change", message: "璇烽�夋嫨" }],
+}
+
+const { form, resetForm } = useFormData({
+ incomeDate: undefined, // 鏀跺叆鏃ユ湡
+ incomeType: undefined, // 鏀跺叆绫诲瀷
+ customerName: undefined, // 瀹㈡埛鍚嶇О
+ incomeMoney: undefined, // 鏀跺叆閲戦
+ incomeDescribed: undefined, // 鏀跺叆鎻忚堪
+ incomeMethod: undefined, // 鏀舵鏂瑰紡
+ invoiceNumber: undefined, // 鍙戠エ鍙风爜
+ note: undefined, // 澶囨敞
+});
const sendForm = () => {
- proxy.$refs.formRef.$refs.formRef.validate(async valid => {
- if (valid) {
- const {code} = id.value
- ? await update({id: id.value, ...formRef.value.form})
- : await add(formRef.value.form);
- if (code == 200) {
- emits("success");
- ElMessage({message: "鎿嶄綔鎴愬姛", type: "success"});
- close();
- } else {
- loading.value = false;
- }
- }
- })
+ if (submitting.value) return;
+ formRef.value?.validate(async (valid) => {
+ if (valid) {
+ submitting.value = true;
+ try {
+ const { code } = id.value
+ ? await update({ id: id.value, ...form })
+ : await add(form);
+ if (code == 200) {
+ emits("success");
+ ElMessage({ message: "鎿嶄綔鎴愬姛", type: "success" });
+ close();
+ }
+ } finally {
+ submitting.value = false;
+ }
+ }
+ })
};
const close = () => {
- formRef.value.resetFormAndValidate();
- closeModal();
+ resetForm();
+ formRef.value?.clearValidate();
+ id.value = undefined;
+ dialogVisible.value = false;
};
-const loadForm = async (id) => {
- openModal(id);
- await nextTick();
- formRef.value.loadForm(id);
+const loadForm = async (rowId) => {
+ operationType.value = "edit";
+ id.value = rowId;
+ dialogVisible.value = true;
+ if (rowId) {
+ const { code, data } = await getAccountIncome(rowId);
+ if (code == 200) {
+ form.incomeDate = data.incomeDate;
+ form.incomeType = data.incomeType;
+ form.customerName = data.customerName;
+ form.incomeMoney = data.incomeMoney;
+ form.incomeDescribed = data.incomeDescribed;
+ form.incomeMethod = data.incomeMethod;
+ form.invoiceNumber = data.invoiceNumber;
+ form.note = data.note;
+ }
+ } else {
+ resetForm();
+ formRef.value?.clearValidate();
+ }
+};
+
+const openModal = () => {
+ operationType.value = "add";
+ id.value = undefined;
+ resetForm();
+ formRef.value?.clearValidate();
+ dialogVisible.value = true;
};
defineExpose({
diff --git a/src/views/financialManagement/revenueManagement/filesDia.vue b/src/views/financialManagement/revenueManagement/filesDia.vue
deleted file mode 100644
index f752496..0000000
--- a/src/views/financialManagement/revenueManagement/filesDia.vue
+++ /dev/null
@@ -1,202 +0,0 @@
-<template>
- <div>
- <el-dialog
- v-model="dialogFormVisible"
- title="涓婁紶闄勪欢"
- width="50%"
- @close="closeDia"
- >
- <div style="margin-bottom: 10px;text-align: right">
- <el-upload
- v-model:file-list="fileList"
- class="upload-demo"
- :action="uploadUrl"
- :on-success="handleUploadSuccess"
- :on-error="handleUploadError"
- name="file"
- :show-file-list="false"
- :headers="headers"
- style="display: inline;margin-right: 10px"
- >
- <el-button type="primary">涓婁紶闄勪欢</el-button>
- </el-upload>
- <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
- </div>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :tableLoading="tableLoading"
- :isSelection="true"
- @selection-change="handleSelectionChange"
- height="500"
- >
- </PIMTable>
- <pagination
- style="margin: 10px 0"
- v-show="total > 0"
- @pagination="paginationSearch"
- :total="total"
- :page="page.current"
- :limit="page.size"
- />
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="closeDia">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- <filePreview ref="filePreviewRef" />
- </div>
-</template>
-
-<script setup>
-import {ref} from "vue";
-import {ElMessageBox} from "element-plus";
-import {getToken} from "@/utils/auth.js";
-import filePreview from '@/components/filePreview/index.vue'
-import {
- fileAdd,
- fileDel,
- fileListPage
-} from "@/api/financialManagement/revenueManagement.js";
-import Pagination from "@/components/PIMTable/Pagination.vue";
-const { proxy } = getCurrentInstance()
-const emit = defineEmits(['close'])
-
-const dialogFormVisible = ref(false);
-const currentId = ref('')
-const selectedRows = ref([]);
-const filePreviewRef = ref()
-const tableColumn = ref([
- {
- label: "鏂囦欢鍚嶇О",
- prop: "name",
- },
- {
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
- operation: [
- {
- name: "涓嬭浇",
- type: "text",
- clickFun: (row) => {
- downLoadFile(row);
- },
- },
- {
- name: "棰勮",
- type: "text",
- clickFun: (row) => {
- lookFile(row);
- },
- }
- ],
- },
-]);
-const page = reactive({
- current: 1,
- size: 100,
-});
-const total = ref(0);
-const tableData = ref([]);
-const fileList = ref([]);
-const tableLoading = ref(false);
-const accountType = ref('')
-const headers = ref({
- Authorization: "Bearer " + getToken(),
-});
-const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 涓婁紶鐨勫浘鐗囨湇鍔″櫒鍦板潃
-
-// 鎵撳紑寮规
-const openDialog = (row,type) => {
- accountType.value = type;
- dialogFormVisible.value = true;
- currentId.value = row.id;
- getList()
-}
-const paginationSearch = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
-};
-const getList = () => {
- fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => {
- tableData.value = res.data.records;
- total.value = res.data.total;
- })
-}
-// 琛ㄦ牸閫夋嫨鏁版嵁
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
-};
-
-// 鍏抽棴寮规
-const closeDia = () => {
- dialogFormVisible.value = false;
- emit('close')
-};
-// 涓婁紶鎴愬姛澶勭悊
-function handleUploadSuccess(res, file) {
- // 濡傛灉涓婁紶鎴愬姛
- if (res.code == 200) {
- const fileRow = {}
- fileRow.name = res.data.originalName
- fileRow.url = res.data.tempPath
- uploadFile(fileRow)
- } else {
- proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
- }
-}
-function uploadFile(file) {
- file.accountId = currentId.value;
- file.accountType = accountType.value;
- fileAdd(file).then(res => {
- proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
- getList()
- })
-}
-// 涓婁紶澶辫触澶勭悊
-function handleUploadError() {
- proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
-}
-// 涓嬭浇闄勪欢
-const downLoadFile = (row) => {
- proxy.$download.name(row.url);
-}
-// 鍒犻櫎
-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(() => {
- fileDel(ids).then((res) => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- getList();
- });
- }).catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
-};
-// 棰勮闄勪欢
-const lookFile = (row) => {
- filePreviewRef.value.open(row.url)
-}
-
-defineExpose({
- openDialog,
-});
-</script>
-
-<style scoped>
-
-</style>
\ No newline at end of file
diff --git a/src/views/financialManagement/revenueManagement/index.vue b/src/views/financialManagement/revenueManagement/index.vue
index 9dcd23e..a8a59c8 100644
--- a/src/views/financialManagement/revenueManagement/index.vue
+++ b/src/views/financialManagement/revenueManagement/index.vue
@@ -34,8 +34,8 @@
<el-button
type="danger"
icon="Delete"
- :disabled="multipleList.length <= 0"
- @click="deleteRow(multipleList.map((item) => item.id))"
+ :disabled="multipleList.length <= 0 || hasBusinessIdInSelection"
+ @click="handleBatchDelete"
>
鎵归噺鍒犻櫎
</el-button>
@@ -55,12 +55,17 @@
@pagination="changePage"
>
<template #operation="{ row }">
- <el-button type="primary" text @click="edit(row.id)" icon="editPen">
+ <el-button
+ type="primary"
+ link
+ :disabled="!!row.businessId"
+ @click="edit(row.id)"
+ >
缂栬緫
</el-button>
<el-button
type="primary"
- text
+ link
@click="openFilesFormDia(row)"
>
闄勪欢
@@ -69,18 +74,27 @@
</PIMTable>
</div>
<Modal ref="modalRef" @success="getTableData"></Modal>
- <files-dia ref="filesDia"></files-dia>
+ <FileListDialog
+ ref="fileListRef"
+ v-model="fileListDialogVisible"
+ :show-upload-button="true"
+ :show-delete-button="true"
+ :upload-method="handleUpload"
+ :delete-method="handleFileDelete"
+ />
</div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { listPage, delAccountIncome } from "@/api/financialManagement/revenueManagement";
-import { onMounted, getCurrentInstance } from "vue";
+import { listPage, delAccountIncome, fileListPage, fileAdd, fileDel } from "@/api/financialManagement/revenueManagement";
+import { onMounted, getCurrentInstance, ref, computed } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
-import FilesDia from "./filesDia.vue";
+import FileListDialog from "@/components/Dialog/FileListDialog.vue";
+import request from "@/utils/request";
+import { getToken } from "@/utils/auth";
defineOptions({
name: "鏀跺叆绠$悊",
@@ -92,7 +106,10 @@
const modalRef = ref();
const { payment_methods } = proxy.useDict("payment_methods");
const { income_types } = proxy.useDict("income_types");
-const filesDia = ref()
+const fileListRef = ref(null);
+const fileListDialogVisible = ref(false);
+const currentFileRow = ref(null);
+const accountType = ref('鏀跺叆');
const {
filters,
@@ -111,12 +128,10 @@
[
{
label: "鏀跺叆鏃ユ湡",
- align: "center",
prop: "incomeDate",
},
{
label: "鏀跺叆绫诲瀷",
- align: "center",
prop: "incomeType",
dataType: "tag",
formatData: (params) => {
@@ -129,26 +144,25 @@
},
{
label: "瀹㈡埛鍚嶇О",
- align: "center",
prop: "customerName",
+ width: '200'
},
{
label: "鏀跺叆閲戦",
- align: "center",
prop: "incomeMoney",
},
{
label: "鏀跺叆鎻忚堪",
- align: "center",
prop: "incomeDescribed",
},
{
label: "鏀舵鏂瑰紡",
- align: "center",
prop: "incomeMethod",
+ align: 'center',
+ width: '100',
dataType: "tag",
formatData: (params) => {
if (payment_methods.value.find((m) => m.value == params)) {
@@ -160,24 +174,20 @@
},
{
label: "鍙戠エ鍙风爜",
- align: "center",
prop: "invoiceNumber",
},
{
label: "澶囨敞",
- align: "center",
prop: "note",
},
{
label: "褰曞叆浜�",
- align: "center",
prop: "inputUser",
},
{
label: "褰曞叆鏃ユ湡",
- align: "center",
prop: "inputTime",
},
@@ -187,7 +197,7 @@
dataType: "slot",
slot: "operation",
align: "center",
- width: "200px",
+ width: "160px",
},
]
);
@@ -197,10 +207,21 @@
multipleList.value = selectionList;
};
+// 鍒ゆ柇閫変腑鐨勯」涓槸鍚︽湁 businessId
+const hasBusinessIdInSelection = computed(() => {
+ return multipleList.value.some(item => item.businessId);
+});
+
const add = () => {
modalRef.value.openModal();
};
const edit = (id) => {
+ // 妫�鏌ュ綋鍓嶈鏄惁鏈� businessId
+ const row = dataList.value.find(item => item.id === id);
+ if (row && row.businessId) {
+ proxy.$modal.msgWarning("璇ヨ褰曞凡鍏宠仈涓氬姟锛屼笉鑳界紪杈�");
+ return;
+ }
modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
@@ -209,6 +230,25 @@
onCurrentChange(page);
};
const deleteRow = (id) => {
+ // 濡傛灉鏄暟缁勶紝妫�鏌ユ槸鍚︽湁 businessId
+ if (Array.isArray(id)) {
+ const hasBusinessId = id.some(itemId => {
+ const row = dataList.value.find(item => item.id === itemId);
+ return row && row.businessId;
+ });
+ if (hasBusinessId) {
+ proxy.$modal.msgWarning("閫変腑鐨勮褰曚腑鍖呭惈宸插叧鑱斾笟鍔$殑璁板綍锛屼笉鑳藉垹闄�");
+ return;
+ }
+ } else {
+ // 鍗曚釜鍒犻櫎锛屾鏌ユ槸鍚︽湁 businessId
+ const row = dataList.value.find(item => item.id === id);
+ if (row && row.businessId) {
+ proxy.$modal.msgWarning("璇ヨ褰曞凡鍏宠仈涓氬姟锛屼笉鑳藉垹闄�");
+ return;
+ }
+ }
+
ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ暟鎹�, 鏄惁缁х画?", "鎻愮ず", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
@@ -223,6 +263,23 @@
getTableData();
}
});
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (multipleList.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+
+ // 妫�鏌ユ槸鍚︽湁 businessId
+ if (hasBusinessIdInSelection.value) {
+ proxy.$modal.msgWarning("閫変腑鐨勮褰曚腑鍖呭惈宸插叧鑱斾笟鍔$殑璁板綍锛屼笉鑳藉垹闄�");
+ return;
+ }
+
+ const ids = multipleList.value.map((item) => item.id);
+ deleteRow(ids);
};
const changeDaterange = (value) => {
@@ -252,10 +309,154 @@
});
};
// 鎵撳紑闄勪欢寮规
-const openFilesFormDia = (row) => {
- nextTick(() => {
- filesDia.value?.openDialog( row,'鏀跺叆')
- })
+const openFilesFormDia = async (row) => {
+ currentFileRow.value = row;
+ accountType.value = '鏀跺叆';
+ try {
+ const res = await fileListPage({
+ accountId: row.id,
+ accountType: accountType.value,
+ current: 1,
+ size: 100
+ });
+ if (res.code === 200 && fileListRef.value) {
+ // 灏嗘暟鎹浆鎹负 FileListDialog 闇�瑕佺殑鏍煎紡
+ const fileList = (res.data?.records || []).map(item => ({
+ name: item.name,
+ url: item.url,
+ id: item.id,
+ ...item
+ }));
+ fileListRef.value.open(fileList);
+ fileListDialogVisible.value = true;
+ }
+ } catch (error) {
+ proxy.$modal.msgError("鑾峰彇闄勪欢鍒楄〃澶辫触");
+ }
+};
+
+// 涓婁紶闄勪欢
+const handleUpload = async () => {
+ if (!currentFileRow.value) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨鏁版嵁");
+ return null;
+ }
+
+ return new Promise((resolve) => {
+ // 鍒涘缓涓�涓殣钘忕殑鏂囦欢杈撳叆鍏冪礌
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.style.display = 'none';
+ input.onchange = async (e) => {
+ const file = e.target.files[0];
+ if (!file) {
+ resolve(null);
+ return;
+ }
+
+ try {
+ // 浣跨敤 FormData 涓婁紶鏂囦欢
+ const formData = new FormData();
+ formData.append('file', file);
+
+ const uploadRes = await request({
+ url: '/file/upload',
+ method: 'post',
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: `Bearer ${getToken()}`
+ }
+ });
+
+ if (uploadRes.code === 200) {
+ // 淇濆瓨闄勪欢淇℃伅
+ const fileData = {
+ accountId: currentFileRow.value.id,
+ accountType: accountType.value,
+ name: uploadRes.data.originalName || file.name,
+ url: uploadRes.data.tempPath || uploadRes.data.url
+ };
+
+ const saveRes = await fileAdd(fileData);
+ if (saveRes.code === 200) {
+ proxy.$modal.msgSuccess("鏂囦欢涓婁紶鎴愬姛");
+ // 閲嶆柊鍔犺浇鏂囦欢鍒楄〃
+ const listRes = await fileListPage({
+ accountId: currentFileRow.value.id,
+ accountType: accountType.value,
+ current: 1,
+ size: 100
+ });
+ if (listRes.code === 200 && fileListRef.value) {
+ const fileList = (listRes.data?.records || []).map(item => ({
+ name: item.name,
+ url: item.url,
+ id: item.id,
+ ...item
+ }));
+ fileListRef.value.setList(fileList);
+ }
+ // 杩斿洖鏂版枃浠朵俊鎭�
+ resolve({
+ name: fileData.name,
+ url: fileData.url,
+ id: saveRes.data?.id
+ });
+ } else {
+ proxy.$modal.msgError(saveRes.msg || "鏂囦欢淇濆瓨澶辫触");
+ resolve(null);
+ }
+ } else {
+ proxy.$modal.msgError(uploadRes.msg || "鏂囦欢涓婁紶澶辫触");
+ resolve(null);
+ }
+ } catch (error) {
+ proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
+ resolve(null);
+ } finally {
+ document.body.removeChild(input);
+ }
+ };
+
+ document.body.appendChild(input);
+ input.click();
+ });
+};
+
+// 鍒犻櫎闄勪欢
+const handleFileDelete = async (row) => {
+ try {
+ const res = await fileDel([row.id]);
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ // 閲嶆柊鍔犺浇鏂囦欢鍒楄〃
+ if (currentFileRow.value && fileListRef.value) {
+ const listRes = await fileListPage({
+ accountId: currentFileRow.value.id,
+ accountType: accountType.value,
+ current: 1,
+ size: 100
+ });
+ if (listRes.code === 200) {
+ const fileList = (listRes.data?.records || []).map(item => ({
+ name: item.name,
+ url: item.url,
+ id: item.id,
+ ...item
+ }));
+ fileListRef.value.setList(fileList);
+ }
+ }
+ return true; // 杩斿洖 true 琛ㄧず鍒犻櫎鎴愬姛锛岀粍浠朵細鏇存柊鍒楄〃
+ } else {
+ proxy.$modal.msgError(res.msg || "鍒犻櫎澶辫触");
+ return false;
+ }
+ } catch (error) {
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ return false;
+ }
};
onMounted(() => {
diff --git a/src/views/procurementManagement/paymentEntry/index.vue b/src/views/procurementManagement/paymentEntry/index.vue
index 89152bf..5ee8d17 100644
--- a/src/views/procurementManagement/paymentEntry/index.vue
+++ b/src/views/procurementManagement/paymentEntry/index.vue
@@ -537,7 +537,7 @@
})
.then(() => {
tableLoading.value = true;
- delPaymentRegistration(row.id)
+ delPaymentRegistration([row.id])
.then((res) => {
proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
getList();
diff --git a/src/views/procurementManagement/paymentHistory/index.vue b/src/views/procurementManagement/paymentHistory/index.vue
index c38b4b0..179373b 100644
--- a/src/views/procurementManagement/paymentHistory/index.vue
+++ b/src/views/procurementManagement/paymentHistory/index.vue
@@ -43,6 +43,13 @@
鎼滅储
</el-button>
<el-button @click="handleExport">瀵煎嚭</el-button>
+ <el-button
+ type="danger"
+ :disabled="selectedRows.length === 0"
+ @click="handleBatchDelete"
+ >
+ 鎵归噺鍒犻櫎 ({{ selectedRows.length }})
+ </el-button>
</el-form-item>
</el-form>
<div class="table_list">
@@ -58,7 +65,18 @@
:tableLoading="tableLoading"
@pagination="pagination"
:total="page.total"
- ></PIMTable>
+ >
+ <template #operation="{ row }">
+ <el-button
+ type="primary"
+ link
+ size="small"
+ @click="handleDelete(row)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </PIMTable>
</div>
</div>
</template>
@@ -66,7 +84,9 @@
<script setup>
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
-import { paymentHistoryListPage } from "@/api/procurementManagement/paymentEntry.js";
+import { ElMessageBox } from "element-plus";
+import { paymentHistoryListPage} from "@/api/procurementManagement/paymentEntry.js";
+import {delPaymentRegistration } from "@/api/procurementManagement/procurementInvoiceLedger.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
@@ -104,6 +124,13 @@
{
label: "鐧昏鏃ユ湡",
prop: "registrationtDate",
+ },
+ {
+ label: "鎿嶄綔",
+ dataType: "slot",
+ slot: "operation",
+ width: 100,
+ align: "center",
},
]);
const tableData = ref([]);
@@ -170,6 +197,62 @@
getList();
};
+// 鍒犻櫎
+const handleDelete = (row) => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎鎻愮ず", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ tableLoading.value = true;
+ delPaymentRegistration([row.id])
+ .then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉℃暟鎹悧锛焋,
+ "鍒犻櫎鎻愮ず",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ )
+ .then(() => {
+ tableLoading.value = true;
+ const ids = selectedRows.value.map((item) => item.id);
+ delPaymentRegistration(ids)
+ .then((res) => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ selectedRows.value = [];
+ getList();
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
// 瀵煎嚭
const handleExport = () => {
const { paymentDate, ...rest } = searchForm;
diff --git a/src/views/productionManagement/processRoute/Edit.vue b/src/views/productionManagement/processRoute/Edit.vue
index 2ede998..0c0fe0f 100644
--- a/src/views/productionManagement/processRoute/Edit.vue
+++ b/src/views/productionManagement/processRoute/Edit.vue
@@ -8,46 +8,45 @@
>
<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="瑙勬牸鍨嬪彿锛�"
+ label="浜у搧鍚嶇О"
prop="productModelId"
:rules="[
{
required: true,
- message: '璇烽�夋嫨瑙勬牸鍨嬪彿',
+ message: '璇烽�夋嫨浜у搧',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-button type="primary" @click="showProductSelectDialog = true">
+ {{ formState.productName && formState.productModelName
+ ? `${formState.productName} - ${formState.productModelName}`
+ : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item
+ label="BOM"
+ prop="bomId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨BOM',
+ trigger: 'change',
}
]"
>
<el-select
- v-model="formState.productModelId"
- placeholder="璇烽�夋嫨"
+ v-model="formState.bomId"
+ placeholder="璇烽�夋嫨BOM"
clearable
+ :disabled="!formState.productModelId || bomOptions.length === 0"
+ style="width: 100%"
>
<el-option
- v-for="item in productModelsOptions"
+ v-for="item in bomOptions"
:key="item.id"
- :label="item.model"
+ :label="item.bomNo || `BOM-${item.id}`"
:value="item.id"
/>
</el-select>
@@ -57,6 +56,13 @@
<el-input v-model="formState.description" type="textarea" />
</el-form-item>
</el-form>
+
+ <!-- 浜у搧閫夋嫨寮圭獥 -->
+ <ProductSelectDialog
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
+ />
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit">纭</el-button>
@@ -68,9 +74,10 @@
</template>
<script setup>
-import {ref, computed, getCurrentInstance, onMounted} from "vue";
+import {ref, computed, getCurrentInstance, onMounted, nextTick, watch} from "vue";
import {update} from "@/api/productionManagement/processRoute.js";
-import {modelList, productTreeList} from "@/api/basicData/product.js";
+import {getByModel} from "@/api/productionManagement/productBom.js";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
const props = defineProps({
visible: {
@@ -87,7 +94,14 @@
const emit = defineEmits(['update:visible', 'completed']);
// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
-const formState = ref({});
+const formState = ref({
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ bomId: undefined,
+ description: '',
+});
const isShow = computed({
get() {
@@ -98,66 +112,111 @@
},
});
+const showProductSelectDialog = ref(false);
+const bomOptions = ref([]);
+
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 (props.record) {
+ formState.value = {
+ ...props.record,
+ productId: props.record.productId,
+ productModelId: props.record.productModelId,
+ productName: props.record.productName || "",
+ // 娉ㄦ剰锛歳ecord涓殑瀛楁鏄痬odel锛岄渶瑕佹槧灏勫埌productModelName
+ productModelName: props.record.model || props.record.productModelName || "",
+ bomId: props.record.bomId,
+ description: props.record.description || '',
};
- if (children && children.length > 0) {
- newItem.children = convertIdToValue(children);
+ // 濡傛灉鏈変骇鍝佸瀷鍙稩D锛屽姞杞紹OM鍒楄〃
+ if (props.record.productModelId) {
+ loadBomList(props.record.productModelId);
}
-
- return newItem;
- });
+ }
}
+
+// 鍔犺浇BOM鍒楄〃
+const loadBomList = async (productModelId) => {
+ if (!productModelId) {
+ bomOptions.value = [];
+ return;
+ }
+ try {
+ const res = await getByModel(productModelId);
+ // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
+ let bomList = [];
+ if (Array.isArray(res)) {
+ bomList = res;
+ } else if (res && res.data) {
+ bomList = Array.isArray(res.data) ? res.data : [res.data];
+ } else if (res && typeof res === 'object') {
+ bomList = [res];
+ }
+ bomOptions.value = bomList;
+ } catch (error) {
+ console.error("鍔犺浇BOM鍒楄〃澶辫触锛�", error);
+ bomOptions.value = [];
+ }
+};
+
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ // 鍏堟煡璇OM鍒楄〃锛堝繀閫夛級
+ try {
+ const res = await getByModel(product.id);
+ // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
+ let bomList = [];
+ if (Array.isArray(res)) {
+ bomList = res;
+ } else if (res && res.data) {
+ bomList = Array.isArray(res.data) ? res.data : [res.data];
+ } else if (res && typeof res === 'object') {
+ bomList = [res];
+ }
+
+ if (bomList.length > 0) {
+ formState.value.productModelId = product.id;
+ formState.value.productName = product.productName;
+ formState.value.productModelName = product.model;
+ // 濡傛灉褰撳墠閫夋嫨鐨凚OM涓嶅湪鏂板垪琛ㄤ腑锛屽垯閲嶇疆BOM閫夋嫨
+ const currentBomExists = bomList.some(bom => bom.id === formState.value.bomId);
+ if (!currentBomExists) {
+ formState.value.bomId = undefined;
+ }
+ bomOptions.value = bomList;
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+ proxy.$refs["formRef"]?.validateField('productModelId');
+ } else {
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
+ }
+ } catch (error) {
+ // 濡傛灉鎺ュ彛杩斿洖404鎴栧叾浠栭敊璇紝璇存槑娌℃湁BOM
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
+ }
+ }
+};
const handleSubmit = () => {
proxy.$refs["formRef"].validate(valid => {
if (valid) {
+ // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰BOM
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+ return;
+ }
+ if (!formState.value.bomId) {
+ proxy.$modal.msgError("璇烽�夋嫨BOM");
+ return;
+ }
update(formState.value).then(res => {
// 鍏抽棴妯℃�佹
isShow.value = false;
@@ -176,11 +235,18 @@
});
+// 鐩戝惉寮圭獥鎵撳紑锛屽垵濮嬪寲琛ㄥ崟鏁版嵁
+watch(() => props.visible, (visible) => {
+ if (visible && props.record) {
+ nextTick(() => {
+ setFormData();
+ });
+ }
+}, { immediate: true });
+
onMounted(() => {
- getProductOptions()
- getModels(props.record.productId)
- nextTick(() => {
- setFormData()
- });
-})
+ if (props.visible && props.record) {
+ setFormData();
+ }
+});
</script>
diff --git a/src/views/productionManagement/processRoute/New.vue b/src/views/productionManagement/processRoute/New.vue
index 856a2a4..62c6873 100644
--- a/src/views/productionManagement/processRoute/New.vue
+++ b/src/views/productionManagement/processRoute/New.vue
@@ -8,46 +8,45 @@
>
<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="瑙勬牸鍨嬪彿锛�"
+ label="浜у搧鍚嶇О"
prop="productModelId"
:rules="[
{
required: true,
- message: '璇烽�夋嫨瑙勬牸鍨嬪彿',
+ message: '璇烽�夋嫨浜у搧',
+ trigger: 'change',
+ }
+ ]"
+ >
+ <el-button type="primary" @click="showProductSelectDialog = true">
+ {{ formState.productName && formState.productModelName
+ ? `${formState.productName} - ${formState.productModelName}`
+ : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item
+ label="BOM"
+ prop="bomId"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨BOM',
+ trigger: 'change',
}
]"
>
<el-select
- v-model="formState.productModelId"
- placeholder="璇烽�夋嫨"
+ v-model="formState.bomId"
+ placeholder="璇烽�夋嫨BOM"
clearable
+ :disabled="!formState.productModelId || bomOptions.length === 0"
+ style="width: 100%"
>
<el-option
- v-for="item in productModelsOptions"
+ v-for="item in bomOptions"
:key="item.id"
- :label="item.model"
+ :label="item.bomNo || `BOM-${item.id}`"
:value="item.id"
/>
</el-select>
@@ -57,6 +56,13 @@
<el-input v-model="formState.description" type="textarea" />
</el-form-item>
</el-form>
+
+ <!-- 浜у搧閫夋嫨寮圭獥 -->
+ <ProductSelectDialog
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
+ />
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit">纭</el-button>
@@ -68,9 +74,10 @@
</template>
<script setup>
-import {ref, computed, getCurrentInstance, onMounted} from "vue";
+import {ref, computed, getCurrentInstance} from "vue";
import {add} from "@/api/productionManagement/processRoute.js";
-import {modelList, productTreeList} from "@/api/basicData/product.js";
+import {getByModel} from "@/api/productionManagement/productBom.js";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
const props = defineProps({
visible: {
@@ -85,6 +92,9 @@
const formState = ref({
productId: undefined,
productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ bomId: undefined,
description: '',
});
@@ -97,66 +107,73 @@
},
});
-const productModelsOptions = ref([])
-const productOptions = ref([])
+const showProductSelectDialog = ref(false);
+const bomOptions = ref([]);
let { proxy } = getCurrentInstance()
const closeModal = () => {
+ // 閲嶇疆琛ㄥ崟鏁版嵁
+ formState.value = {
+ productId: undefined,
+ productModelId: undefined,
+ productName: "",
+ productModelName: "",
+ bomId: undefined,
+ description: '',
+ };
+ bomOptions.value = [];
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瀛楃涓诧級
+// 浜у搧閫夋嫨澶勭悊
+const handleProductSelect = async (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ // 鍏堟煡璇OM鍒楄〃锛堝繀閫夛級
+ try {
+ const res = await getByModel(product.id);
+ // 澶勭悊杩斿洖鐨凚OM鏁版嵁锛氬彲鑳芥槸鏁扮粍銆佸璞℃垨鍖呭惈data瀛楁
+ let bomList = [];
+ if (Array.isArray(res)) {
+ bomList = res;
+ } else if (res && res.data) {
+ bomList = Array.isArray(res.data) ? res.data : [res.data];
+ } else if (res && typeof res === 'object') {
+ bomList = [res];
}
+
+ if (bomList.length > 0) {
+ formState.value.productModelId = product.id;
+ formState.value.productName = product.productName;
+ formState.value.productModelName = product.model;
+ formState.value.bomId = undefined; // 閲嶇疆BOM閫夋嫨
+ bomOptions.value = bomList;
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉鏇存柊
+ proxy.$refs["formRef"]?.validateField('productModelId');
+ } else {
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
+ }
+ } catch (error) {
+ // 濡傛灉鎺ュ彛杩斿洖404鎴栧叾浠栭敊璇紝璇存槑娌℃湁BOM
+ proxy.$modal.msgError("璇ヤ骇鍝佹病鏈塀OM锛岃鍏堝垱寤築OM");
}
}
- 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) {
+ // 楠岃瘉鏄惁閫夋嫨浜嗕骇鍝佸拰BOM
+ if (!formState.value.productModelId) {
+ proxy.$modal.msgError("璇烽�夋嫨浜у搧");
+ return;
+ }
+ if (!formState.value.bomId) {
+ proxy.$modal.msgError("璇烽�夋嫨BOM");
+ return;
+ }
add(formState.value).then(res => {
// 鍏抽棴妯℃�佹
isShow.value = false;
@@ -174,8 +191,4 @@
handleSubmit,
isShow,
});
-
-onMounted(() => {
- getProductOptions()
-})
</script>
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
index 7d5ab5d..41103f9 100644
--- a/src/views/productionManagement/processRoute/index.vue
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -80,6 +80,10 @@
prop: "model",
},
{
+ label: "BOM缂栧彿",
+ prop: "bomNo",
+ },
+ {
label: "鎻忚堪",
prop: "description",
},
@@ -166,7 +170,13 @@
router.push({
path: '/productionManagement/processRouteItem',
query: {
- id: row.id
+ id: row.id,
+ processRouteCode: row.processRouteCode || '',
+ productName: row.productName || '',
+ model: row.model || '',
+ bomNo: row.bomNo || '',
+ description: row.description || '',
+ type: 'route',
}
})
};
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 641ffff..18e21e8 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -1,133 +1,207 @@
<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>
+ <PageHeader content="宸ヨ壓璺嚎椤圭洰" />
+
+ <!-- 宸ヨ壓璺嚎淇℃伅灞曠ず -->
+ <el-card v-if="routeInfo.processRouteCode" class="route-info-card" shadow="hover">
+ <div class="route-info">
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">宸ヨ壓璺嚎缂栧彿</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.processRouteCode }}</span>
+ </div>
+ </div>
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">浜у搧鍚嶇О</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.productName || '-' }}</span>
+ </div>
+ </div>
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">瑙勬牸鍚嶇О</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.model || '-' }}</span>
+ </div>
+ </div>
+ <div class="info-item">
+ <div class="info-label-wrapper">
+ <span class="info-label">BOM缂栧彿</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.bomNo || '-' }}</span>
+ </div>
+ </div>
+ <div class="info-item full-width" v-if="routeInfo.description">
+ <div class="info-label-wrapper">
+ <span class="info-label">鎻忚堪</span>
+ </div>
+ <div class="info-value-wrapper">
+ <span class="info-value">{{ routeInfo.description }}</span>
+ </div>
+ </div>
</div>
-
- <el-switch
- v-model="isTable"
- inline-prompt
- active-text="琛ㄦ牸"
- inactive-text="鍒楄〃"
- @change="handleViewChange"
- />
+ </el-card>
+
+ <!-- 琛ㄦ牸瑙嗗浘 -->
+ <div v-if="viewMode === 'table'" class="section-header">
+ <div class="section-title">宸ヨ壓璺嚎椤圭洰鍒楄〃</div>
+ <div class="section-actions">
+ <el-button
+ icon="Grid"
+ @click="toggleView"
+ style="margin-right: 10px;"
+ >
+ 鍗$墖瑙嗗浘
+ </el-button>
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ </div>
</div>
<el-table
- v-if="isTable"
- ref="multipleTable"
+ v-if="viewMode === 'table'"
+ ref="tableRef"
v-loading="tableLoading"
border
- :data="routeItems"
+ :data="tableData"
: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">
+ <el-table-column align="center" label="搴忓彿" width="60" type="index" />
+ <el-table-column label="宸ュ簭鍚嶇О" prop="processId" width="200">
<template #default="scope">
- {{ scope.$index + 1 }}
+ {{ getProcessName(scope.row.processId) || '-' }}
</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>
+ <el-table-column label="浜у搧鍚嶇О" prop="productName" min-width="160" />
+ <el-table-column label="瑙勬牸鍚嶇О" prop="model" min-width="140" />
+ <el-table-column label="鍗曚綅" prop="unit" width="100" />
+ <el-table-column label="鎿嶄綔" align="center" fixed="right" width="150">
+ <template #default="scope">
+ <el-button type="primary" link size="small" @click="handleEdit(scope.row)">缂栬緫</el-button>
+ <el-button type="danger" link size="small" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
</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;"
+
+ <!-- 鍗$墖瑙嗗浘 -->
+ <template v-else>
+ <div class="section-header">
+ <div class="section-title">宸ヨ壓璺嚎椤圭洰鍒楄〃</div>
+ <div class="section-actions">
+ <el-button
+ icon="Menu"
+ @click="toggleView"
+ style="margin-right: 10px;"
>
- <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>
+ 琛ㄦ牸瑙嗗浘
+ </el-button>
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
</div>
</div>
- </div>
+ <div v-loading="tableLoading" class="card-container">
+ <div
+ ref="cardsContainer"
+ class="cards-wrapper"
+ >
+ <div
+ v-for="(item, index) in tableData"
+ :key="item.id || index"
+ class="process-card"
+ :data-index="index"
+ >
+ <!-- 搴忓彿鍦嗗湀 -->
+ <div class="card-header">
+ <div class="card-number">{{ index + 1 }}</div>
+ <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div>
+ </div>
+
+ <!-- 浜у搧淇℃伅 -->
+ <div class="card-content">
+ <div v-if="item.productName" class="product-info">
+ <div class="product-name">{{ item.productName }}</div>
+ <div v-if="item.model" class="product-model">
+ {{ item.model }}
+ <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
+ </div>
+ </div>
+ <div v-else class="product-info empty">鏆傛棤浜у搧淇℃伅</div>
+ </div>
+
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div class="card-footer">
+ <el-button type="primary" link size="small" @click="handleEdit(item)">缂栬緫</el-button>
+ <el-button type="danger" link size="small" @click="handleDelete(item)">鍒犻櫎</el-button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </template>
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog
+ v-model="dialogVisible"
+ :title="operationType === 'add' ? '鏂板宸ヨ壓璺嚎椤圭洰' : '缂栬緫宸ヨ壓璺嚎椤圭洰'"
+ width="500px"
+ @close="closeDialog"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="120px"
+ >
+ <el-form-item label="宸ュ簭" prop="processId">
+ <el-select
+ v-model="form.processId"
+ placeholder="璇烽�夋嫨宸ュ簭"
+ clearable
+ style="width: 100%"
+ >
+ <el-option
+ v-for="process in processOptions"
+ :key="process.id"
+ :label="process.name"
+ :value="process.id"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="浜у搧鍚嶇О" prop="productModelId">
+ <el-button type="primary" @click="showProductSelectDialog = true">
+ {{ form.productName && form.model
+ ? `${form.productName} - ${form.model}`
+ : '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+
+ <el-form-item label="鍗曚綅" prop="unit">
+ <el-input
+ v-model="form.unit"
+ :placeholder="form.productModelId ? '鏍规嵁閫夋嫨鐨勪骇鍝佽嚜鍔ㄥ甫鍑�' : '璇峰厛閫夋嫨浜у搧'"
+ clearable
+ :disabled="true"
+ />
+ </el-form-item>
+ </el-form>
+
+ <template #footer>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit" :loading="submitLoading">纭畾</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 浜у搧閫夋嫨瀵硅瘽妗� -->
<ProductSelectDialog
- v-model="isShowProductSelectDialog"
- @confirm="handelSelectProducts"
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
/>
</div>
</template>
@@ -135,178 +209,285 @@
<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 { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
+import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem } from "@/api/productionManagement/productProcessRoute.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);
+import { useRoute } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
+import Sortable from 'sortablejs'
const route = useRoute()
-const router = useRouter()
-const routeId = computed({
- get() {
- return route.query.id;
- },
+const { proxy } = getCurrentInstance() || {};
- set(val) {
- emit('update:router', val)
- }
+const routeId = computed(() => route.query.id);
+const orderId = computed(() => route.query.orderId);
+const pageType = computed(() => route.query.type);
+
+const tableLoading = ref(false);
+const tableData = ref([]);
+const dialogVisible = ref(false);
+const operationType = ref('add'); // add | edit
+const formRef = ref(null);
+const submitLoading = ref(false);
+const cardsContainer = ref(null);
+const tableRef = ref(null);
+const viewMode = ref('table'); // table | card
+const routeInfo = ref({
+ processRouteCode: '',
+ productName: '',
+ model: '',
+ bomNo: '',
+ description: ''
});
+const processOptions = ref([]);
+const showProductSelectDialog = ref(false);
+let tableSortable = null;
+let cardSortable = null;
-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瀹屽叏娓叉煋
+// 鍒囨崲瑙嗗浘
+const toggleView = () => {
+ viewMode.value = viewMode.value === 'table' ? 'card' : 'table';
+ // 鍒囨崲瑙嗗浘鍚庨噸鏂板垵濮嬪寲鎷栨嫿鎺掑簭
nextTick(() => {
- // 寮哄埗閲嶆柊娓叉煋缁勪欢
- if (proxy?.$forceUpdate) {
- proxy.$forceUpdate();
- }
-
- const temp = [...routeItems.value];
- routeItems.value = [];
- nextTick(() => {
- routeItems.value = temp;
- initSortable();
- });
+ initSortable();
});
};
-const findProcessRouteItems = () => {
+const form = ref({
+ id: undefined,
+ routeId: routeId.value,
+ processId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+});
+
+const rules = {
+ processId: [{ required: true, message: '璇烽�夋嫨宸ュ簭', trigger: 'change' }],
+ productModelId: [{ required: true, message: '璇烽�夋嫨浜у搧', trigger: 'change' }],
+};
+
+// 鏍规嵁宸ュ簭ID鑾峰彇宸ュ簭鍚嶇О
+const getProcessName = (processId) => {
+ if (!processId) return '';
+ const process = processOptions.value.find(p => p.id === processId);
+ return process ? process.name : '';
+};
+
+// 鑾峰彇鍒楄〃
+const getList = () => {
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 listPromise =
+ pageType.value === "order"
+ ? findProductProcessRouteItemList({ orderId: orderId.value })
+ : findProcessRouteItemList({ routeId: routeId.value });
+
+ listPromise
+ .then(res => {
+ tableData.value = res.data || [];
+ tableLoading.value = false;
+ // 鍒楄〃鍔犺浇瀹屾垚鍚庡垵濮嬪寲鎷栨嫿鎺掑簭
+ nextTick(() => {
+ initSortable();
});
+ })
+ .catch(err => {
+ tableLoading.value = false;
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ proxy?.$modal?.msgError("鑾峰彇鍒楄〃澶辫触");
+ });
};
-const findProcessList = () => {
+// 鑾峰彇宸ュ簭鍒楄〃
+const getProcessList = () => {
processList({})
- .then(res => {
- processOptions.value = res.data;
- })
- .catch(err => {
- console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
- });
+ .then(res => {
+ processOptions.value = res.data || [];
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+ });
};
-const { proxy } = getCurrentInstance() || {};
+// 鑾峰彇宸ヨ壓璺嚎璇︽儏锛堜粠璺敱鍙傛暟鑾峰彇锛�
+const getRouteInfo = () => {
+ routeInfo.value = {
+ processRouteCode: route.query.processRouteCode || '',
+ productName: route.query.productName || '',
+ model: route.query.model || '',
+ bomNo: route.query.bomNo || '',
+ description: route.query.description || ''
+ };
+};
-const handleSubmit = () => {
- const hasEmptyProcess = routeItems.value.some(item => !item.processId);
- if (hasEmptyProcess) {
- proxy?.$modal?.msgError("璇蜂负鎵�鏈夐」鐩�夋嫨宸ュ簭");
- return;
- }
+// 鏂板
+const handleAdd = () => {
+ operationType.value = 'add';
+ resetForm();
+ dialogVisible.value = true;
+};
- addOrUpdateProcessRouteItem({
+// 缂栬緫
+const handleEdit = (row) => {
+ operationType.value = 'edit';
+ form.value = {
+ id: row.id,
routeId: routeId.value,
- processRouteItem: routeItems.value.map(({ id, ...item }) => item)
+ processId: row.processId,
+ productModelId: row.productModelId,
+ productName: row.productName || "",
+ model: row.model || "",
+ unit: row.unit || "",
+ };
+ dialogVisible.value = true;
+};
+
+// 鍒犻櫎
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇ュ伐鑹鸿矾绾块」鐩紵', '鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
})
- .then(res => {
- router.push({
- path: '/productionManagement/processRoute',
+ .then(() => {
+ // 鐢熶骇璁㈠崟涓嬩娇鐢� productProcessRoute 鐨勫垹闄ゆ帴鍙o紙璺敱鍚庢嫾鎺� id锛夛紝鍏跺畠鎯呭喌浣跨敤宸ヨ壓璺嚎椤圭洰鎵归噺鍒犻櫎鎺ュ彛
+ const deletePromise =
+ pageType.value === 'order'
+ ? deleteRouteItem(row.id)
+ : batchDeleteProcessRouteItem([row.id]);
+
+ deletePromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess('鍒犻櫎鎴愬姛');
+ getList();
})
- proxy?.$modal?.msgSuccess("鎻愪氦鎴愬姛");
- })
- .catch(err => {
- proxy?.$modal?.msgError(`鎻愪氦澶辫触锛�${err.msg || "缃戠粶寮傚父"}`);
- });
+ .catch(() => {
+ proxy?.$modal?.msgError('鍒犻櫎澶辫触');
+ });
+ })
+ .catch(() => {});
};
-const destroySortable = () => {
- if (tableSortable) {
- tableSortable.destroy();
- tableSortable = null;
- }
- if (stepsSortable) {
- stepsSortable.destroy();
- stepsSortable = null;
+// 浜у搧閫夋嫨
+const handleProductSelect = (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ form.value.productModelId = product.id;
+ form.value.productName = product.productName;
+ form.value.model = product.model;
+ form.value.unit = product.unit || "";
+ showProductSelectDialog.value = false;
+ // 瑙﹀彂琛ㄥ崟楠岃瘉
+ formRef.value?.validateField('productModelId');
}
};
+// 鎻愪氦
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ submitLoading.value = true;
+
+ if (operationType.value === 'add') {
+ // 鏂板锛氫紶鍗曚釜瀵硅薄锛屽寘鍚玠ragSort瀛楁
+ // dragSort = 褰撳墠鍒楄〃闀垮害 + 1锛岃〃绀烘柊澧炶褰曟帓鍦ㄦ渶鍚�
+ const dragSort = tableData.value.length + 1;
+ const isOrderPage = pageType.value === 'order';
+
+ const addPromise = isOrderPage
+ ? addRouteItem({
+ productOrderId: orderId.value,
+ productRouteId: routeId.value,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ dragSort,
+ })
+ : addOrUpdateProcessRouteItem({
+ routeId: routeId.value,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ dragSort,
+ });
+
+ addPromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess('鏂板鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy?.$modal?.msgError('鏂板澶辫触');
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+ } else {
+ // 缂栬緫锛氱敓浜ц鍗曚笅浣跨敤 productProcessRoute/updateRouteItem锛屽叾瀹冩儏鍐典娇鐢ㄥ伐鑹鸿矾绾块」鐩洿鏂版帴鍙�
+ const isOrderPage = pageType.value === 'order';
+
+ const updatePromise = isOrderPage
+ ? addOrUpdateProductProcessRouteItem({
+ id: form.value.id,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ })
+ : addOrUpdateProcessRouteItem({
+ routeId: routeId.value,
+ processId: form.value.processId,
+ productModelId: form.value.productModelId,
+ id: form.value.id,
+ });
+
+ updatePromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess('淇敼鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy?.$modal?.msgError('淇敼澶辫触');
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+ }
+ }
+ });
+};
+
+// 閲嶇疆琛ㄥ崟
+const resetForm = () => {
+ form.value = {
+ id: undefined,
+ routeId: routeId.value,
+ processId: undefined,
+ productModelId: undefined,
+ productName: "",
+ model: "",
+ unit: "",
+ };
+ formRef.value?.resetFields();
+};
+
+// 鍏抽棴寮圭獥
+const closeDialog = () => {
+ dialogVisible.value = false;
+ resetForm();
+};
+
+// 鍒濆鍖栨嫋鎷芥帓搴�
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 (viewMode.value === 'table') {
+ // 琛ㄦ牸瑙嗗浘鐨勬嫋鎷芥帓搴�
+ if (!tableRef.value) return;
+
+ const tbody = tableRef.value.$el.querySelector('.el-table__body tbody') ||
+ tableRef.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
+
if (!tbody) return;
tableSortable = new Sortable(tbody, {
@@ -315,189 +496,381 @@
handle: '.el-table__row',
filter: '.el-button, .el-select',
onEnd: (evt) => {
- if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+ if (evt.oldIndex === evt.newIndex || !tableData.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);
+ // 閲嶆柊鎺掑簭鏁扮粍
+ const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
+ tableData.value.splice(evt.newIndex, 0, moveItem);
+
+ // 璁$畻鏂扮殑搴忓彿锛坉ragSort浠�1寮�濮嬶級
+ const newIndex = evt.newIndex;
+ const dragSort = newIndex + 1;
+
+ // 璋冪敤鎺掑簭鎺ュ彛
+ if (moveItem.id) {
+ const isOrderPage = pageType.value === 'order';
+ const sortPromise = isOrderPage
+ ? sortRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ })
+ : sortProcessRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ });
+
+ sortPromise
+ .then(() => {
+ // 鏇存柊鎵�鏈夎鐨刣ragSort
+ tableData.value.forEach((item, index) => {
+ if (item.id) {
+ item.dragSort = index + 1;
+ }
+ });
+ proxy?.$modal?.msgSuccess('鎺掑簭鎴愬姛');
+ })
+ .catch((err) => {
+ // 鎺掑簭澶辫触锛屾仮澶嶅師鏁扮粍
+ tableData.value.splice(newIndex, 1);
+ tableData.value.splice(evt.oldIndex, 0, moveItem);
+ proxy?.$modal?.msgError('鎺掑簭澶辫触');
+ console.error("鎺掑簭澶辫触锛�", err);
+ });
+ }
}
});
} else {
- if (!stepsContainer.value) return;
+ // 鍗$墖瑙嗗浘鐨勬嫋鎷芥帓搴�
+ if (!cardsContainer.value) return;
- // 淇敼锛氱洿鎺ヤ娇鐢╯tepsContainer.value浣滀负鎷栨嫿瀹瑰櫒
- const stepsList = stepsContainer.value;
- if (!stepsList) {
- console.warn('鏈壘鍒版楠ゆ潯鎷栨嫿瀹瑰櫒');
- return;
- }
-
- // 淇敼锛氱畝鍖栨嫋鎷介厤缃�
- stepsSortable = new Sortable(stepsList, {
+ cardSortable = new Sortable(cardsContainer.value, {
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,
+ handle: '.process-card',
+ filter: '.el-button',
onEnd: (evt) => {
- if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+ if (evt.oldIndex === evt.newIndex || !tableData.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];
+ // 閲嶆柊鎺掑簭鏁扮粍
+ const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
+ tableData.value.splice(evt.newIndex, 0, moveItem);
+
+ // 璁$畻鏂扮殑搴忓彿锛坉ragSort浠�1寮�濮嬶級
+ const newIndex = evt.newIndex;
+ const dragSort = newIndex + 1;
+
+ // 璋冪敤鎺掑簭鎺ュ彛
+ if (moveItem.id) {
+ const isOrderPage = pageType.value === 'order';
+ const sortPromise = isOrderPage
+ ? sortRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ })
+ : sortProcessRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ });
+
+ sortPromise
+ .then(() => {
+ // 鏇存柊鎵�鏈夎鐨刣ragSort
+ tableData.value.forEach((item, index) => {
+ if (item.id) {
+ item.dragSort = index + 1;
+ }
+ });
+ proxy?.$modal?.msgSuccess('鎺掑簭鎴愬姛');
+ })
+ .catch((err) => {
+ // 鎺掑簭澶辫触锛屾仮澶嶅師鏁扮粍
+ tableData.value.splice(newIndex, 1);
+ tableData.value.splice(evt.oldIndex, 0, moveItem);
+ proxy?.$modal?.msgError('鎺掑簭澶辫触');
+ console.error("鎺掑簭澶辫触锛�", err);
+ });
+ }
}
});
-
- // 璋冭瘯锛氭墦鍗板鍣ㄥ拰瀹炰緥锛岀‘璁ょ粦瀹氭垚鍔�
- console.log('姝ラ鏉℃嫋鎷藉鍣�:', stepsList);
- console.log('Sortable瀹炰緥:', stepsSortable);
}
};
-const handleViewChange = () => {
- destroySortable();
- // 寤惰繜鍒濆鍖栵紝纭繚瑙嗗浘鍒囨崲鍚嶥OM瀹屽叏娓叉煋
- nextTick(() => {
- setTimeout(() => initSortable(), 100);
- });
+// 閿�姣佹嫋鎷芥帓搴�
+const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (cardSortable) {
+ cardSortable.destroy();
+ cardSortable = null;
+ }
};
onMounted(() => {
- findProcessRouteItems();
- findProcessList();
+ getRouteInfo();
+ getList();
+ getProcessList();
});
onUnmounted(() => {
destroySortable();
});
-
-defineExpose({
- handleSubmit,
-});
</script>
<style scoped>
+.card-container {
+ padding: 20px 0;
+}
+
+.cards-wrapper {
+ display: flex;
+ gap: 16px;
+ overflow-x: auto;
+ padding: 10px 0;
+ min-height: 200px;
+}
+
+.cards-wrapper::-webkit-scrollbar {
+ height: 8px;
+}
+
+.cards-wrapper::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+.cards-wrapper::-webkit-scrollbar-thumb {
+ background: #c1c1c1;
+ border-radius: 4px;
+}
+
+.cards-wrapper::-webkit-scrollbar-thumb:hover {
+ background: #a8a8a8;
+}
+
+.process-card {
+ flex-shrink: 0;
+ width: 220px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ cursor: move;
+ transition: all 0.3s;
+}
+
+.process-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ transform: translateY(-2px);
+}
+
+.card-header {
+ text-align: center;
+ margin-bottom: 12px;
+}
+
+.card-number {
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ border-radius: 50%;
+ background: #409eff;
+ color: #fff;
+ font-weight: bold;
+ font-size: 16px;
+ margin: 0 auto 8px;
+}
+
+.card-process-name {
+ font-size: 14px;
+ color: #333;
+ font-weight: 500;
+ word-break: break-all;
+}
+
+.card-content {
+ flex: 1;
+ margin-bottom: 12px;
+ min-height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.product-info {
+ font-size: 13px;
+ color: #666;
+ text-align: center;
+ width: 100%;
+}
+
+.product-info.empty {
+ color: #999;
+ text-align: center;
+ padding: 20px 0;
+}
+
+.product-name {
+ margin-bottom: 6px;
+ word-break: break-all;
+ line-height: 1.5;
+ text-align: center;
+}
+
+.product-model {
+ color: #909399;
+ font-size: 12px;
+ word-break: break-all;
+ line-height: 1.5;
+ text-align: center;
+}
+
+.product-unit {
+ margin-left: 4px;
+ color: #409eff;
+}
+
+.card-footer {
+ display: flex;
+ justify-content: space-around;
+ padding-top: 12px;
+ border-top: 1px solid #f0f0f0;
+}
+
+.card-footer .el-button {
+ padding: 0;
+ font-size: 12px;
+}
+
:deep(.sortable-ghost) {
- opacity: 0.6;
+ opacity: 0.5;
background-color: #f5f7fa !important;
}
+:deep(.sortable-drag) {
+ opacity: 0.8;
+}
+
+/* 琛ㄦ牸瑙嗗浘鏍峰紡 */
:deep(.el-table__row) {
transition: background-color 0.2s;
+ cursor: move;
}
:deep(.el-table__row:hover) {
background-color: #f9fafc !important;
}
-:deep(.el-card__footer){
- padding: 0 !important;
+/* 鍖哄煙鏍囬鏍峰紡 */
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
}
-.operate-button {
+.section-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ padding-left: 12px;
+ position: relative;
+ margin-bottom: 0;
+}
+
+.section-title::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 3px;
+ height: 16px;
+ background: #409eff;
+ border-radius: 2px;
+}
+
+.section-actions {
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;
+/* 宸ヨ壓璺嚎淇℃伅鍗$墖鏍峰紡 */
+.route-info-card {
+ margin-bottom: 20px;
+ border: 1px solid #e4e7ed;
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+ border-radius: 8px;
+ overflow: hidden;
}
-/* 淇敼锛氳嚜瀹氫箟姝ラ椤规牱寮� */
-.custom-step {
- cursor: move !important;
- padding: 8px;
- position: relative;
- transition: all 0.2s ease;
- flex: 0 0 auto;
- min-width: 220px;
- touch-action: none;
+.route-info {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 16px;
+ padding: 4px;
}
-/* 鎷栨嫿鎮诞鏍峰紡锛屾彁绀哄彲鎷栨嫿 */
-.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 {
+.info-item {
display: flex;
flex-direction: column;
- align-items: center;
- height: 140px;
+ background: #ffffff;
+ border-radius: 6px;
+ padding: 14px 16px;
+ border: 1px solid #f0f2f5;
+ transition: all 0.3s ease;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
-.step-card-footer {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- padding: 10px;
+.info-item:hover {
+ border-color: #409eff;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
+ transform: translateY(-1px);
}
-/* 鑷畾涔夊簭鍙锋牱寮忎紭鍖� */
-.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;
+.info-item.full-width {
+ grid-column: 1 / -1;
+}
+
+.info-label-wrapper {
+ margin-bottom: 8px;
+}
+
+.info-label {
+ display: inline-block;
+ color: #909399;
+ font-size: 12px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ padding: 2px 0;
+ position: relative;
+}
+
+.info-label::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 20px;
+ height: 2px;
+ background: linear-gradient(90deg, #409eff, transparent);
+ border-radius: 1px;
+}
+
+.info-value-wrapper {
+ flex: 1;
+}
+
+.info-value {
+ display: block;
+ color: #303133;
+ font-size: 15px;
+ font-weight: 500;
+ line-height: 1.5;
+ word-break: break-all;
}
</style>
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
index a131830..20a472b 100644
--- a/src/views/productionManagement/productStructure/Detail/index.vue
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -1,30 +1,32 @@
<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>
+ <PageHeader content="浜у搧缁撴瀯璇︽儏">
+ <template #right-button>
+ <el-button v-if="dataValue.isEdit && !isOrderPage"
+ type="primary"
+ @click="addItem">娣诲姞
+ </el-button>
+ <el-button v-if="!dataValue.isEdit && !isOrderPage"
+ type="primary"
+ @click="dataValue.isEdit = true">缂栬緫
+ </el-button>
+ <el-button v-if="dataValue.isEdit && !isOrderPage"
+ type="primary"
+ @click="cancelEdit">鍙栨秷
+ </el-button>
+ <el-button v-if="!isOrderPage"
+ type="primary"
+ :loading="dataValue.loading"
+ @click="submit"
+ :disabled="!dataValue.isEdit">纭
+ </el-button>
+ </template>
+ </PageHeader>
<el-table
:data="tableData"
border
:preserve-expanded-content="false"
+ :default-expand-all="true"
style="width: 100%"
>
<el-table-column type="expand">
@@ -91,7 +93,8 @@
</el-form-item>
</template>
</el-table-column>
- <el-table-column prop="demandedQuantity"
+ <el-table-column v-if="isOrderPage"
+ prop="demandedQuantity"
label="闇�姹傛�婚噺">
<template #default="{ row, $index }">
<el-form-item :prop="`dataList.${$index}.demandedQuantity`"
@@ -120,25 +123,10 @@
</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"
+ <el-button v-if="dataValue.isEdit"
+ type="danger"
text
@click="dataValue.dataList.splice($index, 1)">鍒犻櫎
</el-button>
@@ -148,10 +136,9 @@
</el-form>
</template>
</el-table-column>
- <el-table-column label="浜у搧缂栫爜" prop="productCode" />
+ <el-table-column label="BOM缂栧彿" prop="bomNo" />
<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"
@@ -170,6 +157,7 @@
ref,
} from "vue";
import { queryList, add } from "@/api/productionManagement/productStructure.js";
+import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
import { list } from "@/api/productionManagement/productionProcess";
import { ElMessage } from "element-plus";
import {useRoute, useRouter} from "vue-router";
@@ -195,6 +183,13 @@
}
});
+// 浠庤矾鐢卞弬鏁拌幏鍙栦骇鍝佷俊鎭�
+const routeBomNo = computed(() => route.query.bomNo || '');
+const routeProductName = computed(() => route.query.productName || '');
+const routeProductModelName = computed(() => route.query.productModelName || '');
+const routeOrderId = computed(() => route.query.orderId);
+const pageType = computed(() => route.query.type);
+const isOrderPage = computed(() => pageType.value === 'order' && routeOrderId.value);
const dataValue = reactive({
dataList: [],
@@ -210,8 +205,7 @@
{
productName: "",
model: "",
- unit: "",
- productCode: "",
+ bomNo: "",
}
])
@@ -221,12 +215,15 @@
};
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;
+ if (isOrderPage.value) {
+ // 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
+ const { data } = await listProcessBom({ orderId: routeOrderId.value });
+ dataValue.dataList = data || [];
+ } else {
+ // 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
+ const { data } = await queryList(routeId.value);
+ dataValue.dataList = data || [];
+ }
};
const fetchProcessOptions = async () => {
@@ -242,6 +239,7 @@
row[0].productName;
dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+ dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
dataValue.showProductDialog = false;
};
@@ -251,7 +249,7 @@
dataValue.loading = true;
if (valid) {
add({
- parentId: routeId.value,
+ bomId: routeId.value,
productStructureList: dataValue.dataList || [],
}).then(res => {
router.push({
@@ -277,7 +275,6 @@
unitQuantity: 0,
demandedQuantity: 0,
unit: "",
- diskQuantity: 0,
});
};
@@ -287,6 +284,16 @@
};
onMounted(() => {
+ // 浠庤矾鐢卞弬鏁板洖鏄炬暟鎹�
+ tableData[0].productName = routeProductName.value;
+ tableData[0].model = routeProductModelName.value;
+ tableData[0].bomNo = routeBomNo.value;
+
+ // 璁㈠崟鎯呭喌涓嬬鐢ㄧ紪杈�
+ if (isOrderPage.value) {
+ dataValue.isEdit = false;
+ }
+
fetchData();
fetchProcessOptions();
});
diff --git a/src/views/productionManagement/productStructure/index.vue b/src/views/productionManagement/productStructure/index.vue
index e32ff8d..d8ce689 100644
--- a/src/views/productionManagement/productStructure/index.vue
+++ b/src/views/productionManagement/productStructure/index.vue
@@ -1,5 +1,9 @@
<template>
<div class="app-container">
+ <div style="text-align: right; margin-bottom: 10px;">
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
+ <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">鍒犻櫎</el-button>
+ </div>
<PIMTable
rowKey="id"
:column="tableColumn"
@@ -14,100 +18,323 @@
<el-button
type="primary"
text
- @click="showDetail(row.id)">{{ row.productName }}
+ @click="showDetail(row)">{{ row.bomNo }}
</el-button>
</template>
</PIMTable>
<StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow"/>
+
+ <!-- 鏂板/缂栬緫寮圭獥 -->
+ <el-dialog
+ v-model="dialogVisible"
+ :title="operationType === 'add' ? '鏂板BOM' : '缂栬緫BOM'"
+ width="600px"
+ @close="closeDialog"
+ >
+ <el-form
+ ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="120px"
+ >
+ <el-form-item label="浜у搧鍚嶇О" prop="productModelId">
+ <el-button type="primary" @click="showProductSelectDialog = true">
+ {{ form.productName || '閫夋嫨浜у搧' }}
+ </el-button>
+ </el-form-item>
+ <el-form-item label="鐗堟湰鍙�" prop="version">
+ <el-input v-model="form.version" placeholder="璇疯緭鍏ョ増鏈彿" clearable />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ v-model="form.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉�"
+ clearable
+ />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 浜у搧閫夋嫨寮圭獥 -->
+ <ProductSelectDialog
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
+ />
</div>
</template>
<script setup>
-import {ref} from "vue";
-import {productModelList} from "@/api/basicData/productModel.js";
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
+import { listPage, add, update, batchDelete } from "@/api/productionManagement/productBom.js";
import { useRouter } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
const router = useRouter()
+const { proxy } = getCurrentInstance()
const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue'))
const tableColumn = ref([
{
- label: "浜у搧缂栫爜",
- prop: "productCode",
- slot: "detail"
+ label: "BOM缂栧彿",
+ prop: "bomNo",
+ dataType: 'slot',
+ slot: "detail",
+ minWidth: 140
},
{
label: "浜у搧鍚嶇О",
prop: "productName",
- dataType: 'slot',
- slot: "detail"
+
+ minWidth: 160
},
{
label: "瑙勬牸鍨嬪彿",
- prop: "model",
+ prop: "productModelName",
+ minWidth: 140
},
{
- label: "鍗曚綅",
- prop: "unit",
+ label: "鐗堟湰鍙�",
+ prop: "version",
+ width: 100
+ },
+ {
+ label: "澶囨敞",
+ prop: "remark",
+ minWidth: 160
+ },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 150,
+ operation: [
+ {
+ name: "缂栬緫",
+ type: "text",
+ clickFun: (row) => {
+ handleEdit(row)
+ }
+ },
+ {
+ name: "鍒犻櫎",
+ type: "danger",
+ link: true,
+ clickFun: (row) => {
+ handleDelete(row)
+ }
+ }
+ ]
}
]);
+
const tableData = ref([]);
const tableLoading = ref(false);
const showEdit = ref(false);
const selectedRows = ref([]);
const currentRow = ref({});
+const dialogVisible = ref(false);
+const operationType = ref('add'); // add | edit
+const formRef = ref(null);
+const showProductSelectDialog = ref(false);
+
const page = reactive({
current: 1,
size: 10,
total: 0,
});
+
const data = reactive({
form: {
+ id: undefined,
productName: "",
+ productModelName: "",
+ productModelId: "",
+ remark: "",
+ version: ""
},
rules: {
- productName: [{required: true, message: "璇疯緭鍏�", trigger: "blur"}],
- },
- modelForm: {
- otherModel: '',
- model: "",
- unit: "",
- speculativeTradingName: [],
- },
+ productModelId: [{ required: true, message: "璇烽�夋嫨浜у搧", trigger: "change" }],
+ version: [{ required: true, message: "璇疯緭鍏ョ増鏈彿", trigger: "blur" }]
+ }
});
-const {form, rules} = toRefs(data);
+
+const { form, rules } = toRefs(data);
+
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
selectedRows.value = selection;
};
-// 鏌ヨ瑙勬牸鍨嬪彿
+// 鍒嗛〉
const pagination = (obj) => {
page.current = obj.page;
page.size = obj.limit;
- getModelList();
+ getList();
};
-const showDetail = (id) => {
+// 鏌ヨ鍒楄〃
+const getList = () => {
+ tableLoading.value = true;
+ listPage({
+ current: page.current,
+ size: page.size,
+ })
+ .then((res) => {
+ const records = res?.data?.records || [];
+ tableData.value = records;
+ page.total = res?.data?.total || 0;
+ })
+ .catch((err) => {
+ console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+// 鏂板
+const handleAdd = () => {
+ operationType.value = 'add';
+ Object.assign(form.value, {
+ id: undefined,
+ productName: "",
+ productModelName: "",
+ productModelId: "",
+ remark: "",
+ version: ""
+ });
+ dialogVisible.value = true;
+};
+
+// 缂栬緫
+const handleEdit = (row) => {
+ operationType.value = 'edit';
+ Object.assign(form.value, {
+ id: row.id,
+ productName: row.productName || "",
+ productModelName: row.productModelName || "",
+ productModelId: row.productModelId || "",
+ remark: row.remark || "",
+ version: row.version || ""
+ });
+ dialogVisible.value = true;
+};
+
+// 鍒犻櫎锛堝崟鏉★級
+const handleDelete = (row) => {
+ ElMessageBox.confirm('纭鍒犻櫎璇OM锛�', '鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ batchDelete([row.id])
+ .then(() => {
+ proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('鍒犻櫎澶辫触');
+ });
+ })
+ .catch(() => {});
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (!selectedRows.value.length) {
+ proxy.$modal.msgWarning('璇烽�夋嫨鏁版嵁');
+ return;
+ }
+ const ids = selectedRows.value.map(item => item.id);
+ ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', {
+ confirmButtonText: '纭',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ })
+ .then(() => {
+ batchDelete(ids)
+ .then(() => {
+ proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('鍒犻櫎澶辫触');
+ });
+ })
+ .catch(() => {});
+};
+
+// 浜у搧閫夋嫨
+const handleProductSelect = (products) => {
+ if (products && products.length > 0) {
+ const product = products[0];
+ form.value.productModelId = product.id;
+ form.value.productName = product.productName;
+ form.value.productModelName = product.model;
+ }
+ showProductSelectDialog.value = false;
+};
+
+// 鎻愪氦琛ㄥ崟
+const handleSubmit = () => {
+ formRef.value.validate((valid) => {
+ if (valid) {
+ const payload = { ...form.value };
+ if (operationType.value === 'add') {
+ add(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess('鏂板鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('鏂板澶辫触');
+ });
+ } else {
+ update(payload)
+ .then(() => {
+ proxy.$modal.msgSuccess('淇敼鎴愬姛');
+ closeDialog();
+ getList();
+ })
+ .catch(() => {
+ proxy.$modal.msgError('淇敼澶辫触');
+ });
+ }
+ }
+ });
+};
+
+// 鍏抽棴寮圭獥
+const closeDialog = () => {
+ dialogVisible.value = false;
+ formRef.value?.resetFields();
+};
+
+// 鏌ョ湅璇︽儏
+const showDetail = (row) => {
router.push({
path: '/productionManagement/productStructureDetail',
query: {
- id: id
+ id: row.id,
+ bomNo: row.bomNo || '',
+ productName: row.productName || '',
+ productModelName: row.productModelName || ''
}
- })
-}
-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();
-})
+ getList();
+});
</script>
diff --git a/src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue b/src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue
deleted file mode 100644
index 354f9de..0000000
--- a/src/views/productionManagement/productionOrder/ProcessRouteItemForm.vue
+++ /dev/null
@@ -1,555 +0,0 @@
-<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 df7f950..9250eb8 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -8,7 +8,7 @@
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
- style="width: 200px;"
+ style="width: 160px;"
@change="handleQuery" />
</el-form-item>
<el-form-item label="鍚堝悓鍙�:">
@@ -16,7 +16,7 @@
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
- style="width: 200px;"
+ style="width: 160px;"
@change="handleQuery" />
</el-form-item>
<el-form-item label="浜у搧鍚嶇О:">
@@ -24,7 +24,7 @@
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
- style="width: 200px;"
+ style="width: 160px;"
@change="handleQuery" />
</el-form-item>
<el-form-item label="瑙勬牸:">
@@ -32,7 +32,7 @@
placeholder="璇疯緭鍏�"
clearable
prefix-icon="Search"
- style="width: 200px;"
+ style="width: 160px;"
@change="handleQuery" />
</el-form-item>
<el-form-item>
@@ -50,12 +50,41 @@
:tableData="tableData"
:page="page"
:tableLoading="tableLoading"
- @pagination="pagination"></PIMTable>
+ @pagination="pagination">
+ <template #completionStatus="{ row }">
+ <el-progress
+ :percentage="toProgressPercentage(row?.completionStatus)"
+ :color="progressColor(toProgressPercentage(row?.completionStatus))"
+ :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
+ />
+ </template>
+ </PIMTable>
</div>
- <process-route-item-form v-if="isShowItemModal"
- v-model:visible="isShowItemModal"
- :record="record"
- @completed="getList" />
+ <el-dialog v-model="bindRouteDialogVisible"
+ title="缁戝畾宸ヨ壓璺嚎"
+ width="500px">
+ <el-form label-width="90px">
+ <el-form-item label="宸ヨ壓璺嚎">
+ <el-select v-model="bindForm.routeId"
+ placeholder="璇烽�夋嫨宸ヨ壓璺嚎"
+ style="width: 100%;"
+ :loading="bindRouteLoading">
+ <el-option v-for="item in routeOptions"
+ :key="item.id"
+ :label="`${item.processRouteCode || ''}`"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button @click="bindRouteDialogVisible = false">鍙� 娑�</el-button>
+ <el-button type="primary"
+ :loading="bindRouteSaving"
+ @click="handleBindRouteConfirm">纭� 璁�</el-button>
+ </span>
+ </template>
+ </el-dialog>
</div>
</template>
@@ -63,30 +92,75 @@
import { onMounted, ref } from "vue";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
- import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
+ import { useRouter } from "vue-router";
+ import {
+ productOrderListPage,
+ listProcessRoute,
+ bindingRoute,
+ listProcessBom,
+ } from "@/api/productionManagement/productionOrder.js";
+ import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
const { proxy } = getCurrentInstance();
- import ProcessRouteItemForm from "@/views/productionManagement/productionOrder/ProcessRouteItemForm.vue";
+
+ const router = useRouter();
const tableColumn = ref([
{
label: "鐢熶骇璁㈠崟鍙�",
prop: "npsNo",
+ width: '120px',
},
{
label: "閿�鍞悎鍚屽彿",
prop: "salesContractNo",
+ width: '150px',
},
{
label: "瀹㈡埛鍚嶇О",
prop: "customerName",
+ width: '200px',
},
{
label: "浜у搧鍚嶇О",
prop: "productCategory",
+ width: '120px',
},
{
label: "瑙勬牸",
prop: "specificationModel",
+ width: '120px',
+ },
+ {
+ label: "宸ヨ壓璺嚎缂栧彿",
+ prop: "processRouteCode",
+ width: '140px',
+ },
+ {
+ label: "闇�姹傛暟閲�",
+ prop: "quantity",
+ },
+ {
+ label: "瀹屾垚鏁伴噺",
+ prop: "completeQuantity",
+ },
+ {
+ dataType: "slot",
+ label: "瀹屾垚杩涘害",
+ prop: "completionStatus",
+ slot: "completionStatus",
+ width: 180,
+ },
+ {
+ label: "寮�濮嬫棩鏈�",
+ prop: "startTime",
+ formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
+ width: 120,
+ },
+ {
+ label: "缁撴潫鏃ユ湡",
+ prop: "endTime",
+ formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
+ width: 120,
},
{
dataType: "action",
@@ -100,6 +174,21 @@
type: "text",
clickFun: row => {
showRouteItemModal(row);
+ },
+ },
+ {
+ name: "缁戝畾宸ヨ壓璺嚎",
+ type: "text",
+ showHide: row => !row.processRouteCode,
+ clickFun: row => {
+ openBindRouteDialog(row);
+ },
+ },
+ {
+ name: "浜у搧缁撴瀯",
+ type: "text",
+ clickFun: row => {
+ showProductStructure(row);
},
},
],
@@ -123,6 +212,77 @@
},
});
const { searchForm } = toRefs(data);
+
+ const toProgressPercentage = val => {
+ const n = Number(val);
+ if (!Number.isFinite(n)) return 0;
+ if (n <= 0) return 0;
+ if (n >= 100) return 100;
+ return Math.round(n);
+ };
+
+ // 30/50/80/100 鍒嗘棰滆壊锛氱孩/姗�/钃�/缁�
+ const progressColor = percentage => {
+ const p = toProgressPercentage(percentage);
+ if (p < 30) return "#f56c6c";
+ if (p < 50) return "#e6a23c";
+ if (p < 80) return "#409eff";
+ return "#67c23a";
+ };
+
+ // 缁戝畾宸ヨ壓璺嚎寮规
+ const bindRouteDialogVisible = ref(false);
+ const bindRouteLoading = ref(false);
+ const bindRouteSaving = ref(false);
+ const routeOptions = ref([]);
+ const bindForm = reactive({
+ orderId: null,
+ routeId: null,
+ });
+
+ const openBindRouteDialog = async row => {
+ bindForm.orderId = row.id;
+ bindForm.routeId = null;
+ bindRouteDialogVisible.value = true;
+ routeOptions.value = [];
+ if (!row.productModelId) {
+ proxy.$modal.msgWarning("褰撳墠璁㈠崟缂哄皯浜у搧鍨嬪彿锛屾棤娉曟煡璇㈠伐鑹鸿矾绾�");
+ bindRouteDialogVisible.value = false;
+ return;
+ }
+ bindRouteLoading.value = true;
+ try {
+ const res = await listProcessRoute({ productModelId: row.productModelId });
+ routeOptions.value = res.data || [];
+ } catch (e) {
+ console.error("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触锛�", e);
+ proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触");
+ } finally {
+ bindRouteLoading.value = false;
+ }
+ };
+
+ const handleBindRouteConfirm = async () => {
+ if (!bindForm.routeId) {
+ proxy.$modal.msgWarning("璇烽�夋嫨宸ヨ壓璺嚎");
+ return;
+ }
+ bindRouteSaving.value = true;
+ try {
+ await bindingRoute({
+ id: bindForm.orderId,
+ routeId: bindForm.routeId,
+ });
+ proxy.$modal.msgSuccess("缁戝畾鎴愬姛");
+ bindRouteDialogVisible.value = false;
+ getList();
+ } catch (e) {
+ console.error("缁戝畾宸ヨ壓璺嚎澶辫触锛�", e);
+ proxy.$modal.msgError("缁戝畾宸ヨ壓璺嚎澶辫触");
+ } finally {
+ bindRouteSaving.value = false;
+ }
+ };
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
@@ -161,11 +321,46 @@
});
};
- const isShowItemModal = ref(false);
- const record = ref({});
- const showRouteItemModal = row => {
- isShowItemModal.value = true;
- record.value = row;
+ const showRouteItemModal = async row => {
+ const orderId = row.id;
+ try {
+ const res = await getOrderProcessRouteMain(orderId);
+ const data = res.data || {};
+ if (!data || !data.id) {
+ proxy.$modal.msgWarning("鏈壘鍒板叧鑱旂殑宸ヨ壓璺嚎");
+ return;
+ }
+ router.push({
+ path: "/productionManagement/processRouteItem",
+ query: {
+ id: data.id,
+ processRouteCode: data.processRouteCode || "",
+ productName: data.productName || "",
+ model: data.model || "",
+ bomNo: data.bomNo || "",
+ description: data.description || "",
+ orderId,
+ type: "order",
+ },
+ });
+ } catch (e) {
+ console.error("鑾峰彇宸ヨ壓璺嚎涓讳俊鎭け璐ワ細", e);
+ proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎淇℃伅澶辫触");
+ }
+ };
+
+ const showProductStructure = row => {
+ router.push({
+ path: "/productionManagement/productStructureDetail",
+ query: {
+ id: row.id,
+ bomNo: row.bomNo || "",
+ productName: row.productCategory || "",
+ productModelName: row.specificationModel || "",
+ orderId: row.id,
+ type: "order",
+ },
+ });
};
// 瀵煎嚭
@@ -176,7 +371,7 @@
type: "warning",
})
.then(() => {
- proxy.download("/salesLedger/scheduling/export", {}, "鐢熶骇璁㈠崟.xlsx");
+ proxy.download("/productOrder/export", {...searchForm.value}, "鐢熶骇璁㈠崟.xlsx");
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
@@ -190,4 +385,7 @@
});
</script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.search_form{
+ align-items: start;
+}</style>
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
index 31e3109..f979d51 100644
--- a/src/views/productionManagement/productionProcess/Edit.vue
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -22,6 +22,9 @@
]">
<el-input v-model="formState.name" />
</el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿" prop="no">
+ <el-input v-model="formState.no" />
+ </el-form-item>
<el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
<el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
</el-form-item>
@@ -40,7 +43,7 @@
</template>
<script setup>
-import { ref, computed, getCurrentInstance } from "vue";
+import { ref, computed, getCurrentInstance, watch } from "vue";
import {update} from "@/api/productionManagement/productionProcess.js";
const props = defineProps({
@@ -61,6 +64,7 @@
const formState = ref({
id: props.record.id,
name: props.record.name,
+ no: props.record.no,
remark: props.record.remark,
salaryQuota: props.record.salaryQuota,
});
@@ -74,6 +78,32 @@
},
});
+// 鐩戝惉 record 鍙樺寲锛屾洿鏂拌〃鍗曟暟鎹�
+watch(() => props.record, (newRecord) => {
+ if (newRecord && isShow.value) {
+ formState.value = {
+ id: newRecord.id,
+ name: newRecord.name || '',
+ no: newRecord.no || '',
+ remark: newRecord.remark || '',
+ salaryQuota: newRecord.salaryQuota || '',
+ };
+ }
+}, { immediate: true, deep: true });
+
+// 鐩戝惉寮圭獥鎵撳紑锛岄噸鏂板垵濮嬪寲琛ㄥ崟鏁版嵁
+watch(() => props.visible, (visible) => {
+ if (visible && props.record) {
+ formState.value = {
+ id: props.record.id,
+ name: props.record.name || '',
+ no: props.record.no || '',
+ remark: props.record.remark || '',
+ salaryQuota: props.record.salaryQuota || '',
+ };
+ }
+});
+
let { proxy } = getCurrentInstance()
const closeModal = () => {
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
index a73a755..7558ba7 100644
--- a/src/views/productionManagement/productionProcess/New.vue
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -22,6 +22,9 @@
]">
<el-input v-model="formState.name" />
</el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿" prop="no">
+ <el-input v-model="formState.no" />
+ </el-form-item>
<el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
<el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
</el-form-item>
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
index ea2a341..7ab8c9a 100644
--- a/src/views/productionManagement/productionProcess/index.vue
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -30,6 +30,7 @@
class="mb10">
<el-button type="primary"
@click="showNewModal">鏂板宸ュ簭</el-button>
+ <el-button type="info" plain @click="handleImport">瀵煎叆</el-button>
<el-button type="danger"
@click="handleDelete"
:disabled="selectedRows.length === 0"
@@ -52,14 +53,29 @@
v-model:visible="isShowEditModal"
:record="record"
@completed="getList" />
+ <ImportDialog
+ ref="importDialogRef"
+ v-model="importDialogVisible"
+ title="瀵煎叆宸ュ簭"
+ :action="importAction"
+ :headers="importHeaders"
+ :auto-upload="false"
+ :on-success="handleImportSuccess"
+ :on-error="handleImportError"
+ @confirm="handleImportConfirm"
+ @download-template="handleDownloadTemplate"
+ @close="handleImportClose"
+ />
</div>
</template>
<script setup>
- import { onMounted, ref } from "vue";
+ import { onMounted, ref, reactive, toRefs, getCurrentInstance } 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";
+ import ImportDialog from "@/components/Dialog/ImportDialog.vue";
+ import { listPage, del, importData, downloadTemplate } from "@/api/productionManagement/productionProcess.js";
+ import { getToken } from "@/utils/auth";
const data = reactive({
searchForm: {
@@ -70,13 +86,14 @@
const { searchForm } = toRefs(data);
const tableColumn = ref([
{
- label: "宸ュ簭鍚嶇О",
- prop: "name",
- },
- {
label: "宸ュ簭缂栧彿",
prop: "no",
},
+ {
+ label: "宸ュ簭鍚嶇О",
+ prop: "name",
+ },
+
{
label: "宸ヨ祫瀹氶",
prop: "salaryQuota",
@@ -84,6 +101,10 @@
{
label: "澶囨敞",
prop: "remark",
+ },
+ {
+ label: "鏇存柊鏃堕棿",
+ prop: "updateTime",
},
{
dataType: "action",
@@ -108,12 +129,18 @@
const isShowNewModal = ref(false);
const isShowEditModal = ref(false);
const record = ref({});
+ const importDialogVisible = ref(false);
+ const importDialogRef = ref(null);
const page = reactive({
current: 1,
size: 100,
total: 0,
});
const { proxy } = getCurrentInstance();
+
+ // 瀵煎叆鐩稿叧閰嶇疆
+ const importAction = import.meta.env.VITE_APP_BASE_API + "/productProcess/importData";
+ const importHeaders = { Authorization: "Bearer " + getToken() };
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
@@ -195,6 +222,63 @@
}
}
+ // 瀵煎叆
+ const handleImport = () => {
+ importDialogVisible.value = true;
+ };
+
+ // 纭瀵煎叆
+ const handleImportConfirm = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.submit();
+ }
+ };
+
+ // 瀵煎叆鎴愬姛
+ const handleImportSuccess = (response) => {
+ if (response.code === 200) {
+ proxy.$modal.msgSuccess("瀵煎叆鎴愬姛");
+ importDialogVisible.value = false;
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles();
+ }
+ getList();
+ } else {
+ proxy.$modal.msgError(response.msg || "瀵煎叆澶辫触");
+ }
+ };
+
+ // 瀵煎叆澶辫触
+ const handleImportError = (error) => {
+ proxy.$modal.msgError("瀵煎叆澶辫触锛�" + (error.message || "鏈煡閿欒"));
+ };
+
+ // 鍏抽棴瀵煎叆寮圭獥
+ const handleImportClose = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles();
+ }
+ };
+
+ // 涓嬭浇妯℃澘
+ const handleDownloadTemplate = async () => {
+ try {
+ const res = await downloadTemplate();
+ const blob = new Blob([res], {
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ });
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = "宸ュ簭瀵煎叆妯℃澘.xlsx";
+ link.click();
+ window.URL.revokeObjectURL(url);
+ proxy.$modal.msgSuccess("妯℃澘涓嬭浇鎴愬姛");
+ } catch (error) {
+ proxy.$modal.msgError("妯℃澘涓嬭浇澶辫触");
+ }
+ };
+
// 瀵煎嚭
// const handleOut = () => {
// ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
diff --git a/src/views/productionManagement/productionReporting/Input.vue b/src/views/productionManagement/productionReporting/Input.vue
index 7959848..3ba68f7 100644
--- a/src/views/productionManagement/productionReporting/Input.vue
+++ b/src/views/productionManagement/productionReporting/Input.vue
@@ -59,13 +59,21 @@
prop: 'productNo',
},
{
- label: '浜у搧鍨嬪彿',
+ label: '鎶曞叆浜у搧鍚嶇О',
+ prop: 'productName',
+ },
+ {
+ label: '鎶曞叆浜у搧鍨嬪彿',
prop: 'model',
},
{
label: '鎶曞叆鏁伴噺',
prop: 'quantity',
},
+ {
+ label: '鍗曚綅',
+ prop: 'unit',
+ },
]
const isShow = computed({
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index b34b14e..f4137af 100644
--- a/src/views/productionManagement/productionReporting/index.vue
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -115,7 +115,7 @@
</template>
</el-table-column>
<el-table-column label="鎿嶄綔"
- width="60">
+ >
<template #default="scope">
<el-button link
type="primary"
@@ -139,9 +139,6 @@
<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>
@@ -157,7 +154,6 @@
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: {
@@ -187,92 +183,52 @@
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: "salesContractNo",
+ width: 120,
},
{
- 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: "productName",
+ width: 120,
},
{
- label: "鐢熶骇鏃堕棿",
+ label: "浜у搧瑙勬牸鍨嬪彿",
+ prop: "productModelName",
+ width: 120,
+ },
+ {
+ label: "浜у嚭鏁伴噺",
+ prop: "quantity",
+ width: 120,
+ },
+ // {
+ // label: "鎶ュ簾鏁伴噺",
+ // prop: "scrapQuantity",
+ // width: 120,
+ // },
+ {
+ label: "鍗曚綅",
+ prop: "unit",
+ width: 120,
+ },
+
+ {
+ 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);
},
},
{
@@ -449,13 +405,6 @@
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;
};
diff --git a/src/views/productionManagement/workOrder/index.vue b/src/views/productionManagement/workOrder/index.vue
index 34b368d..f5d2bc1 100644
--- a/src/views/productionManagement/workOrder/index.vue
+++ b/src/views/productionManagement/workOrder/index.vue
@@ -23,7 +23,11 @@
:tableData="tableData"
:page="page"
:tableLoading="tableLoading"
- @pagination="pagination"></PIMTable>
+ @pagination="pagination">
+ <template #completionStatus="{ row }">
+ <el-progress :percentage="toProgressPercentage(row?.completionStatus)" :color="progressColor(toProgressPercentage(row?.completionStatus))" :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
+ </template>
+ </PIMTable>
</div>
<el-dialog v-model="editDialogVisible"
title="缂栬緫鏃堕棿"
@@ -90,7 +94,7 @@
<span class="info-label">浜у搧瑙勬牸</span>
<span class="info-value">{{ transferCardRowData.model }}</span>
</div>
- <div class="info-item">
+ <!-- <div class="info-item">
<span class="info-label">宸ュ崟鐘舵��</span>
<span class="info-value">{{
transferCardRowData.status === 1 ? '寰呯‘璁�' :
@@ -99,7 +103,8 @@
transferCardRowData.status === 4 ? '宸茬敓浜�' :
transferCardRowData.status
}}</span>
- </div>
+ </div> -->
+
<div class="info-item">
<span class="info-label">璁″垝寮�濮嬫椂闂�</span>
<span class="info-value">{{ transferCardRowData.planStartTime }}</span>
@@ -115,12 +120,12 @@
</div>
<div class="info-group">
<div class="info-item">
- <span class="info-label"> </span>
- <span class="info-value"> </span>
+ <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">{{ transferCardRowData.planQuantity }}</span>
+ <span class="info-label">瀹屾垚鏁伴噺</span>
+ <span class="info-value">{{ transferCardRowData.completeQuantity }}</span>
</div>
<div class="info-item">
<span class="info-label">鑹搧鏁伴噺</span>
@@ -176,10 +181,17 @@
placeholder="璇疯緭鍏ユ湰娆$敓浜ф暟閲�" />
</el-form-item>
<el-form-item label="鐝粍淇℃伅">
- <el-input v-model="reportForm.userName"
- style="width: 300px"
- readonly
- placeholder="璇疯緭鍏ョ彮缁勪俊鎭�" />
+ <el-select v-model="reportForm.userId"
+ style="width: 300px"
+ placeholder="璇烽�夋嫨鐝粍淇℃伅"
+ clearable
+ filterable
+ @change="handleUserChange">
+ <el-option v-for="user in userOptions"
+ :key="user.userId"
+ :label="user.userName"
+ :value="user.userId" />
+ </el-select>
</el-form-item>
</el-form>
<template #footer>
@@ -202,7 +214,7 @@
updateProductWorkOrder,
addProductMain,
} from "@/api/productionManagement/workOrder.js";
- import { getUserProfile } from "@/api/system/user.js";
+ import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
import QRCode from "qrcode";
import { getCurrentInstance, reactive, toRefs } from "vue";
const { proxy } = getCurrentInstance();
@@ -236,13 +248,20 @@
prop: "processName",
},
{
- label: "寰呯敓浜ф暟閲�",
+ label: "闇�姹傛暟閲�",
prop: "planQuantity",
width: "140",
},
{
- label: "璁″垝鐢熶骇鏁伴噺",
- prop: "quantity",
+ label: "瀹屾垚鏁伴噺",
+ prop: "completeQuantity",
+ width: "140",
+ },
+ {
+ label: "瀹屾垚杩涘害",
+ prop: "completionStatus",
+ dataType: "slot",
+ slot: "completionStatus",
width: "140",
},
{
@@ -304,6 +323,7 @@
const transferCardQrUrl = ref("");
const transferCardRowData = ref(null);
const reportDialogVisible = ref(false);
+ const userOptions = ref([]);
const reportForm = reactive({
planQuantity: 0,
quantity: 0,
@@ -327,6 +347,20 @@
},
});
const { searchForm } = toRefs(data);
+ const toProgressPercentage = val => {
+ const n = Number(val);
+ if (!Number.isFinite(n)) return 0;
+ if (n <= 0) return 0;
+ if (n >= 100) return 100;
+ return Math.round(n);
+ };
+ const progressColor = percentage => {
+ const p = toProgressPercentage(percentage);
+ if (p < 30) return "#f56c6c";
+ if (p < 50) return "#e6a23c";
+ if (p < 80) return "#409eff";
+ return "#67c23a";
+ };
let editrow = ref(null);
// 鏌ヨ鍒楄〃
@@ -356,9 +390,7 @@
const showTransferCard = async row => {
transferCardRowData.value = row;
- const qrContent =
- proxy.javaApi + "/work-order?orderRow=" + JSON.stringify(row);
- console.log(qrContent, "qrContent");
+ const qrContent = String(row.id);
transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
transferCardVisible.value = true;
@@ -395,7 +427,17 @@
reportForm.workOrderId = row.id;
reportForm.reportWork = row.reportWork;
reportForm.productMainId = row.productMainId;
- // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅
+ // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅锛岃缃负榛樿閫変腑
+ getUserProfile()
+ .then(res => {
+ if (res.code === 200) {
+ reportForm.userId = res.data.userId;
+ reportForm.userName = res.data.userName;
+ }
+ })
+ .catch(err => {
+ console.error("鑾峰彇鐢ㄦ埛淇℃伅澶辫触", err);
+ });
reportDialogVisible.value = true;
};
@@ -433,18 +475,34 @@
});
};
- onMounted(() => {
- getList();
- getUserProfile()
+ // 鑾峰彇鐢ㄦ埛鍒楄〃
+ const getUserList = () => {
+ userListNoPageByTenantId()
.then(res => {
if (res.code === 200) {
- reportForm.userName = res.data.userName;
- reportForm.userId = res.data.userId;
+ userOptions.value = res.data || [];
}
})
.catch(err => {
- console.error("鑾峰彇鐢ㄦ埛淇℃伅澶辫触", err);
+ console.error("鑾峰彇鐢ㄦ埛鍒楄〃澶辫触", err);
});
+ };
+
+ // 鐢ㄦ埛閫夋嫨鍙樺寲鏃舵洿鏂� userName
+ const handleUserChange = (userId) => {
+ if (userId) {
+ const selectedUser = userOptions.value.find(user => user.userId === userId);
+ if (selectedUser) {
+ reportForm.userName = selectedUser.userName;
+ }
+ } else {
+ reportForm.userName = "";
+ }
+ };
+
+ onMounted(() => {
+ getList();
+ getUserList();
});
</script>
diff --git a/src/views/salesManagement/receiptPayment/index.vue b/src/views/salesManagement/receiptPayment/index.vue
index 40e6f14..66af76a 100644
--- a/src/views/salesManagement/receiptPayment/index.vue
+++ b/src/views/salesManagement/receiptPayment/index.vue
@@ -523,7 +523,7 @@
receiptPaymentType: row.receiptPaymentType,
receiptPaymentAmount: row.receiptPaymentAmount,
};
- receiptPaymentSaveOrUpdate(updateData).then((res) => {
+ receiptPaymentSaveOrUpdate([updateData]).then((res) => {
row.editType = !row.editType;
getList();
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
diff --git a/src/views/salesManagement/receiptPaymentHistory/index.vue b/src/views/salesManagement/receiptPaymentHistory/index.vue
index 7bcb433..f66bed7 100644
--- a/src/views/salesManagement/receiptPaymentHistory/index.vue
+++ b/src/views/salesManagement/receiptPaymentHistory/index.vue
@@ -27,6 +27,13 @@
<el-form-item>
<el-button type="primary" @click="handleQuery"> 鎼滅储 </el-button>
<el-button @click="handleExport">瀵煎嚭</el-button>
+ <el-button
+ type="danger"
+ :disabled="selectedRows.length === 0"
+ @click="handleBatchDelete"
+ >
+ 鎵归噺鍒犻櫎 ({{ selectedRows.length }})
+ </el-button>
</el-form-item>
</el-form>
<div class="table_list">
@@ -42,7 +49,18 @@
:total="page.total"
@pagination="pagination"
@selection-change="handleSelectionChange"
- ></PIMTable>
+ >
+ <template #operation="{ row }">
+ <el-button
+ type="primary"
+ link
+ size="small"
+ @click="handleDelete(row)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </PIMTable>
</div>
</div>
</template>
@@ -50,7 +68,8 @@
<script setup>
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
-import { receiptPaymentHistoryListPage } from "@/api/salesManagement/receiptPayment.js";
+import { ElMessageBox } from "element-plus";
+import { receiptPaymentHistoryListPage, receiptPaymentDel } from "@/api/salesManagement/receiptPayment.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
@@ -104,6 +123,14 @@
label: "鐧昏鏃ユ湡",
prop: "createTime",
width:100
+ },
+ {
+ label: "鎿嶄綔",
+ dataType: "slot",
+ fixed: "right",
+ slot: "operation",
+ width: 100,
+ align: "center",
},
]);
const tableData = ref([]);
@@ -175,6 +202,66 @@
getList();
};
+// 鍒犻櫎
+const handleDelete = (row) => {
+ ElMessageBox.confirm("纭鍒犻櫎璇ヨ褰曞悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(async () => {
+ try {
+ tableLoading.value = true;
+ await receiptPaymentDel([row.id]);
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ getList();
+ } catch (error) {
+ console.error("鍒犻櫎澶辫触:", error);
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ } finally {
+ tableLoading.value = false;
+ }
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑堝垹闄�");
+ });
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉℃暟鎹悧锛焋,
+ "鍒犻櫎鎻愮ず",
+ {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ )
+ .then(async () => {
+ try {
+ tableLoading.value = true;
+ const ids = selectedRows.value.map((item) => item.id);
+ await receiptPaymentDel(ids);
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ selectedRows.value = [];
+ getList();
+ } catch (error) {
+ console.error("鍒犻櫎澶辫触:", error);
+ proxy.$modal.msgError("鍒犻櫎澶辫触");
+ } finally {
+ tableLoading.value = false;
+ }
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
+
// 瀵煎嚭
const handleExport = () => {
const { receiptPaymentDate, ...rest } = searchForm;
--
Gitblit v1.9.3