| multiple/config.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/collaborativeApproval/approvalProcess.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/product/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/collaborativeApproval/approvalProcess/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/procurementManagement/procurementLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesQuotation/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
multiple/config.json
@@ -10,8 +10,8 @@ "HBTM": { "env": { "VITE_APP_TITLE": "鹤å£å¤©æ²ä¿¡æ¯ç®¡çç³»ç»", "VITE_BASE_API": "http://36.133.46.107:9030", "VITE_JAVA_API": "http://36.133.46.107:9031" "VITE_BASE_API": "http://1.15.17.182:9028", "VITE_JAVA_API": "http://1.15.17.182:9029" }, "screen": "screen/login-background.png", "logo": "logo/Logo.png", src/api/collaborativeApproval/approvalProcess.js
@@ -60,4 +60,28 @@ url: '/approveNode/details/' + query, method: 'get', }) } // ç»´æ¤å®¡æ¹äººæ°å¢-æ´æ° export function addApproveUser(query) { return request({ url: '/approveUser/add', method: 'post', data: query, }) } // å é¤å®¡æ¹äºº export function deleteApproveUser(query) { return request({ url: '/approveUser/del', method: 'delete', data: query, }) } // æ¥è¯¢å®¡æ¹äºº export function approveUserList(query) { return request({ url: '/approveUser/getList', method: 'get', params: query, }) } src/views/basicData/product/index.vue
@@ -2,38 +2,31 @@ <div class="app-container product-view"> <div class="left"> <div> <el-input v-model="search" style="width: 210px" placeholder="è¾å ¥å ³é®åè¿è¡æç´¢" @input="searchFilter" @change="searchFilter" @clear="searchFilter" clearable prefix-icon="Search" /> <el-button type="primary" @click="openProDia('addOne')" style="margin-left: 10px" >æ°å¢äº§å大类</el-button > <el-input v-model="search" style="width: 210px" placeholder="è¾å ¥å ³é®åè¿è¡æç´¢" @input="searchFilter" @change="searchFilter" @clear="searchFilter" clearable prefix-icon="Search" /> <el-button type="primary" @click="openProDia('addOne')" style="margin-left: 10px">æ°å¢äº§å大类</el-button> </div> <div ref="containerRef"> <el-tree ref="tree" v-loading="treeLoad" :data="list" @node-click="handleNodeClick" :expand-on-click-node="false" :default-expanded-keys="expandedKeys" :filter-node-method="filterNode" :props="{ children: 'children', label: 'label' }" highlight-current node-key="id" class="product-tree-scroll" style="height: calc(100vh - 190px); overflow-y: auto" > <el-tree ref="tree" v-loading="treeLoad" :data="list" @node-click="handleNodeClick" :expand-on-click-node="false" :default-expanded-keys="expandedKeys" :filter-node-method="filterNode" :props="{ children: 'children', label: 'label' }" highlight-current node-key="id" class="product-tree-scroll" style="height: calc(100vh - 190px); overflow-y: auto"> <template #default="{ node, data }"> <div class="custom-tree-node"> <span class="tree-node-content"> @@ -44,23 +37,22 @@ <span class="tree-node-label">{{ data.label }}</span> </span> <div> <el-button type="primary" link @click="openProDia('edit', data)" > <el-button type="primary" link @click="openProDia('edit', data)"> ç¼è¾ </el-button> <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3"> <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3"> æ·»å 产å </el-button> <el-button v-if="!node.childNodes.length" style="margin-left: 4px" type="danger" link @click="remove(node, data)" > <el-button v-if="!node.childNodes.length" style="margin-left: 4px" type="danger" link @click="remove(node, data)"> å é¤ </el-button> </div> @@ -70,117 +62,134 @@ </div> </div> <div class="right"> <div style="margin-bottom: 10px" v-if="isShowButton"> <el-button type="primary" @click="openModelDia('add')"> <div style="margin-bottom: 10px" v-if="isShowButton"> <el-button type="primary" @click="openModelDia('add')"> æ°å¢è§æ ¼åå· </el-button> <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" /> <el-button type="danger" @click="handleDelete" style="margin-left: 10px" plain > <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" /> <el-button type="danger" @click="handleDelete" style="margin-left: 10px" plain> å é¤ </el-button> </div> <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" ></PIMTable> <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination"></PIMTable> </div> <el-dialog v-model="productDia" title="产å" width="400px" @keydown.enter.prevent> <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef" > <el-dialog v-model="productDia" title="产å" width="400px" @keydown.enter.prevent> <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="产ååç§°ï¼" prop="productName"> <el-input v-model="form.productName" placeholder="请è¾å ¥äº§ååç§°" maxlength="20" show-word-limit clearable @keydown.enter.prevent /> <el-form-item label="产ååç§°ï¼" prop="productName"> <el-input v-model="form.productName" placeholder="请è¾å ¥äº§ååç§°" maxlength="20" show-word-limit clearable @keydown.enter.prevent /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确认</el-button> <el-button type="primary" @click="submitForm">确认</el-button> <el-button @click="closeProDia">åæ¶</el-button> </div> </template> </el-dialog> <el-dialog v-model="modelDia" title="è§æ ¼åå·" width="400px" @close="closeModelDia" @keydown.enter.prevent > <el-form :model="modelForm" label-width="140px" label-position="top" :rules="modelRules" ref="modelFormRef" > <el-dialog v-model="modelDia" title="è§æ ¼åå·" width="400px" @close="closeModelDia" @keydown.enter.prevent> <el-form :model="modelForm" label-width="140px" label-position="top" :rules="modelRules" ref="modelFormRef"> <el-row> <el-col :span="24"> <el-form-item label="è§æ ¼åå·ï¼" prop="model"> <el-input v-model="modelForm.model" placeholder="请è¾å ¥è§æ ¼åå·" clearable @keydown.enter.prevent /> <el-form-item label="è§æ ¼åå·ï¼" prop="model"> <el-input v-model="modelForm.model" placeholder="请è¾å ¥è§æ ¼åå·" clearable @keydown.enter.prevent /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="å度ï¼" prop="thickness"> <el-input v-model="modelForm.thickness" placeholder="请è¾å ¥å度" clearable @keydown.enter.prevent @blur="modelForm.thickness = formatThicknessTo15(modelForm.thickness)" /> <el-form-item label="å度ï¼" prop="thickness"> <el-input v-model="modelForm.thickness" placeholder="请è¾å ¥å度" clearable @keydown.enter.prevent @blur="modelForm.thickness = formatThicknessTo15(modelForm.thickness)" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="åä½ï¼" prop="unit"> <el-input v-model="modelForm.unit" placeholder="请è¾å ¥åä½" clearable @keydown.enter.prevent /> <el-form-item label="åä½ï¼" prop="unit"> <el-input v-model="modelForm.unit" placeholder="请è¾å ¥åä½" clearable @keydown.enter.prevent /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitModelForm">确认</el-button> <el-button type="primary" @click="submitModelForm">确认</el-button> <el-button @click="closeModelDia">åæ¶</el-button> </div> </template> </el-dialog> <!-- äºç»´ç å¯¹è¯æ¡ --> <el-dialog v-model="qrCodeDialog" title="产åäºç»´ç " width="400px"> <div class="qrcode-container"> <img v-if="qrCodeUrl" :src="qrCodeUrl" class="qrcode-image" /> <div v-else class="loading">çæä¸...</div> </div> <div style="text-align: center;"> {{ qrCodeName }} </div> <template #footer> <div class="dialog-footer"> <el-button @click="qrCodeDialog = false">å ³é</el-button> <el-button type="primary" @click="saveQrCodeAsImage" :disabled="!qrCodeUrl">ä¿å为å¾ç</el-button> </div> </template> </el-dialog> @@ -188,379 +197,465 @@ </template> <script setup> import { ref } from "vue"; import { ElMessageBox } from "element-plus"; import { addOrEditProduct, addOrEditProductModel, delProduct, delProductModel, modelListPage, productTreeList, } from "@/api/basicData/product.js"; import ImportExcel from "./ImportExcel/index.vue"; import { ref, getCurrentInstance, toRefs, reactive } from "vue"; import { ElMessageBox } from "element-plus"; import QRCode from "qrcode"; import { saveAs } from "file-saver"; import { addOrEditProduct, addOrEditProductModel, delProduct, delProductModel, modelListPage, productTreeList, } from "@/api/basicData/product.js"; import ImportExcel from "./ImportExcel/index.vue"; const { proxy } = getCurrentInstance(); const tree = ref(null); const containerRef = ref(null); const { proxy } = getCurrentInstance(); const tree = ref(null); const containerRef = ref(null); const productDia = ref(false); const modelDia = ref(false); const modelOperationType = ref(""); const search = ref(""); const currentId = ref(""); const currentParentId = ref(""); const operationType = ref(""); const treeLoad = ref(false); const list = ref([]); const expandedKeys = ref([]); const tableColumn = ref([ { label: "è§æ ¼åå·", prop: "model", }, { label: "å度", prop: "thickness", // å表å±ç¤ºæ¶ç»ä¸ä¿ç 15 ä½å°æ° formatData: (val) => formatThicknessTo15(val), }, { label: "åä½", prop: "unit", }, { dataType: "action", label: "æä½", align: "center", operation: [ { name: "ç¼è¾", type: "text", clickFun: (row) => { openModelDia("edit", row); const productDia = ref(false); const modelDia = ref(false); const qrCodeDialog = ref(false); const qrCodeUrl = ref(""); const currentProductId = ref(""); const modelOperationType = ref(""); const search = ref(""); const currentId = ref(""); const currentParentId = ref(""); const operationType = ref(""); const treeLoad = ref(false); const list = ref([]); const expandedKeys = ref([]); const tableColumn = ref([ { label: "è§æ ¼åå·", prop: "model", }, { label: "å度", prop: "thickness", // å表å±ç¤ºæ¶ç»ä¸ä¿ç 15 ä½å°æ° formatData: val => formatThicknessTo15(val), }, { label: "åä½", prop: "unit", }, { dataType: "action", label: "æä½", align: "center", operation: [ { name: "ç¼è¾", type: "text", clickFun: row => { openModelDia("edit", row); }, }, }, ], }, ]); const tableData = ref([]); const tableLoading = ref(false); const isShowButton = ref(false); const selectedRows = ref([]); const page = reactive({ current: 1, size: 10, total: 0, }); const data = reactive({ form: { productName: "", }, rules: { productName: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, { max: 20, message: "产ååç§°ä¸è½è¶ è¿20个å符", trigger: "blur" }, ], }, modelForm: { model: "", thickness: "", }, modelRules: { model: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], thickness: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], }, }); const { form, rules, modelForm, modelRules } = toRefs(data); // æååº¦æ ¼å¼åæåºå® 15 ä½å°æ°ï¼ç¨äºå±ç¤º/æäº¤ï¼ const formatThicknessTo15 = (val) => { if (val === null || val === undefined) return ""; const s = String(val).trim(); if (s === "") return ""; const n = Number(s); if (Number.isNaN(n)) return s; return n.toFixed(15); }; // æ¥è¯¢äº§åæ const getProductTreeList = () => { treeLoad.value = true; productTreeList() .then((res) => { list.value = res; list.value.forEach((a) => { expandedKeys.value.push(a.label); }); treeLoad.value = false; }) .catch((err) => { treeLoad.value = false; }); }; // è¿æ»¤äº§åæ const searchFilter = () => { proxy.$refs.tree.filter(search.value); }; // æå¼äº§åå¼¹æ¡ const openProDia = (type, data) => { operationType.value = type; productDia.value = true; form.value.productName = ""; if (type === "edit") { form.value.productName = data.productName; } }; // æå¼è§æ ¼åå·å¼¹æ¡ const openModelDia = (type, data) => { modelOperationType.value = type; modelDia.value = true; modelForm.value.model = ""; modelForm.value.id = ""; modelForm.value.thickness = ""; if (type === "edit") { modelForm.value = { ...data }; } }; // æäº¤äº§ååç§°ä¿®æ¹ const submitForm = () => { proxy.$refs.formRef.validate((valid) => { if (valid) { if (operationType.value === "add") { form.value.parentId = currentId.value; form.value.id = ""; } else if (operationType.value === "addOne") { form.value.id = ""; form.value.parentId = ""; } else { form.value.id = currentId.value; form.value.parentId = ""; } addOrEditProduct(form.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeProDia(); getProductTreeList(); }); } { name: "çæäºç»´ç ", type: "text", clickFun: row => { generateQrcode(row); }, }, ], }, ]); const tableData = ref([]); const tableLoading = ref(false); const isShowButton = ref(false); const selectedRows = ref([]); const page = reactive({ current: 1, size: 10, total: 0, }); }; // å ³é产åå¼¹æ¡ const closeProDia = () => { proxy.$refs.formRef.resetFields(); productDia.value = false; }; const data = reactive({ form: { productName: "", }, rules: { productName: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, { max: 20, message: "产ååç§°ä¸è½è¶ è¿20个å符", trigger: "blur" }, ], }, modelForm: { model: "", thickness: "", }, modelRules: { model: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], thickness: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], }, }); const { form, rules, modelForm, modelRules } = toRefs(data); // å é¤äº§å const remove = (node, data) => { let ids = []; ids.push(data.id); ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { tableLoading.value = true; delProduct(ids) .then((res) => { proxy.$modal.msgSuccess("å 餿å"); // æååº¦æ ¼å¼åæåºå® 15 ä½å°æ°ï¼ç¨äºå±ç¤º/æäº¤ï¼ const formatThicknessTo15 = val => { if (val === null || val === undefined) return ""; const s = String(val).trim(); if (s === "") return ""; const n = Number(s); if (Number.isNaN(n)) return s; return n.toFixed(15); }; // æ¥è¯¢äº§åæ const getProductTreeList = () => { treeLoad.value = true; productTreeList() .then(res => { list.value = res; list.value.forEach(a => { expandedKeys.value.push(a.label); }); treeLoad.value = false; }) .catch(err => { treeLoad.value = false; }); }; // è¿æ»¤äº§åæ const searchFilter = () => { proxy.$refs.tree.filter(search.value); }; // æå¼äº§åå¼¹æ¡ const openProDia = (type, data) => { operationType.value = type; productDia.value = true; form.value.productName = ""; if (type === "edit") { form.value.productName = data.productName; } }; // æå¼è§æ ¼åå·å¼¹æ¡ const openModelDia = (type, data) => { modelOperationType.value = type; modelDia.value = true; modelForm.value.model = ""; modelForm.value.id = ""; modelForm.value.thickness = ""; if (type === "edit") { modelForm.value = { ...data }; } }; // æäº¤äº§ååç§°ä¿®æ¹ const submitForm = () => { proxy.$refs.formRef.validate(valid => { if (valid) { if (operationType.value === "add") { form.value.parentId = currentId.value; form.value.id = ""; } else if (operationType.value === "addOne") { form.value.id = ""; form.value.parentId = ""; } else { form.value.id = currentId.value; form.value.parentId = ""; } addOrEditProduct(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeProDia(); getProductTreeList(); }) .finally(() => { tableLoading.value = false; }); }) .catch(() => { proxy.$modal.msg("已忶"); } }); }; // éæ©äº§å const handleNodeClick = (val, node, el) => { // 夿æ¯å¦ä¸ºå¶åèç¹ isShowButton.value = !(val.children && val.children.length > 0); // åªæå¶åèç¹ææ§è¡ä»¥ä¸é»è¾ currentId.value = val.id; currentParentId.value = val.parentId; getModelList(); }; }; // å ³é产åå¼¹æ¡ const closeProDia = () => { proxy.$refs.formRef.resetFields(); productDia.value = false; }; // æäº¤è§æ ¼åå·ä¿®æ¹ const submitModelForm = () => { proxy.$refs.modelFormRef.validate((valid) => { if (valid) { modelForm.value.productId = currentId.value; modelForm.value.thickness = formatThicknessTo15(modelForm.value.thickness); addOrEditProductModel(modelForm.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeModelDia(); getModelList(); // å é¤äº§å const remove = (node, data) => { let ids = []; ids.push(data.id); ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { tableLoading.value = true; delProduct(ids) .then(res => { proxy.$modal.msgSuccess("å 餿å"); getProductTreeList(); }) .finally(() => { tableLoading.value = false; }); }) .catch(() => { proxy.$modal.msg("已忶"); }); } }); }; // å ³éåå·å¼¹æ¡ const closeModelDia = () => { proxy.$refs.modelFormRef.resetFields(); modelDia.value = false; }; // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = (selection) => { selectedRows.value = selection; }; }; const fatherName = ref(""); // éæ©äº§å const handleNodeClick = (val, node, el) => { // 夿æ¯å¦ä¸ºå¶åèç¹ isShowButton.value = !(val.children && val.children.length > 0); // åªæå¶åèç¹ææ§è¡ä»¥ä¸é»è¾ currentId.value = val.id; currentParentId.value = val.parentId; fatherName.value = val.label; getModelList(); }; // æ¥è¯¢è§æ ¼åå· const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; getModelList(); }; const getModelList = () => { tableLoading.value = true; modelListPage({ id: currentId.value, current: page.current, size: page.size, }).then((res) => { console.log("res", res); tableData.value = res.records; page.total = res.total; tableLoading.value = false; }); }; // å é¤è§æ ¼åå· const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map((item) => item.id); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { tableLoading.value = true; delProductModel(ids) .then((res) => { proxy.$modal.msgSuccess("å 餿å"); // æäº¤è§æ ¼åå·ä¿®æ¹ const submitModelForm = () => { proxy.$refs.modelFormRef.validate(valid => { if (valid) { modelForm.value.productId = currentId.value; modelForm.value.thickness = formatThicknessTo15( modelForm.value.thickness ); addOrEditProductModel(modelForm.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeModelDia(); getModelList(); }) .finally(() => { tableLoading.value = false; }); }) .catch(() => { proxy.$modal.msg("已忶"); } }); }; // è°ç¨treeè¿æ»¤æ¹æ³ 䏿è±è¿æ»¤ const filterNode = (value, data, node) => { if (!value) { //å¦ææ°æ®ä¸ºç©ºï¼åè¿åtrue,æ¾ç¤ºææçæ°æ®é¡¹ return true; } // å ³é®åä¸èç¹ label åæå°åæ¯è¾ï¼è±ææ¨¡ç³å¹é ä¸åºå大å°åï¼å¦ LOW / low å¯å¹é Low-Eï¼ const val = String(value).trim().toLowerCase(); if (!val) return true; return chooseNode(val, data, node); // è°ç¨è¿æ»¤äºå±æ¹æ³ }; // è¿æ»¤ç¶èç¹ / åèç¹ (妿è¾å ¥çåæ°æ¯ç¶èç¹ä¸è½å¹é ï¼åè¿å该èç¹ä»¥åå ¶ä¸çææåèç¹ï¼å¦æåæ°æ¯åèç¹ï¼åè¿å该èç¹çç¶èç¹ãnameæ¯ä¸æå符ï¼enNameæ¯è±æå符. const chooseNode = (value, data, node) => { const labelLower = String(data.label ?? "").toLowerCase(); if (labelLower.indexOf(value) !== -1) { return true; } const level = node.level; // å¦æä¼ å ¥çèç¹æ¬èº«å°±æ¯ä¸çº§èç¹å°±ä¸ç¨æ ¡éªäº if (level === 1) { return false; } // å åå½åèç¹çç¶èç¹ let parentData = node.parent; // éåå½åèç¹çç¶èç¹ let index = 0; while (index < level - 1) { // 妿å¹é å°ç´æ¥è¿åï¼ä¸ filterNode ä¸è´ï¼æ ç¾ä¸å ³é®ååæå°åæ¯è¾ï¼è±ææ¨¡ç³å¹é ä¸åºå大å°å const parentLabelLower = String(parentData.data.label ?? "").toLowerCase(); if (parentLabelLower.indexOf(value) !== -1) { }; // å ³éåå·å¼¹æ¡ const closeModelDia = () => { proxy.$refs.modelFormRef.resetFields(); modelDia.value = false; }; // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = selection => { selectedRows.value = selection; }; // æ¥è¯¢è§æ ¼åå· const pagination = obj => { page.current = obj.page; page.size = obj.limit; getModelList(); }; const getModelList = () => { tableLoading.value = true; modelListPage({ id: currentId.value, current: page.current, size: page.size, }).then(res => { console.log("res", res); tableData.value = res.records; page.total = res.total; tableLoading.value = false; }); }; // å é¤è§æ ¼åå· const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map(item => item.id); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { tableLoading.value = true; delProductModel(ids) .then(res => { proxy.$modal.msgSuccess("å 餿å"); getModelList(); }) .finally(() => { tableLoading.value = false; }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // è°ç¨treeè¿æ»¤æ¹æ³ 䏿è±è¿æ»¤ const filterNode = (value, data, node) => { if (!value) { //å¦ææ°æ®ä¸ºç©ºï¼åè¿åtrue,æ¾ç¤ºææçæ°æ®é¡¹ return true; } // å¦åçè¯åå¾ä¸ä¸å±åå¹é parentData = parentData.parent; index++; } // 没å¹é å°è¿åfalse return false; }; getProductTreeList(); // å ³é®åä¸èç¹ label åæå°åæ¯è¾ï¼è±ææ¨¡ç³å¹é ä¸åºå大å°åï¼å¦ LOW / low å¯å¹é Low-Eï¼ const val = String(value).trim().toLowerCase(); if (!val) return true; return chooseNode(val, data, node); // è°ç¨è¿æ»¤äºå±æ¹æ³ }; // è¿æ»¤ç¶èç¹ / åèç¹ (妿è¾å ¥çåæ°æ¯ç¶èç¹ä¸è½å¹é ï¼åè¿å该èç¹ä»¥åå ¶ä¸çææåèç¹ï¼å¦æåæ°æ¯åèç¹ï¼åè¿å该èç¹çç¶èç¹ãnameæ¯ä¸æå符ï¼enNameæ¯è±æå符. const chooseNode = (value, data, node) => { const labelLower = String(data.label ?? "").toLowerCase(); if (labelLower.indexOf(value) !== -1) { return true; } const level = node.level; // å¦æä¼ å ¥çèç¹æ¬èº«å°±æ¯ä¸çº§èç¹å°±ä¸ç¨æ ¡éªäº if (level === 1) { return false; } // å åå½åèç¹çç¶èç¹ let parentData = node.parent; // éåå½åèç¹çç¶èç¹ let index = 0; while (index < level - 1) { // 妿å¹é å°ç´æ¥è¿åï¼ä¸ filterNode ä¸è´ï¼æ ç¾ä¸å ³é®ååæå°åæ¯è¾ï¼è±ææ¨¡ç³å¹é ä¸åºå大å°å const parentLabelLower = String(parentData.data.label ?? "").toLowerCase(); if (parentLabelLower.indexOf(value) !== -1) { return true; } // å¦åçè¯åå¾ä¸ä¸å±åå¹é parentData = parentData.parent; index++; } // 没å¹é å°è¿åfalse return false; }; const qrCodeName = ref(""); // çæäºç»´ç const generateQrcode = async row => { try { currentProductId.value = row.id; qrCodeName.value = fatherName.value + "-" + row.model; // 使ç¨row.idçæäºç»´ç const qrCodeData = row.id.toString(); // çæäºç»´ç URL qrCodeUrl.value = await QRCode.toDataURL(qrCodeData, { width: 300, margin: 1, }); // æå¼äºç»´ç å¯¹è¯æ¡ qrCodeDialog.value = true; } catch (error) { console.error("çæäºç»´ç 失败:", error); proxy.$modal.msgError("çæäºç»´ç 失败"); } }; // ä¿åäºç»´ç 为å¾ç const saveQrCodeAsImage = () => { if (!qrCodeUrl.value) return; try { // ä»Data URLå建Blob const blob = dataURLToBlob(qrCodeUrl.value); // 使ç¨file-saverä¿åå¾ç saveAs(blob, `${qrCodeName.value}.png`); proxy.$modal.msgSuccess("ä¿åæå"); } catch (error) { console.error("ä¿åå¾ç失败:", error); proxy.$modal.msgError("ä¿åå¾ç失败"); } }; // å°Data URL转æ¢ä¸ºBlob const dataURLToBlob = dataURL => { const arr = dataURL.split(","); const mime = arr[0].match(/:(.*?);/)[1]; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], { type: mime }); }; getProductTreeList(); </script> <style scoped> .product-view { display: flex; } .left { width: 450px; min-width: 450px; padding: 16px; background: #ffffff; } .right { flex: 1; min-width: 0; padding: 16px; margin-left: 20px; background: #ffffff; } .custom-tree-node { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px; } .tree-node-content { flex: 1; min-width: 0; display: flex; align-items: center; height: 100%; overflow: hidden; } .tree-node-content .orange-icon { flex-shrink: 0; } .tree-node-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .orange-icon { color: orange; font-size: 18px; margin-right: 8px; /* 徿 䏿åä¹é´å ç¹é´è· */ } .product-tree-scroll { scrollbar-width: thin; scrollbar-color: #c0c4cc #f5f7fa; } .product-tree-scroll::-webkit-scrollbar { width: 8px; } .product-tree-scroll::-webkit-scrollbar-track { background: #f5f7fa; border-radius: 4px; } .product-tree-scroll::-webkit-scrollbar-thumb { background: #c0c4cc; border-radius: 4px; } .product-tree-scroll::-webkit-scrollbar-thumb:hover { background: #909399; } .product-view { display: flex; } .left { width: 450px; min-width: 450px; padding: 16px; background: #ffffff; } .right { flex: 1; min-width: 0; padding: 16px; margin-left: 20px; background: #ffffff; } .custom-tree-node { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px; } .tree-node-content { flex: 1; min-width: 0; display: flex; align-items: center; height: 100%; overflow: hidden; } .tree-node-content .orange-icon { flex-shrink: 0; } .tree-node-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .orange-icon { color: orange; font-size: 18px; margin-right: 8px; /* 徿 䏿åä¹é´å ç¹é´è· */ } .product-tree-scroll { scrollbar-width: thin; scrollbar-color: #c0c4cc #f5f7fa; } .product-tree-scroll::-webkit-scrollbar { width: 8px; } .product-tree-scroll::-webkit-scrollbar-track { background: #f5f7fa; border-radius: 4px; } .product-tree-scroll::-webkit-scrollbar-thumb { background: #c0c4cc; border-radius: 4px; } .product-tree-scroll::-webkit-scrollbar-thumb:hover { background: #909399; } /* äºç»´ç æ ·å¼ */ .qrcode-container { display: flex; justify-content: center; align-items: center; padding: 20px; min-height: 300px; } .qrcode-image { max-width: 100%; height: auto; } .loading { font-size: 16px; color: #606266; } </style> src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -1,118 +1,135 @@ <template> <div> <el-dialog v-model="dialogFormVisible" :title="operationType === 'approval' ? '审æ¹' : '详æ '" width="700px" @close="closeDia" > <el-form :model="form" label-width="140px" label-position="top" ref="formRef"> <el-row> <el-col :span="24"> <el-form-item label="æµç¨ç¼å·ï¼" prop="approveId"> <el-input v-model="form.approveId" placeholder="èªå¨ç¼å·" clearable disabled/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="ç³è¯·é¨é¨ï¼"> <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-form-item> </el-col> </el-row> <el-row v-if="!isQuotationApproval && !isPurchaseApproval"> <el-col :span="24"> <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> <el-input v-model="form.approveReason" placeholder="请è¾å ¥" clearable type="textarea" disabled/> </el-form-item> </el-col> </el-row> <!-- 审æ¹äººéæ©ï¼å¨æèç¹ï¼ --> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="ç³è¯·äººï¼" prop="approveUser"> <el-select v-model="form.approveUser" placeholder="éæ©äººå" disabled > <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ç³è¯·æ¥æï¼" prop="approveTime"> <el-date-picker v-model="form.approveTime" type="date" placeholder="è¯·éæ©æ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" disabled /> </el-form-item> </el-col> </el-row> </el-form> <el-dialog v-model="dialogFormVisible" :title="operationType === 'approval' ? '审æ¹' : '详æ '" width="700px" @close="closeDia"> <el-form :model="form" label-width="140px" label-position="top" ref="formRef"> <el-row> <el-col :span="24"> <el-form-item label="æµç¨ç¼å·ï¼" prop="approveId"> <el-input v-model="form.approveId" placeholder="èªå¨ç¼å·" clearable disabled /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="ç³è¯·é¨é¨ï¼"> <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-form-item> </el-col> </el-row> <el-row v-if="!isQuotationApproval && !isPurchaseApproval"> <el-col :span="24"> <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> <el-input v-model="form.approveReason" placeholder="请è¾å ¥" clearable type="textarea" disabled /> </el-form-item> </el-col> </el-row> <!-- 审æ¹äººéæ©ï¼å¨æèç¹ï¼ --> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="ç³è¯·äººï¼" prop="approveUser"> <el-input v-model="form.approveUserName" clearable disabled /> <!-- <el-select v-model="form.approveUser" placeholder="éæ©äººå" disabled> <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> --> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ç³è¯·æ¥æï¼" prop="approveTime"> <el-date-picker v-model="form.approveTime" type="date" placeholder="è¯·éæ©æ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" disabled /> </el-form-item> </el-col> </el-row> </el-form> <!-- æ¥ä»·å®¡æ¹ï¼å±ç¤ºæ¥ä»·è¯¦æ ï¼å¤ç¨é宿¥ä»·"æ¥ç详æ å¯¹è¯æ¡"å å®¹ç»æï¼ --> <div v-if="isQuotationApproval" style="margin: 10px 0 18px;"> <div v-if="isQuotationApproval" style="margin: 10px 0 18px;"> <el-divider content-position="left">æ¥ä»·è¯¦æ </el-divider> <el-skeleton :loading="quotationLoading" animated> <el-skeleton :loading="quotationLoading" animated> <template #template> <el-skeleton-item variant="h3" style="width: 30%" /> <el-skeleton-item variant="text" style="width: 100%" /> <el-skeleton-item variant="text" style="width: 100%" /> <el-skeleton-item variant="h3" style="width: 30%" /> <el-skeleton-item variant="text" style="width: 100%" /> <el-skeleton-item variant="text" style="width: 100%" /> </template> <template #default> <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ " /> <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ " /> <template v-else> <el-descriptions :column="2" border> <el-descriptions :column="2" border> <el-descriptions-item label="æ¥ä»·åå·">{{ currentQuotation.quotationNo }}</el-descriptions-item> <el-descriptions-item label="客æ·åç§°">{{ currentQuotation.customer }}</el-descriptions-item> <el-descriptions-item label="ä¸å¡å">{{ currentQuotation.salesperson }}</el-descriptions-item> <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> <el-descriptions-item label="æææè³">{{ currentQuotation.validDate }}</el-descriptions-item> <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentQuotation.paymentMethod }}</el-descriptions-item> <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> Â¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} </span> </el-descriptions-item> </el-descriptions> <div style="margin-top: 20px;"> <h4>产åæç»</h4> <el-table :data="currentQuotation.products || []" border style="width: 100%"> <el-table-column prop="product" label="产ååç§°" /> <el-table-column prop="specification" label="è§æ ¼åå·" /> <el-table-column prop="unit" label="åä½" /> <el-table-column prop="unitPrice" label="åä»·"> <el-table :data="currentQuotation.products || []" border style="width: 100%"> <el-table-column prop="product" label="产ååç§°" /> <el-table-column prop="specification" label="è§æ ¼åå·" /> <el-table-column prop="unit" label="åä½" /> <el-table-column prop="unitPrice" label="åä»·"> <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template> </el-table-column> </el-table> </div> <div v-if="currentQuotation.remark" style="margin-top: 20px;"> <div v-if="currentQuotation.remark" style="margin-top: 20px;"> <h4>夿³¨</h4> <p>{{ currentQuotation.remark }}</p> </div> @@ -120,20 +137,26 @@ </template> </el-skeleton> </div> <!-- éè´å®¡æ¹ï¼å±ç¤ºéè´è¯¦æ --> <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;"> <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;"> <el-divider content-position="left">éè´è¯¦æ </el-divider> <el-skeleton :loading="purchaseLoading" animated> <el-skeleton :loading="purchaseLoading" animated> <template #template> <el-skeleton-item variant="h3" style="width: 30%" /> <el-skeleton-item variant="text" style="width: 100%" /> <el-skeleton-item variant="text" style="width: 100%" /> <el-skeleton-item variant="h3" style="width: 30%" /> <el-skeleton-item variant="text" style="width: 100%" /> <el-skeleton-item variant="text" style="width: 100%" /> </template> <template #default> <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" description="æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ " /> <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" description="æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ " /> <template v-else> <el-descriptions :column="2" border> <el-descriptions :column="2" border> <el-descriptions-item label="éè´ååå·">{{ currentPurchase.purchaseContractNumber }}</el-descriptions-item> <el-descriptions-item label="ä¾åºååç§°">{{ currentPurchase.supplierName }}</el-descriptions-item> <el-descriptions-item label="项ç®åç§°">{{ currentPurchase.projectName }}</el-descriptions-item> @@ -141,24 +164,32 @@ <el-descriptions-item label="ç¾è®¢æ¥æ">{{ currentPurchase.executionDate }}</el-descriptions-item> <el-descriptions-item label="å½å ¥æ¥æ">{{ currentPurchase.entryDate }}</el-descriptions-item> <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentPurchase.paymentMethod }}</el-descriptions-item> <el-descriptions-item label="ååéé¢" :span="2"> <el-descriptions-item label="ååéé¢" :span="2"> <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> Â¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} </span> </el-descriptions-item> </el-descriptions> <div style="margin-top: 20px;"> <h4>产åæç»</h4> <el-table :data="currentPurchase.productData || []" border style="width: 100%"> <el-table-column prop="productCategory" label="产ååç§°" /> <el-table-column prop="specificationModel" label="è§æ ¼åå·" /> <el-table-column prop="unit" label="åä½" /> <el-table-column prop="quantity" label="æ°é" /> <el-table-column prop="taxInclusiveUnitPrice" label="å«ç¨åä»·"> <el-table :data="currentPurchase.productData || []" border style="width: 100%"> <el-table-column prop="productCategory" label="产ååç§°" /> <el-table-column prop="specificationModel" label="è§æ ¼åå·" /> <el-table-column prop="unit" label="åä½" /> <el-table-column prop="quantity" label="æ°é" /> <el-table-column prop="taxInclusiveUnitPrice" label="å«ç¨åä»·"> <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> </el-table-column> <el-table-column prop="taxInclusiveTotalPrice" label="å«ç¨æ»ä»·"> <el-table-column prop="taxInclusiveTotalPrice" label="å«ç¨æ»ä»·"> <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> </el-table-column> </el-table> @@ -167,52 +198,78 @@ </template> </el-skeleton> </div> <el-form :model="{ activities }" ref="formRef" label-position="top"> <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> <el-step v-for="(activity, index) in activities" :key="index" finish-status="success" :title="getNodeTitle(index, activities.length)" :description="activity.approveNodeUser" :icon="getNodeIcon(activity, index)" > <template #icon> <el-icon v-if="activity.approveNodeStatus === 2" color="red" :size="22"><WarningFilled /></el-icon> <el-icon v-else-if="activity.isShen" color="#1890ff" :size="22"><Edit /></el-icon> <el-icon v-else-if="activity.approveNodeStatus === 1" color="#67C23A" :size="26"><Check /></el-icon> <el-icon v-else color="#C0C4CC" :size="22"><MoreFilled /></el-icon> </template> <el-form :model="{ activities }" ref="formRef" label-position="top"> <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> <el-step v-for="(activity, index) in activities" :key="index" finish-status="success" :title="getNodeTitle(index, activities.length)" :description="activity.approveNodeUser" :icon="getNodeIcon(activity, index)"> <template #icon> <el-icon v-if="activity.approveNodeStatus === 2" color="red" :size="22"> <WarningFilled /> </el-icon> <el-icon v-else-if="activity.isShen" color="#1890ff" :size="22"> <Edit /> </el-icon> <el-icon v-else-if="activity.approveNodeStatus === 1" color="#67C23A" :size="26"> <Check /> </el-icon> <el-icon v-else color="#C0C4CC" :size="22"> <MoreFilled /> </el-icon> </template> <template #title> <span style="color: #000000">{{ getNodeTitle(index, activities.length) }}</span> </template> <template #description> <div class="node-user"> <div class="avatar-wrapper"> <img :src="userStore.avatar" class="user-avatar" alt=""/> <img :src="userStore.avatar" class="user-avatar" alt="" /> </div> <span style="color: #000000">{{ activity.approveNodeUser }}-{{activity.isApproval}}</span> </div> <div v-if="!activity.isShen" class="node-reason"> <div v-if="!activity.isShen" class="node-reason"> <span>å®¡æ¹æè§ï¼</span>{{ activity.approveNodeReason }} </div> <div v-else-if="activity.isShen"> <el-form-item :prop="'activities.' + index + '.approveNodeReason'" :rules="[{ required: true, message: 'å®¡æ¹æè§ä¸è½ä¸ºç©º', trigger: 'blur' }]" > <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input> <el-form-item :prop="'activities.' + index + '.approveNodeReason'" :rules="[{ required: true, message: 'å®¡æ¹æè§ä¸è½ä¸ºç©º', trigger: 'blur' }]"> <el-input v-model="activity.approveNodeReason" clearable type="textarea" :disabled="operationType === 'view'"></el-input> </el-form-item> </div> </template> </el-step> </el-steps> </el-form> <template #footer v-if="operationType === 'approval'"> <template #footer v-if="operationType === 'approval'"> <div class="dialog-footer"> <el-button type="primary" @click="submitForm(2)">ä¸éè¿</el-button> <el-button type="primary" @click="submitForm(1)">éè¿</el-button> <el-button type="primary" @click="submitForm(2)">ä¸éè¿</el-button> <el-button type="primary" @click="submitForm(1)">éè¿</el-button> <el-button @click="closeDia">åæ¶</el-button> </div> </template> @@ -221,228 +278,248 @@ </template> <script setup> import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue"; import { approveProcessDetails, getDept, updateApproveNode } from "@/api/collaborativeApproval/approvalProcess.js"; import useUserStore from "@/store/modules/user.js"; import {userListNoPageByTenantId} from "@/api/system/user.js"; import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; const emit = defineEmits(['close']) const { proxy } = getCurrentInstance() import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs, } from "vue"; import { approveProcessDetails, getDept, updateApproveNode, } from "@/api/collaborativeApproval/approvalProcess.js"; import useUserStore from "@/store/modules/user.js"; import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js"; import { WarningFilled, Edit, Check, MoreFilled, } from "@element-plus/icons-vue"; import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; const emit = defineEmits(["close"]); const { proxy } = getCurrentInstance(); const props = defineProps({ approveType: { type: [Number, String], default: 0 } }) const dialogFormVisible = ref(false); const operationType = ref('') const activities = ref([]) const formRef = ref(null); const userStore = useUserStore() const productOptions = ref([]); const userList = ref([]) const quotationLoading = ref(false) const currentQuotation = ref({}) const purchaseLoading = ref(false) const currentPurchase = ref({}) const isQuotationApproval = computed(() => Number(props.approveType) === 6) const isPurchaseApproval = computed(() => Number(props.approveType) === 5) const data = reactive({ form: { approveTime: "", approveId: "", approveUser: "", approveDeptId: "", approveReason: "", checkResult: "", }, }); const { form } = toRefs(data); // èç¹æ é¢ const getNodeTitle = (index, len) => { if (index === len - 1) return 'ç»æ'; return '审æ¹'; }; // è·åå½åæ¿æ´»æ¥éª¤ const getActiveStep = () => { // å¦æææ isShen é½ä¸º falseï¼è¿åæåä¸ä¸ªæ¥éª¤ï¼å ¨é¨å®æï¼ const hasActive = activities.value.some(a => a.isShen === true); if (!hasActive) return activities.value.length; // å½åèç¹ç´¢å¼ return activities.value.findIndex(a => a.isShen == true); }; // æ¥éª¤icon const getNodeIcon = (activity, index) => { if (activity.approveNodeStatus === 2) return 'el-icon-warning'; // ä¸éè¿ if (activity.isShen) return 'Edit'; return ''; }; // æå¼å¼¹æ¡ const openDialog = (type, row) => { operationType.value = type; dialogFormVisible.value = true; currentQuotation.value = {} currentPurchase.value = {} userListNoPageByTenantId().then((res) => { userList.value = res.data; }); form.value = {...row} // ç«å³æ¸ é¤è¡¨åéªè¯ç¶æï¼å ä¸ºåæ®µæ¯disabledçï¼ä¸éè¦éªè¯ï¼ nextTick(() => { if (formRef.value) { formRef.value.clearValidate(); } }); // ç¡®ä¿é项å è½½å®æååå¹é å¼ç±»å getProductOptions().then(() => { // ç¡®ä¿å¼ç±»åå¹é ï¼å¦æé项已å è½½ï¼ if (productOptions.value.length > 0 && form.value.approveDeptId) { const matchedOption = productOptions.value.find(opt => opt.deptId == form.value.approveDeptId || String(opt.deptId) === String(form.value.approveDeptId) ); if (matchedOption) { form.value.approveDeptId = matchedOption.deptId; } } // 忬¡æ¸ é¤éªè¯ï¼ç¡®ä¿é项å è½½åå¼å¹é æ£ç¡® nextTick(() => { if (formRef.value) { formRef.value.clearValidate(); } }); }); // æ¥ä»·å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½ç"æ¥ä»·åå·"廿¥æ¥ä»·å表 if (isQuotationApproval.value) { const quotationNo = row?.approveReason; if (quotationNo) { quotationLoading.value = true getQuotationList({ quotationNo }).then((res) => { const records = res?.data?.records || [] currentQuotation.value = records[0] || {} }).finally(() => { quotationLoading.value = false }) } } // éè´å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½ç"éè´ååå·"廿¥éè´è¯¦æ if (isPurchaseApproval.value) { const purchaseContractNumber = row?.approveReason; if (purchaseContractNumber) { purchaseLoading.value = true getPurchaseByCode({ purchaseContractNumber }).then((res) => { currentPurchase.value = res }).catch((err) => { console.error('æ¥è¯¢éè´è¯¦æ 失败:', err) proxy.$modal.msgError('æ¥è¯¢éè´è¯¦æ 失败') }).finally(() => { purchaseLoading.value = false }) } } approveProcessDetails(row.approveId).then((res) => { activities.value = res.data // å¢å isApprovalåæ®µ activities.value.forEach(item => { if (item.url && item.url.includes('word')) { item.urlTem = item.url.replaceAll('word', 'img') } else { item.urlTem = item.url } if (item.approveNodeStatus === 2) { item.isApproval = '已驳å'; } else if (item.approveNodeStatus === 1) { item.isApproval = 'å·²åæ'; } else { item.isApproval = 'æªå®¡æ¹'; } }) }) } const getProductOptions = () => { return getDept().then((res) => { productOptions.value = res.data; }); }; // æäº¤å®¡æ¹ const submitForm = (status) => { const filteredActivities = activities.value.filter(activity => activity.isShen); if (!filteredActivities || filteredActivities.length === 0) { proxy.$modal.msgError("æªæ¾å°å¾ 审æ¹çèç¹"); return; } const currentActivity = filteredActivities[0]; if (!currentActivity) { proxy.$modal.msgError("æªæ¾å°å¾ 审æ¹çèç¹"); return; } currentActivity.approveNodeStatus = status; // 夿æ¯å¦ä¸ºæå䏿¥ const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; updateApproveNode({ ...currentActivity, isLast }).then(() => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); const props = defineProps({ approveType: { type: [Number, String], default: 0, }, }); }; // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; quotationLoading.value = false currentQuotation.value = {} purchaseLoading.value = false currentPurchase.value = {} emit('close') }; defineExpose({ openDialog, }); const dialogFormVisible = ref(false); const operationType = ref(""); const activities = ref([]); const formRef = ref(null); const userStore = useUserStore(); const productOptions = ref([]); const userList = ref([]); const quotationLoading = ref(false); const currentQuotation = ref({}); const purchaseLoading = ref(false); const currentPurchase = ref({}); const isQuotationApproval = computed(() => Number(props.approveType) === 6); const isPurchaseApproval = computed(() => Number(props.approveType) === 5); const data = reactive({ form: { approveTime: "", approveId: "", approveUser: "", approveDeptId: "", approveReason: "", checkResult: "", }, }); const { form } = toRefs(data); // èç¹æ é¢ const getNodeTitle = (index, len) => { if (index === len - 1) return "ç»æ"; return "审æ¹"; }; // è·åå½åæ¿æ´»æ¥éª¤ const getActiveStep = () => { // å¦æææ isShen é½ä¸º falseï¼è¿åæåä¸ä¸ªæ¥éª¤ï¼å ¨é¨å®æï¼ const hasActive = activities.value.some(a => a.isShen === true); if (!hasActive) return activities.value.length; // å½åèç¹ç´¢å¼ return activities.value.findIndex(a => a.isShen == true); }; // æ¥éª¤icon const getNodeIcon = (activity, index) => { if (activity.approveNodeStatus === 2) return "el-icon-warning"; // ä¸éè¿ if (activity.isShen) return "Edit"; return ""; }; // æå¼å¼¹æ¡ const openDialog = (type, row) => { operationType.value = type; dialogFormVisible.value = true; currentQuotation.value = {}; currentPurchase.value = {}; approveUserList({ approveType: props.approveType }).then(res => { userList.value = res.data; }); form.value = { ...row }; // ç«å³æ¸ é¤è¡¨åéªè¯ç¶æï¼å ä¸ºåæ®µæ¯disabledçï¼ä¸éè¦éªè¯ï¼ nextTick(() => { if (formRef.value) { formRef.value.clearValidate(); } }); // ç¡®ä¿é项å è½½å®æååå¹é å¼ç±»å getProductOptions().then(() => { // ç¡®ä¿å¼ç±»åå¹é ï¼å¦æé项已å è½½ï¼ if (productOptions.value.length > 0 && form.value.approveDeptId) { const matchedOption = productOptions.value.find( opt => opt.deptId == form.value.approveDeptId || String(opt.deptId) === String(form.value.approveDeptId) ); if (matchedOption) { form.value.approveDeptId = matchedOption.deptId; } } // 忬¡æ¸ é¤éªè¯ï¼ç¡®ä¿é项å è½½åå¼å¹é æ£ç¡® nextTick(() => { if (formRef.value) { formRef.value.clearValidate(); } }); }); // æ¥ä»·å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½ç"æ¥ä»·åå·"廿¥æ¥ä»·å表 if (isQuotationApproval.value) { const quotationNo = row?.approveReason; if (quotationNo) { quotationLoading.value = true; getQuotationList({ quotationNo }) .then(res => { const records = res?.data?.records || []; currentQuotation.value = records[0] || {}; }) .finally(() => { quotationLoading.value = false; }); } } // éè´å®¡æ¹ï¼ç¨å®¡æ¹äºç±å段æ¿è½½ç"éè´ååå·"廿¥éè´è¯¦æ if (isPurchaseApproval.value) { const purchaseContractNumber = row?.approveReason; if (purchaseContractNumber) { purchaseLoading.value = true; getPurchaseByCode({ purchaseContractNumber }) .then(res => { currentPurchase.value = res; }) .catch(err => { console.error("æ¥è¯¢éè´è¯¦æ 失败:", err); proxy.$modal.msgError("æ¥è¯¢éè´è¯¦æ 失败"); }) .finally(() => { purchaseLoading.value = false; }); } } approveProcessDetails(row.approveId).then(res => { activities.value = res.data; // å¢å isApprovalåæ®µ activities.value.forEach(item => { if (item.url && item.url.includes("word")) { item.urlTem = item.url.replaceAll("word", "img"); } else { item.urlTem = item.url; } if (item.approveNodeStatus === 2) { item.isApproval = "已驳å"; } else if (item.approveNodeStatus === 1) { item.isApproval = "å·²åæ"; } else { item.isApproval = "æªå®¡æ¹"; } }); }); }; const getProductOptions = () => { return getDept().then(res => { productOptions.value = res.data; }); }; // æäº¤å®¡æ¹ const submitForm = status => { const filteredActivities = activities.value.filter( activity => activity.isShen ); if (!filteredActivities || filteredActivities.length === 0) { proxy.$modal.msgError("æªæ¾å°å¾ 审æ¹çèç¹"); return; } const currentActivity = filteredActivities[0]; if (!currentActivity) { proxy.$modal.msgError("æªæ¾å°å¾ 审æ¹çèç¹"); return; } currentActivity.approveNodeStatus = status; // 夿æ¯å¦ä¸ºæå䏿¥ const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length - 1; updateApproveNode({ ...currentActivity, isLast }).then(() => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); }); }; // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; quotationLoading.value = false; currentQuotation.value = {}; purchaseLoading.value = false; currentPurchase.value = {}; emit("close"); }; defineExpose({ openDialog, }); </script> <style scoped> .node-user { margin: 10px 0; font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; } .node-status { color: #1890ff; margin-left: 8px; font-size: 14px; } .node-reason { font-size: 15px; color: #333; margin: 10px 0; } .user-avatar { cursor: pointer; width: 30px; height: 30px; border-radius: 50px; } .signImg { cursor: pointer; width: 200px; height: 60px; } .node-user { margin: 10px 0; font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; } .node-status { color: #1890ff; margin-left: 8px; font-size: 14px; } .node-reason { font-size: 15px; color: #333; margin: 10px 0; } .user-avatar { cursor: pointer; width: 30px; height: 30px; border-radius: 50px; } .signImg { cursor: pointer; width: 200px; height: 60px; } </style> src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -1,99 +1,103 @@ <template> <div> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢å®¡æ¹æµç¨' : 'ç¼è¾å®¡æ¹æµç¨'" width="50%" @close="closeDia" > <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢å®¡æ¹æµç¨' : 'ç¼è¾å®¡æ¹æµç¨'" width="50%" @close="closeDia"> <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> <el-row> <el-col :span="24"> <el-form-item label="æµç¨ç¼å·ï¼" prop="approveId"> <el-input v-model="form.approveId" placeholder="èªå¨ç¼å·" clearable disabled/> <el-form-item label="æµç¨ç¼å·ï¼" prop="approveId"> <el-input v-model="form.approveId" placeholder="èªå¨ç¼å·" clearable disabled /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="ç³è¯·é¨é¨ï¼" prop="approveDeptName"> <!-- <el-input v-model="form.approveDeptName" placeholder="请è¾å ¥" clearable/>--> <el-select v-model="form.approveDeptId" placeholder="éæ©é¨é¨" @change="handleDeptChange" > <el-option v-for="user in productOptions" :key="user.deptId" :label="user.deptName" :value="user.deptId" /> </el-select> <el-form-item label="ç³è¯·é¨é¨ï¼" prop="approveDeptName"> <!-- <el-input v-model="form.approveDeptName" placeholder="请è¾å ¥" clearable/>--> <el-select v-model="form.approveDeptId" placeholder="éæ©é¨é¨" @change="handleDeptChange"> <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> <el-row> <el-col :span="24"> <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> <el-input v-model="form.approveReason" placeholder="请è¾å ¥" clearable type="textarea" /> <el-form-item :label="props.approveType == 5 ? 'éè´ååå·ï¼' : '审æ¹äºç±ï¼'" prop="approveReason"> <el-input v-model="form.approveReason" placeholder="请è¾å ¥" clearable type="textarea" /> </el-form-item> </el-col> </el-row> <!-- è¯·åæ¶é´ï¼ä» å½ approveType 为 2 æ¶æ¾ç¤ºï¼ --> <el-row :gutter="30" v-if="props.approveType == 2"> <el-row :gutter="30" v-if="props.approveType == 2"> <el-col :span="12"> <el-form-item label="请åå¼å§æ¶é´ï¼" prop="startDate"> <el-date-picker v-model="form.startDate" type="date" placeholder="è¯·éæ©å¼å§æ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" /> <el-form-item label="请åå¼å§æ¶é´ï¼" prop="startDate"> <el-date-picker v-model="form.startDate" type="date" placeholder="è¯·éæ©å¼å§æ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="请åç»ææ¶é´ï¼" prop="endDate"> <el-date-picker v-model="form.endDate" type="date" placeholder="è¯·éæ©ç»ææ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" /> <el-form-item label="请åç»ææ¶é´ï¼" prop="endDate"> <el-date-picker v-model="form.endDate" type="date" placeholder="è¯·éæ©ç»ææ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" /> </el-form-item> </el-col> </el-row> <!-- æ¥ééé¢ï¼ä» å½ approveType 为 4 æ¶æ¾ç¤ºï¼ --> <el-row v-if="props.approveType == 4"> <el-col :span="24"> <el-form-item label="æ¥ééé¢ï¼" prop="price"> <el-input-number v-model="form.price" placeholder="请è¾å ¥æ¥ééé¢" :min="0" :precision="2" :step="0.01" style="width: 100%" clearable /> <el-form-item label="æ¥ééé¢ï¼" prop="price"> <el-input-number v-model="form.price" placeholder="请è¾å ¥æ¥ééé¢" :min="0" :precision="2" :step="0.01" style="width: 100%" clearable /> </el-form-item> </el-col> </el-row> <!-- åºå·®å°ç¹ï¼ä» å½ approveType 为 3 æ¶æ¾ç¤ºï¼ --> <el-row v-if="props.approveType == 3"> <el-col :span="24"> <el-form-item label="åºå·®å°ç¹ï¼" prop="location"> <el-input v-model="form.location" placeholder="请è¾å ¥åºå·®å°ç¹" clearable /> <el-form-item label="åºå·®å°ç¹ï¼" prop="location"> <el-input v-model="form.location" placeholder="请è¾å ¥åºå·®å°ç¹" clearable /> </el-form-item> </el-col> </el-row> @@ -103,37 +107,31 @@ <el-form-item> <template #label> <span>审æ¹äººéæ©ï¼</span> <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">æ°å¢èç¹</el-button> <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">æ°å¢èç¹</el-button> </template> <div style="display: flex; align-items: flex-end; flex-wrap: wrap;"> <div v-for="(node, index) in approverNodes" :key="node.id" style="margin-right: 30px; text-align: center; margin-bottom: 10px;" > <div v-for="(node, index) in approverNodes" :key="node.id" style="margin-right: 30px; text-align: center; margin-bottom: 10px;"> <div> <span>审æ¹äºº</span> â </div> <el-select v-model="node.userId" placeholder="éæ©äººå" style="width: 120px; margin-bottom: 8px;" > <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> <el-select v-model="node.userId" placeholder="éæ©äººå" style="width: 120px; margin-bottom: 8px;"> <el-option v-for="user in userListApproval" :key="user.userId" :label="user.userName" :value="user.userId" /> </el-select> <div> <el-button type="danger" size="small" @click="removeApproverNode(index)" v-if="approverNodes.length > 1" >å é¤</el-button> <el-button type="danger" size="small" @click="removeApproverNode(index)" v-if="approverNodes.length > 1">å é¤</el-button> </div> </div> </div> @@ -142,45 +140,51 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="ç³è¯·äººï¼" prop="approveUser"> <el-select v-model="form.approveUser" placeholder="éæ©äººå" filterable default-first-option :reserve-keyword="false" > <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> <el-form-item label="ç³è¯·äººï¼" prop="approveUser"> <el-select v-model="form.approveUser" placeholder="éæ©äººå" filterable default-first-option :reserve-keyword="false"> <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ç³è¯·æ¥æï¼" prop="approveTime"> <el-date-picker v-model="form.approveTime" type="date" placeholder="è¯·éæ©æ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" /> <el-form-item label="ç³è¯·æ¥æï¼" prop="approveTime"> <el-date-picker v-model="form.approveTime" type="date" placeholder="è¯·éæ©æ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="éä»¶ææï¼" prop="remark"> <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" :on-success="handleUploadSuccess" :on-remove="handleRemove"> <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> <template #tip v-if="operationType !== 'view'"> <el-form-item label="éä»¶ææï¼" prop="remark"> <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" :on-success="handleUploadSuccess" :on-remove="handleRemove"> <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> <template #tip v-if="operationType !== 'view'"> <div class="el-upload__tip"> æä»¶æ ¼å¼æ¯æ docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z @@ -193,7 +197,8 @@ </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确认</el-button> <el-button type="primary" @click="submitForm">确认</el-button> <el-button @click="closeDia">åæ¶</el-button> </div> </template> @@ -202,275 +207,286 @@ </template> <script setup> import {ref, reactive, toRefs, getCurrentInstance} from "vue"; import { approveProcessAdd, approveProcessGetInfo, approveProcessUpdate, getDept } from "@/api/collaborativeApproval/approvalProcess.js"; import { delLedgerFile, } from "@/api/salesManagement/salesLedger.js"; import {userListNoPageByTenantId} from "@/api/system/user.js"; import { getToken } from "@/utils/auth"; const { proxy } = getCurrentInstance() const emit = defineEmits(['close']) import useUserStore from "@/store/modules/user"; import { getCurrentDate } from "@/utils/index.js"; import log from "@/views/monitor/job/log.vue"; const userStore = useUserStore(); import { ref, reactive, toRefs, getCurrentInstance } from "vue"; import { approveProcessAdd, approveProcessGetInfo, approveProcessUpdate, getDept, } from "@/api/collaborativeApproval/approvalProcess.js"; import { delLedgerFile } from "@/api/salesManagement/salesLedger.js"; import { userListNoPageByTenantId } from "@/api/system/user.js"; import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js"; import { getToken } from "@/utils/auth"; const { proxy } = getCurrentInstance(); const emit = defineEmits(["close"]); import useUserStore from "@/store/modules/user"; import { getCurrentDate } from "@/utils/index.js"; import log from "@/views/monitor/job/log.vue"; const userStore = useUserStore(); const dialogFormVisible = ref(false); const operationType = ref('') const fileList = ref([]); const upload = reactive({ // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/file/upload", // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, }); const data = reactive({ form: { approveTime: "", approveId: "", approveUser: "", approveDeptId: "", approveDeptName: "", approveReason: "", checkResult: "", tempFileIds: [], approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid startDate: "", // 请åå¼å§æ¶é´ endDate: "", // 请åç»ææ¶é´ price: null, // æ¥ééé¢ location: "" // åºå·®å°ç¹ }, rules: { approveTime: [{ required: false, message: "请è¾å ¥", trigger: "change" },], approveId: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], approveUser: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], approveDeptName: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], approveReason: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], checkResult: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], startDate: [{ required: true, message: "è¯·éæ©è¯·åå¼å§æ¶é´", trigger: "change" }], endDate: [{ required: true, message: "è¯·éæ©è¯·åç»ææ¶é´", trigger: "change" }], price: [{ required: true, message: "请è¾å ¥æ¥ééé¢", trigger: "blur" }], location: [{ required: true, message: "请è¾å ¥åºå·®å°ç¹", trigger: "blur" }], }, }); const { form, rules } = toRefs(data); const productOptions = ref([]); const currentApproveStatus = ref(0) const props = defineProps({ approveType: { type: [Number, String], default: 0 } }) // 审æ¹äººèç¹ç¸å ³ const approverNodes = ref([ { id: 1, userId: null } ]) let nextApproverId = 2 const userList = ref([]) function addApproverNode() { approverNodes.value.push({ id: nextApproverId++, userId: null }) } function removeApproverNode(index) { approverNodes.value.splice(index, 1) } // å¤çé¨é¨éæ©åå const handleDeptChange = (deptId) => { if (deptId) { const selectedDept = productOptions.value.find(dept => dept.deptId === deptId); if (selectedDept) { form.value.approveDeptName = selectedDept.deptName; } } else { form.value.approveDeptName = ''; } }; // æå¼å¼¹æ¡ const openDialog = (type, row) => { operationType.value = type; dialogFormVisible.value = true; userListNoPageByTenantId().then((res) => { userList.value = res.data; const dialogFormVisible = ref(false); const operationType = ref(""); const fileList = ref([]); const upload = reactive({ // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/file/upload", // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, }); form.value = {} approverNodes.value = [ { id: 1, userId: null } ] form.value.approveUser = userStore.id; form.value.approveTime = getCurrentDate(); // è·åå½åç¨æ·ä¿¡æ¯å¹¶è®¾ç½®é¨é¨ID form.value.approveDeptId = userStore.currentDeptId // å è½½é¨é¨é项ï¼å¹¶å¨å è½½å®æå设置é¨é¨åç§° getProductOptions(); if (operationType.value === 'edit') { fileList.value = row.commonFileList form.value.tempFileIds = fileList.value.map(file => file.id) currentApproveStatus.value = row.approveStatus approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => { form.value = {...res.data} // 忾审æ¹äºº if (res.data && res.data.approveUserIds) { const userIds = res.data.approveUserIds.split(',') approverNodes.value = userIds.map((userId, idx) => ({ id: idx + 1, userId: parseInt(userId.trim()) })) nextApproverId = userIds.length + 1 } else { approverNodes.value = [{ id: 1, userId: null }] nextApproverId = 2 } }) const data = reactive({ form: { approveTime: "", approveId: "", approveUser: "", approveDeptId: "", approveDeptName: "", approveReason: "", checkResult: "", tempFileIds: [], approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid startDate: "", // 请åå¼å§æ¶é´ endDate: "", // 请åç»ææ¶é´ price: null, // æ¥ééé¢ location: "", // åºå·®å°ç¹ }, rules: { approveTime: [{ required: false, message: "请è¾å ¥", trigger: "change" }], approveId: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], approveUser: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], approveDeptName: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], approveReason: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], checkResult: [{ required: false, message: "请è¾å ¥", trigger: "blur" }], startDate: [ { required: true, message: "è¯·éæ©è¯·åå¼å§æ¶é´", trigger: "change" }, ], endDate: [ { required: true, message: "è¯·éæ©è¯·åç»ææ¶é´", trigger: "change" }, ], price: [{ required: true, message: "请è¾å ¥æ¥ééé¢", trigger: "blur" }], location: [{ required: true, message: "请è¾å ¥åºå·®å°ç¹", trigger: "blur" }], }, }); const { form, rules } = toRefs(data); const productOptions = ref([]); const currentApproveStatus = ref(0); const props = defineProps({ approveType: { type: [Number, String], default: 0, }, }); // 审æ¹äººèç¹ç¸å ³ const approverNodes = ref([{ id: 1, userId: null }]); let nextApproverId = 2; const userList = ref([]); const userListApproval = ref([]); function addApproverNode() { approverNodes.value.push({ id: nextApproverId++, userId: null }); } } const getProductOptions = () => { return getDept().then((res) => { productOptions.value = res.data; // 妿已æé¨é¨IDï¼èªå¨è®¾ç½®é¨é¨åç§°ï¼ç¨äºéªè¯ï¼ if (form.value.approveDeptId && productOptions.value.length > 0) { const matchedDept = productOptions.value.find(dept => dept.deptId == form.value.approveDeptId || String(dept.deptId) === String(form.value.approveDeptId) function removeApproverNode(index) { approverNodes.value.splice(index, 1); } // å¤çé¨é¨éæ©åå const handleDeptChange = deptId => { if (deptId) { const selectedDept = productOptions.value.find( dept => dept.deptId === deptId ); if (matchedDept) { form.value.approveDeptName = matchedDept.deptName; if (selectedDept) { form.value.approveDeptName = selectedDept.deptName; } } else { form.value.approveDeptName = ""; } }); }; 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 submitForm = () => { // æ¶éææèç¹ç审æ¹äººid form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') form.value.approveType = props.approveType // 审æ¹äººå¿ å¡«æ ¡éª const hasEmptyApprover = approverNodes.value.some(node => !node.userId) if (hasEmptyApprover) { proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼") return } // å½ approveType 为 2 æ¶ï¼æ ¡éªè¯·åæ¶é´ if (props.approveType == 2) { if (!form.value.startDate) { proxy.$modal.msgError("è¯·éæ©è¯·åå¼å§æ¶é´ï¼") return } if (!form.value.endDate) { proxy.$modal.msgError("è¯·éæ©è¯·åç»ææ¶é´ï¼") return } // æ ¡éªç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ if (new Date(form.value.endDate) < new Date(form.value.startDate)) { proxy.$modal.msgError("请åç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ï¼") return } } // å½ approveType 为 3 æ¶ï¼æ ¡éªåºå·®å°ç¹ if (props.approveType == 3) { if (!form.value.location || form.value.location.trim() === '') { proxy.$modal.msgError("请è¾å ¥åºå·®å°ç¹ï¼") return } } // å½ approveType 为 4 æ¶ï¼æ ¡éªæ¥ééé¢ if (props.approveType == 4) { if (!form.value.price || form.value.price <= 0) { proxy.$modal.msgError("请è¾å ¥ææçæ¥ééé¢ï¼") return } } proxy.$refs.formRef.validate(valid => { if (valid) { if (operationType.value === "add" || currentApproveStatus.value == 3) { approveProcessAdd(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); }) } else { approveProcessUpdate(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); }) } } }) } // å ³éå¼¹æ¡ const closeDia = () => { fileList.value = [] proxy.resetForm("formRef"); dialogFormVisible.value = false; emit('close') }; }; // æå¼å¼¹æ¡ const openDialog = (type, row) => { operationType.value = type; dialogFormVisible.value = true; userListNoPageByTenantId().then(res => { userList.value = res.data; }); approveUserList({ approveType: props.approveType }).then(res => { userListApproval.value = res.data; }); form.value = {}; approverNodes.value = [{ id: 1, userId: null }]; form.value.approveUser = userStore.id; form.value.approveTime = getCurrentDate(); // ä¸ä¼ åæ ¡æ£ function handleBeforeUpload(file) { // æ ¡æ£æä»¶å¤§å° // if (file.size > 1024 * 1024 * 10) { // proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶ è¿10MB!"); // return false; // } proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); return true; } // ä¸ä¼ 失败 function handleUploadError(err) { proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); proxy.$modal.closeLoading(); } // ä¸ä¼ æååè° function handleUploadSuccess(res, file, uploadFiles) { proxy.$modal.closeLoading(); if (res.code === 200) { // ç¡®ä¿ tempFileIds åå¨ä¸ä¸ºæ°ç» if (!form.value.tempFileIds) { form.value.tempFileIds = []; // è·åå½åç¨æ·ä¿¡æ¯å¹¶è®¾ç½®é¨é¨ID form.value.approveDeptId = userStore.currentDeptId; // å è½½é¨é¨é项ï¼å¹¶å¨å è½½å®æå设置é¨é¨åç§° getProductOptions(); if (operationType.value === "edit") { fileList.value = row.commonFileList; form.value.tempFileIds = fileList.value.map(file => file.id); currentApproveStatus.value = row.approveStatus; approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then( res => { form.value = { ...res.data }; // 忾审æ¹äºº if (res.data && res.data.approveUserIds) { const userIds = res.data.approveUserIds.split(","); approverNodes.value = userIds.map((userId, idx) => ({ id: idx + 1, userId: parseInt(userId.trim()), })); nextApproverId = userIds.length + 1; } else { approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; } } ); } form.value.tempFileIds.push(res.data.tempId); proxy.$modal.msgSuccess("ä¸ä¼ æå"); } else { proxy.$modal.msgError(res.msg); proxy.$refs.fileUpload.handleRemove(file); } } // ç§»é¤æä»¶ function handleRemove(file) { if (operationType.value === "edit") { let ids = []; ids.push(file.id); delLedgerFile(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); }; const getProductOptions = () => { return getDept().then(res => { productOptions.value = res.data; // 妿已æé¨é¨IDï¼èªå¨è®¾ç½®é¨é¨åç§°ï¼ç¨äºéªè¯ï¼ if (form.value.approveDeptId && productOptions.value.length > 0) { const matchedDept = productOptions.value.find( dept => dept.deptId == form.value.approveDeptId || String(dept.deptId) === String(form.value.approveDeptId) ); if (matchedDept) { form.value.approveDeptName = matchedDept.deptName; } } }); }; 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 submitForm = () => { // æ¶éææèç¹ç审æ¹äººid form.value.approveUserIds = approverNodes.value .map(node => node.userId) .join(","); form.value.approveType = props.approveType; // 审æ¹äººå¿ å¡«æ ¡éª const hasEmptyApprover = approverNodes.value.some(node => !node.userId); if (hasEmptyApprover) { proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼"); return; } // å½ approveType 为 2 æ¶ï¼æ ¡éªè¯·åæ¶é´ if (props.approveType == 2) { if (!form.value.startDate) { proxy.$modal.msgError("è¯·éæ©è¯·åå¼å§æ¶é´ï¼"); return; } if (!form.value.endDate) { proxy.$modal.msgError("è¯·éæ©è¯·åç»ææ¶é´ï¼"); return; } // æ ¡éªç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ if (new Date(form.value.endDate) < new Date(form.value.startDate)) { proxy.$modal.msgError("请åç»ææ¶é´ä¸è½æ©äºå¼å§æ¶é´ï¼"); return; } } // å½ approveType 为 3 æ¶ï¼æ ¡éªåºå·®å°ç¹ if (props.approveType == 3) { if (!form.value.location || form.value.location.trim() === "") { proxy.$modal.msgError("请è¾å ¥åºå·®å°ç¹ï¼"); return; } } // å½ approveType 为 4 æ¶ï¼æ ¡éªæ¥ééé¢ if (props.approveType == 4) { if (!form.value.price || form.value.price <= 0) { proxy.$modal.msgError("请è¾å ¥ææçæ¥ééé¢ï¼"); return; } } proxy.$refs.formRef.validate(valid => { if (valid) { if (operationType.value === "add" || currentApproveStatus.value == 3) { approveProcessAdd(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); }); } else { approveProcessUpdate(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); }); } } }); }; // å ³éå¼¹æ¡ const closeDia = () => { fileList.value = []; proxy.resetForm("formRef"); dialogFormVisible.value = false; emit("close"); }; defineExpose({ openDialog, }); // ä¸ä¼ åæ ¡æ£ function handleBeforeUpload(file) { // æ ¡æ£æä»¶å¤§å° // if (file.size > 1024 * 1024 * 10) { // proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶ è¿10MB!"); // return false; // } proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); return true; } // ä¸ä¼ 失败 function handleUploadError(err) { proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); proxy.$modal.closeLoading(); } // ä¸ä¼ æååè° function handleUploadSuccess(res, file, uploadFiles) { proxy.$modal.closeLoading(); if (res.code === 200) { // ç¡®ä¿ tempFileIds åå¨ä¸ä¸ºæ°ç» if (!form.value.tempFileIds) { form.value.tempFileIds = []; } form.value.tempFileIds.push(res.data.tempId); proxy.$modal.msgSuccess("ä¸ä¼ æå"); } else { proxy.$modal.msgError(res.msg); proxy.$refs.fileUpload.handleRemove(file); } } // ç§»é¤æä»¶ function handleRemove(file) { if (operationType.value === "edit") { let ids = []; ids.push(file.id); delLedgerFile(ids).then(res => { proxy.$modal.msgSuccess("å 餿å"); }); } } defineExpose({ openDialog, }); </script> <style scoped> </style> src/views/collaborativeApproval/approvalProcess/index.vue
@@ -1,364 +1,589 @@ <template> <div class="app-container"> <!-- æ ç¾é¡µåæ¢ä¸åç审æ¹ç±»å --> <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs"> <el-tab-pane label="å ¬åºç®¡ç" name="1"></el-tab-pane> <el-tab-pane label="请å管ç" name="2"></el-tab-pane> <el-tab-pane label="åºå·®ç®¡ç" name="3"></el-tab-pane> <el-tab-pane label="æ¥é管ç" name="4"></el-tab-pane> <el-tab-pane label="éè´å®¡æ¹" name="5"></el-tab-pane> <el-tab-pane label="æ¥ä»·å®¡æ¹" name="6"></el-tab-pane> <el-tab-pane label="å货审æ¹" name="7"></el-tab-pane> <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs"> <el-tab-pane label="å ¬åºç®¡ç" name="1"></el-tab-pane> <el-tab-pane label="请å管ç" name="2"></el-tab-pane> <el-tab-pane label="åºå·®ç®¡ç" name="3"></el-tab-pane> <el-tab-pane label="æ¥é管ç" name="4"></el-tab-pane> <el-tab-pane label="éè´å®¡æ¹" name="5"></el-tab-pane> <el-tab-pane label="æ¥ä»·å®¡æ¹" name="6"></el-tab-pane> <el-tab-pane label="å货审æ¹" name="7"></el-tab-pane> </el-tabs> <div class="search_form"> <div> <span class="search_title">æµç¨ç¼å·ï¼</span> <el-input v-model="searchForm.approveId" style="width: 240px" placeholder="请è¾å ¥æµç¨ç¼å·æç´¢" @change="handleQuery" clearable :prefix-icon="Search" /> <el-input v-model="searchForm.approveId" style="width: 240px" placeholder="请è¾å ¥æµç¨ç¼å·æç´¢" @change="handleQuery" clearable :prefix-icon="Search" /> <span class="search_title ml10">审æ¹ç¶æï¼</span> <el-select v-model="searchForm.approveStatus" clearable @change="handleQuery" style="width: 240px"> <el-option label="å¾ å®¡æ ¸" :value="0" /> <el-option label="å®¡æ ¸ä¸" :value="1" /> <el-option label="å®¡æ ¸å®æ" :value="2" /> <el-option label="å®¡æ ¸æªéè¿" :value="3" /> <el-option label="已鿰æäº¤" :value="4" /> </el-select> <el-button type="primary" @click="handleQuery" style="margin-left: 10px" >æç´¢</el-button > <el-select v-model="searchForm.approveStatus" clearable @change="handleQuery" style="width: 240px"> <el-option label="å¾ å®¡æ ¸" :value="0" /> <el-option label="å®¡æ ¸ä¸" :value="1" /> <el-option label="å®¡æ ¸å®æ" :value="2" /> <el-option label="å®¡æ ¸æªéè¿" :value="3" /> <el-option label="已鿰æäº¤" :value="4" /> </el-select> <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> </div> <div> <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" >æ°å¢</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="danger" plain @click="handleDelete" v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" >å é¤</el-button> <el-button @click="handleOut">审æ¹äººç»´æ¤</el-button> <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7">æ°å¢</el-button> <el-button @click="handleExport">导åº</el-button> <el-button type="danger" plain @click="handleDelete" v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7">å é¤</el-button> </div> </div> <div class="table_list"> <PIMTable rowKey="id" :column="tableColumnCopy" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" :total="page.total" ></PIMTable> <PIMTable rowKey="id" :column="tableColumnCopy" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" :total="page.total"></PIMTable> </div> <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia> <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia> <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia> <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia> <FileList ref="fileListRef" /> <!-- 审æ¹äººç»´æ¤å¯¹è¯æ¡ --> <el-dialog v-model="approverDialogVisible" title="审æ¹äººç»´æ¤" width="800px"> <div class="approver-dialog"> <div class="selected-info" v-if="selectedApprovers.length > 0"> <div class="info-title">已鿩ç审æ¹äººï¼</div> <div class="selected-list"> <el-tag v-for="approver in selectedApprovers" :key="approver.id" class="approver-tag"> {{ approver.userName }} <el-icon class="el-tag__close el-icon--close" @click="removeApprover(approver)"> <CircleClose /> </el-icon> </el-tag> </div> </div> <el-table ref="approverTable" :data="approverList" style="width: 100%" @selection-change="handleApproverSelectionChange" v-loading="approverLoading"> <el-table-column type="selection" width="55"></el-table-column> <el-table-column prop="userId" label="ID"></el-table-column> <el-table-column prop="userName" label="å§å"></el-table-column> <el-table-column prop="createTime" label="å建æ¶é´"></el-table-column> </el-table> </div> <template #footer> <span class="dialog-footer"> <el-button @click="approverDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="submitApprovers" :disabled="selectedApprovers.length === 0"> æäº¤ </el-button> </span> </template> </el-dialog> </div> </template> <script setup> import FileList from "./fileList.vue"; import { Search } from "@element-plus/icons-vue"; import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue"; import {ElMessageBox} from "element-plus"; import { useRoute } from 'vue-router'; import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue"; import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js"; import useUserStore from "@/store/modules/user"; import FileList from "./fileList.vue"; import { Search } from "@element-plus/icons-vue"; import { onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance, } from "vue"; import { ElMessageBox } from "element-plus"; import { useRoute } from "vue-router"; import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue"; import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; import { approveProcessDelete, approveProcessListPage, approveUserList, addApproveUser, deleteApproveUser, } from "@/api/collaborativeApproval/approvalProcess.js"; import { userListNoPageByTenantId } from "@/api/system/user.js"; import useUserStore from "@/store/modules/user"; const userStore = useUserStore(); const route = useRoute(); const userStore = useUserStore(); const route = useRoute(); // å½åéä¸çæ ç¾é¡µï¼é»è®¤ä¸ºå ¬åºç®¡ç const activeTab = ref('1'); // å½åéä¸çæ ç¾é¡µï¼é»è®¤ä¸ºå ¬åºç®¡ç const activeTab = ref("1"); // å½å审æ¹ç±»åï¼æ ¹æ®éä¸çæ ç¾é¡µè®¡ç® const currentApproveType = computed(() => { return Number(activeTab.value); }); // å½å审æ¹ç±»åï¼æ ¹æ®éä¸çæ ç¾é¡µè®¡ç® const currentApproveType = computed(() => { return Number(activeTab.value); }); // æ ç¾é¡µåæ¢å¤ç const handleTabChange = (tabName) => { // 忢æ ç¾é¡µæ¶éç½®æç´¢æ¡ä»¶åå页ï¼å¹¶éæ°å è½½æ°æ® searchForm.value.approveId = ''; searchForm.value.approveStatus = ''; page.current = 1; getList(); }; // æ ç¾é¡µåæ¢å¤ç const handleTabChange = tabName => { // 忢æ ç¾é¡µæ¶éç½®æç´¢æ¡ä»¶åå页ï¼å¹¶éæ°å è½½æ°æ® searchForm.value.approveId = ""; searchForm.value.approveStatus = ""; page.current = 1; getList(); }; const data = reactive({ searchForm: { approveId: "", approveStatus: "", }, }); const { searchForm } = toRefs(data); const data = reactive({ searchForm: { approveId: "", approveStatus: "", }, }); const { searchForm } = toRefs(data); // å¨æè¡¨æ ¼åé ç½®ï¼æ ¹æ®å®¡æ¹ç±»åçæå const tableColumnCopy = computed(() => { const isLeaveType = currentApproveType.value === 2; // 请å管ç const isReimburseType = currentApproveType.value === 4; // æ¥é管ç const isQuotationType = currentApproveType.value === 6; // æ¥ä»·å®¡æ¹ const isPurchaseType = currentApproveType.value === 5; // éè´å®¡æ¹ // å¨æè¡¨æ ¼åé ç½®ï¼æ ¹æ®å®¡æ¹ç±»åçæå const tableColumnCopy = computed(() => { const isLeaveType = currentApproveType.value === 2; // 请å管ç const isReimburseType = currentApproveType.value === 4; // æ¥é管ç const isQuotationType = currentApproveType.value === 6; // æ¥ä»·å®¡æ¹ const isPurchaseType = currentApproveType.value === 5; // éè´å®¡æ¹ // åºç¡åé ç½® const baseColumns = [ { label: "审æ¹ç¶æ", prop: "approveStatus", dataType: "tag", width: 100, formatData: (params) => { if (params == 0) { return "å¾ å®¡æ ¸"; } else if (params == 1) { return "å®¡æ ¸ä¸"; } else if (params == 2) { return "å®¡æ ¸å®æ"; } else if (params == 4) { return "已鿰æäº¤"; } else { return 'ä¸éè¿'; } // åºç¡åé ç½® const baseColumns = [ { label: "审æ¹ç¶æ", prop: "approveStatus", dataType: "tag", width: 100, formatData: params => { if (params == 0) { return "å¾ å®¡æ ¸"; } else if (params == 1) { return "å®¡æ ¸ä¸"; } else if (params == 2) { return "å®¡æ ¸å®æ"; } else if (params == 4) { return "已鿰æäº¤"; } else { return "ä¸éè¿"; } }, formatType: params => { if (params == 0) { return "warning"; } else if (params == 1) { return "primary"; } else if (params == 2) { return "success"; } else if (params == 4) { return "info"; } else { return "danger"; } }, }, formatType: (params) => { if (params == 0) { return "warning"; } else if (params == 1) { return "primary"; } else if (params == 2) { return "success"; } else if (params == 4) { return "info"; } else { return 'danger'; } { label: "æµç¨ç¼å·", prop: "approveId", width: 170, }, }, { label: "æµç¨ç¼å·", prop: "approveId", width: 170 }, { label: "ç³è¯·é¨é¨", prop: "approveDeptName", width: 220 }, { label: isQuotationType ? "æ¥ä»·åå·" : isPurchaseType ? "éè´ååå·" : "审æ¹äºç±", prop: "approveReason", }, { label: "ç³è¯·äºº", prop: "approveUserName", width: 120 { label: "ç³è¯·é¨é¨", prop: "approveDeptName", width: 220, }, { label: isQuotationType ? "æ¥ä»·åå·" : isPurchaseType ? "éè´ååå·" : "审æ¹äºç±", prop: "approveReason", }, { label: "ç³è¯·äºº", prop: "approveUserName", width: 120, }, ]; // éé¢åï¼ä» æ¥éç®¡çæ¾ç¤ºï¼ if (isReimburseType) { baseColumns.push({ label: "éé¢ï¼å ï¼", prop: "price", width: 120, }); } ]; // éé¢åï¼ä» æ¥éç®¡çæ¾ç¤ºï¼ if (isReimburseType) { // æ¥æåï¼æ ¹æ®ç±»å卿é ç½®ï¼ baseColumns.push( { label: isLeaveType ? "å¼å§æ¥æ" : "ç³è¯·æ¥æ", prop: isLeaveType ? "startDate" : "approveTime", width: 200, }, { label: "ç»ææ¥æ", prop: isLeaveType ? "endDate" : "approveOverTime", width: 120, } ); // å½å审æ¹äººå baseColumns.push({ label: "éé¢ï¼å ï¼", prop: "price", width: 120 label: "å½å审æ¹äºº", prop: "approveUserCurrentName", width: 120, }); } // æ¥æåï¼æ ¹æ®ç±»å卿é ç½®ï¼ baseColumns.push( { label: isLeaveType ? "å¼å§æ¥æ" : "ç³è¯·æ¥æ", prop: isLeaveType ? "startDate" : "approveTime", width: 200 }, { label: "ç»ææ¥æ", prop: isLeaveType ? "endDate" : "approveOverTime", width: 120 // æä½å const actionOperations = [ { name: "ç¼è¾", type: "text", clickFun: row => { openForm("edit", row); }, disabled: row => currentApproveType.value === 5 || currentApproveType.value === 6 || currentApproveType.value === 7 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4, }, { name: "å®¡æ ¸", type: "text", clickFun: row => { openApprovalDia("approval", row); }, disabled: row => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id, }, { name: "详æ ", type: "text", clickFun: row => { openApprovalDia("view", row); }, }, ]; // æ¥ä»·å®¡æ¹ï¼ç±»å 6ï¼ä¸å±ç¤ºâéä»¶âæä½ if (!isQuotationType) { actionOperations.push({ name: "éä»¶", type: "text", clickFun: row => { downLoadFile(row); }, }); } ); // å½å审æ¹äººå baseColumns.push({ label: "å½å审æ¹äºº", prop: "approveUserCurrentName", width: 120 }); // æä½å const actionOperations = [ { name: "ç¼è¾", type: "text", clickFun: (row) => { openForm("edit", row); }, disabled: (row) => currentApproveType.value === 5 || currentApproveType.value === 6 || currentApproveType.value === 7 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4 }, { name: "å®¡æ ¸", type: "text", clickFun: (row) => { openApprovalDia("approval", row); }, disabled: (row) => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id }, { name: "详æ ", type: "text", clickFun: (row) => { openApprovalDia("view", row); }, }, ]; // æ¥ä»·å®¡æ¹ï¼ç±»å 6ï¼ä¸å±ç¤ºâéä»¶âæä½ if (!isQuotationType) { actionOperations.push({ name: "éä»¶", type: "text", clickFun: (row) => { downLoadFile(row); }, baseColumns.push({ dataType: "action", label: "æä½", align: "center", fixed: "right", width: 230, operation: actionOperations, }); } baseColumns.push({ dataType: "action", label: "æä½", align: "center", fixed: "right", width: 230, operation: actionOperations, return baseColumns; }); return baseColumns; }); const tableData = ref([]); const selectedRows = ref([]); const tableLoading = ref(false); const page = reactive({ current: 1, size: 100, total: 0 }); const infoFormDia = ref() const approvalDia = ref() const { proxy } = getCurrentInstance() const tableData = ref([]); const selectedRows = ref([]); const tableLoading = ref(false); const page = reactive({ current: 1, size: 100, total: 0, }); const infoFormDia = ref(); const approvalDia = ref(); const { proxy } = getCurrentInstance(); // æ¥è¯¢å表 /** æç´¢æé®æä½ */ const handleQuery = () => { page.current = 1; getList(); }; const fileListRef = ref(null) const downLoadFile = (row) => { fileListRef.value.open(row.commonFileList) // 审æ¹äººç»´æ¤å¯¹è¯æ¡ const approverDialogVisible = ref(false); const selectedApprovers = ref([]); const existingApprovers = ref([]); // å·²æç审æ¹äººå表 const approverLoading = ref(false); // å è½½ç¶æ } const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; getList(); }; const getList = () => { tableLoading.value = true; approveProcessListPage({...page, ...searchForm.value, approveType: currentApproveType.value}).then(res => { tableLoading.value = false; tableData.value = res.data.records page.total = res.data.total; }).catch(err => { tableLoading.value = false; }) }; // å¯¼åº const handleOut = () => { const type = currentApproveType.value const urlMap = { 0: "/approveProcess/exportZero", 1: "/approveProcess/exportOne", 2: "/approveProcess/exportTwo", 3: "/approveProcess/exportThree", 4: "/approveProcess/exportFour", 5: "/approveProcess/exportFive", 6: "/approveProcess/exportSix", 7: "/approveProcess/exportSeven", } const url = urlMap[type] || urlMap[0] const nameMap = { 0: "åå审æ¹ç®¡ç表", 1: "å ¬åºç®¡ç审æ¹è¡¨", 2: "请å管ç审æ¹è¡¨", 3: "åºå·®ç®¡ç审æ¹è¡¨", 4: "æ¥é管ç审æ¹è¡¨", 5: "éè´ç³è¯·å®¡æ¹è¡¨", 6: "æ¥ä»·å®¡æ¹è¡¨", 7: "å货审æ¹è¡¨", } const fileName = nameMap[type] || nameMap[0] proxy.download(url, {}, `${fileName}.xlsx`) } // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = (selection) => { selectedRows.value = selection; }; // 审æ¹äººåè¡¨æ°æ® const approverList = ref([]); const approverTable = ref(null); // æå¼æ°å¢ãç¼è¾å¼¹æ¡ const openForm = (type, row) => { nextTick(() => { infoFormDia.value?.openDialog(type, row) }) }; // æå¼æ°å¢æ£éªå¼¹æ¡ const openApprovalDia = (type, row) => { nextTick(() => { approvalDia.value?.openDialog(type, row) }) }; // æ¥è¯¢å表 /** æç´¢æé®æä½ */ const handleQuery = () => { page.current = 1; getList(); }; const fileListRef = ref(null); const downLoadFile = row => { fileListRef.value.open(row.commonFileList); }; const pagination = obj => { page.current = obj.page; page.size = obj.limit; getList(); }; const getList = () => { tableLoading.value = true; approveProcessListPage({ ...page, ...searchForm.value, approveType: currentApproveType.value, }) .then(res => { tableLoading.value = false; tableData.value = res.data.records; page.total = res.data.total; }) .catch(err => { tableLoading.value = false; }); }; // å¯¼åº const handleExport = () => { const type = currentApproveType.value; const urlMap = { 0: "/approveProcess/exportZero", 1: "/approveProcess/exportOne", 2: "/approveProcess/exportTwo", 3: "/approveProcess/exportThree", 4: "/approveProcess/exportFour", 5: "/approveProcess/exportFive", 6: "/approveProcess/exportSix", 7: "/approveProcess/exportSeven", }; const url = urlMap[type] || urlMap[0]; const nameMap = { 0: "åå审æ¹ç®¡ç表", 1: "å ¬åºç®¡ç审æ¹è¡¨", 2: "请å管ç审æ¹è¡¨", 3: "åºå·®ç®¡ç审æ¹è¡¨", 4: "æ¥é管ç审æ¹è¡¨", 5: "éè´ç³è¯·å®¡æ¹è¡¨", 6: "æ¥ä»·å®¡æ¹è¡¨", 7: "å货审æ¹è¡¨", }; const fileName = nameMap[type] || nameMap[0]; proxy.download(url, {}, `${fileName}.xlsx`); }; // å é¤ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map((item) => item.approveId); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) // 审æ¹äººç»´æ¤ const handleOut = () => { approverLoading.value = true; // ä» API è·åææç¨æ·å表 userListNoPageByTenantId() .then(res => { // è½¬æ¢ API è¿åçæ°æ®ç»æä¸ºè¡¨æ ¼éè¦çæ ¼å¼ approverList.value = res.data.map(user => ({ userId: user.userId, userName: user.nickName, createTime: user.createTime || "", })); // è·åå½å审æ¹ç±»åå·²æç审æ¹äººå表 const currentType = currentApproveType.value; approveUserList({ approveType: currentType }) .then(approversRes => { existingApprovers.value = approversRes.data || []; // approverList.value = approversRes.data; selectedApprovers.value = existingApprovers.value; // approverList.value = ; // æ è®°å·²æç审æ¹äºº // approverList.value = allUsers.map(user => ({ // ...user, // id: // existingApprovers.value.find( // approver => approver.userId === user.userId // )?.id || 0, // isExisting: existingApprovers.value.some( // approver => approver.userId === user.userId // ), // })); console.log(approverList.value, "==approverList.value=="); approverDialogVisible.value = true; approverLoading.value = false; // æ´æ°è¡¨æ ¼å¾éç¶æ nextTick(() => { if (approverTable.value) { // å æ¸ 空ææå¾é approverList.value.forEach(row => { approverTable.value.toggleRowSelection(row, false); }); // åå¾éå·²æç审æ¹äºº existingApprovers.value.forEach(existingApprover => { const row = approverList.value.find( user => user.userId === existingApprover.userId ); if (row) { approverTable.value.toggleRowSelection(row, true); } }); } }); }) .catch(err => { console.error("è·åå·²æå®¡æ¹äººå表失败:", err); proxy.$modal.msgError("è·åå·²æå®¡æ¹äººå表失败"); approverLoading.value = false; }); }) .catch(err => { console.error("è·åç¨æ·å表失败:", err); proxy.$modal.msgError("è·åç¨æ·å表失败"); approverLoading.value = false; }); }; // å¤ç审æ¹äººéæ© const handleApproverSelectionChange = selection => { selectedApprovers.value = selection; }; // ç§»é¤å®¡æ¹äºº const removeApprover = approver => { selectedApprovers.value = selectedApprovers.value.filter( item => item.id !== approver.id ); approverTable.value.toggleRowSelection(approver, false); }; // æäº¤å®¡æ¹äºº const submitApprovers = () => { if (selectedApprovers.value.length === 0) { proxy.$modal.msgWarning("è¯·éæ©å®¡æ¹äºº"); return; } const currentType = currentApproveType.value; const selectedIds = selectedApprovers.value.map(approver => approver.userId); const existingIds = existingApprovers.value.map(approver => approver.userId); // éè¦å é¤ç审æ¹äººï¼åæç使ªè¢«éæ©çï¼ const toDelete = existingApprovers.value .filter(approver => !selectedIds.includes(approver.userId)) .map(approver => approver.id); // éè¦æ·»å ç审æ¹äººï¼æ°éæ©çä½ä¸å¨ç°æå表ä¸çï¼ const toAdd = selectedApprovers.value .filter(approver => !existingIds.includes(approver.userId)) .map(approver => ({ approveType: currentType, id: 0, userId: approver.userId, userName: approver.userName, })); console.log(toDelete, "==å é¤=="); console.log(toAdd, "==æ·»å =="); // å å é¤ä¸éè¦ç审æ¹äºº const deletePromise = toDelete.length > 0 ? deleteApproveUser(toDelete) : Promise.resolve(); deletePromise .then(() => { approveProcessDelete(ids).then((res) => { // ç¶åæ·»å æ°ç审æ¹äºº if (toAdd.length === 0) { return Promise.resolve(); } // é个添å 审æ¹äºº return Promise.all(toAdd.map(user => addApproveUser(user))); }) .then(() => { proxy.$modal.msgSuccess("审æ¹äººç»´æ¤æå"); approverDialogVisible.value = false; selectedApprovers.value = []; }) .catch(err => { console.error("审æ¹äººç»´æ¤å¤±è´¥:", err); proxy.$modal.msgError("审æ¹äººç»´æ¤å¤±è´¥"); }); }; // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = selection => { selectedRows.value = selection; }; // æå¼æ°å¢ãç¼è¾å¼¹æ¡ const openForm = (type, row) => { nextTick(() => { infoFormDia.value?.openDialog(type, row); }); }; // æå¼æ°å¢æ£éªå¼¹æ¡ const openApprovalDia = (type, row) => { nextTick(() => { approvalDia.value?.openDialog(type, row); }); }; // å é¤ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map(item => item.approveId); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { approveProcessDelete(ids).then(res => { proxy.$modal.msgSuccess("å 餿å"); getList(); }); @@ -366,29 +591,65 @@ .catch(() => { proxy.$modal.msg("已忶"); }); }; onMounted(() => { // æ ¹æ®URLåæ°è®¾ç½®æ ç¾é¡µåæ¥è¯¢æ¡ä»¶ const approveType = route.query.approveType; const approveId = route.query.approveId; if (approveType) { // 设置æ ç¾é¡µï¼approveType å¯¹åº activeTab ç nameï¼ activeTab.value = String(approveType); } if (approveId) { // 设置æµç¨ç¼å·æ¥è¯¢æ¡ä»¶ searchForm.value.approveId = String(approveId); } // æ¥è¯¢å表 getList(); }); }; onMounted(() => { // æ ¹æ®URLåæ°è®¾ç½®æ ç¾é¡µåæ¥è¯¢æ¡ä»¶ const approveType = route.query.approveType; const approveId = route.query.approveId; if (approveType) { // 设置æ ç¾é¡µï¼approveType å¯¹åº activeTab ç nameï¼ activeTab.value = String(approveType); } if (approveId) { // 设置æµç¨ç¼å·æ¥è¯¢æ¡ä»¶ searchForm.value.approveId = String(approveId); } // æ¥è¯¢å表 getList(); }); </script> <style scoped> .approval-tabs { margin-bottom: 10px; } .approval-tabs { margin-bottom: 10px; } /* 审æ¹äººç»´æ¤å¯¹è¯æ¡æ ·å¼ */ .approver-dialog { display: flex; flex-direction: column; gap: 20px; } .selected-info { /* margin-top: 20px; */ padding: 15px; background: #f5f7fa; border-radius: 4px; } .info-title { font-weight: 600; margin-bottom: 10px; color: #303133; } .selected-list { display: flex; flex-wrap: wrap; gap: 10px; } .approver-tag { margin-right: 10px; } .dialog-footer { width: 100%; display: flex; justify-content: flex-end; } </style> src/views/procurementManagement/procurementLedger/index.vue
@@ -53,7 +53,9 @@ <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> <el-button type="primary" @click="openForm('add')">æ°å¢å°è´¦</el-button> <el-button type="primary" plain @click="handleImport">å¯¼å ¥</el-button> <el-button type="primary" plain @click="handleImport">å¯¼å ¥</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="danger" plain @@ -94,7 +96,6 @@ prop="availableQuality" /> <el-table-column label="éè´§æ°é" prop="returnQuality" /> <el-table-column label="ç¨ç(%)" prop="taxRate" /> <el-table-column label="å«ç¨åä»·(å )" @@ -119,11 +120,11 @@ show-overflow-tooltip /> <el-table-column label="éå®ååå·" prop="salesContractNo" width="160" width="160" show-overflow-tooltip /> <el-table-column label="ä¾åºååç§°" prop="supplierName" width="160" width="160" show-overflow-tooltip /> <el-table-column label="项ç®åç§°" prop="projectName" @@ -134,9 +135,8 @@ width="100" show-overflow-tooltip> <template #default="scope"> <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" size="small"> <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" size="small"> {{ approvalStatusText[scope.row.approvalStatus] || 'æªç¥ç¶æ' }} </el-tag> </template> @@ -189,12 +189,12 @@ @pagination="paginationChange" /> </div> <FormDialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢éè´å°è´¦é¡µé¢' : 'ç¼è¾éè´å°è´¦é¡µé¢'" :width="'70%'" :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia"> :title="operationType === 'add' ? 'æ°å¢éè´å°è´¦é¡µé¢' : 'ç¼è¾éè´å°è´¦é¡µé¢'" :width="'70%'" :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia"> <el-form :model="form" label-width="140px" label-position="top" @@ -236,7 +236,7 @@ <el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.id" >{{item.supplierName + '---' + item.supplierType}}</el-option> :value="item.id">{{item.supplierName + '---' + item.supplierType}}</el-option> </el-select> </el-form-item> </el-col> @@ -304,38 +304,33 @@ <template #label> <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;"> <span>审æ¹äººéæ©ï¼</span> <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">æ°å¢èç¹</el-button> <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">æ°å¢èç¹</el-button> </div> </template> <div class="approver-nodes-container"> <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item" > <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item"> <div class="approver-node-header"> <span class="approver-node-label">审æ¹èç¹ {{ index + 1 }}</span> <el-button v-if="approverNodes.length > 1" type="danger" size="small" text @click="removeApproverNode(index)" icon="Delete" >å é¤</el-button> <el-button v-if="approverNodes.length > 1" type="danger" size="small" text @click="removeApproverNode(index)" icon="Delete">å é¤</el-button> </div> <el-select v-model="node.userId" placeholder="è¯·éæ©å®¡æ¹äºº" filterable style="width: 100%;" > <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> <el-select v-model="node.userId" placeholder="è¯·éæ©å®¡æ¹äºº" filterable style="width: 100%;"> <el-option v-for="user in userListApprove" :key="user.userId" :label="user.userName" :value="user.userId" /> </el-select> </div> </div> @@ -373,11 +368,10 @@ :value="item.templateName"> <div style="display: flex; justify-content: space-between; align-items: center;"> <span>{{ item.templateName }}</span> <el-icon v-if="item.id" class="delete-icon" @click.stop="handleDeleteTemplate(item)" style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;"> <el-icon v-if="item.id" class="delete-icon" @click.stop="handleDeleteTemplate(item)" style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;"> <Delete /> </el-icon> </div> @@ -493,28 +487,24 @@ </el-form> </FormDialog> <!-- å¯¼å ¥å¼¹çª --> <FormDialog v-model="importUpload.open" :title="importUpload.title" :width="'600px'" @close="importUpload.open = false" @confirm="submitImportFile" @cancel="importUpload.open = false" > <el-upload ref="importUploadRef" :limit="1" accept=".xlsx,.xls" :action="importUpload.url" :headers="importUpload.headers" :before-upload="importUpload.beforeUpload" :on-success="importUpload.onSuccess" :on-error="importUpload.onError" :on-progress="importUpload.onProgress" :on-change="importUpload.onChange" :auto-upload="false" drag > <FormDialog v-model="importUpload.open" :title="importUpload.title" :width="'600px'" @close="importUpload.open = false" @confirm="submitImportFile" @cancel="importUpload.open = false"> <el-upload ref="importUploadRef" :limit="1" accept=".xlsx,.xls" :action="importUpload.url" :headers="importUpload.headers" :before-upload="importUpload.beforeUpload" :on-success="importUpload.onSuccess" :on-error="importUpload.onError" :on-progress="importUpload.onProgress" :on-change="importUpload.onChange" :auto-upload="false" drag> <i class="el-icon-upload"></i> <div class="el-upload__text"> å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em> @@ -522,18 +512,20 @@ <template #tip> <div class="el-upload__tip"> ä» æ¯æ xls/xlsxï¼å¤§å°ä¸è¶ è¿ 10MBã <el-button link type="primary" @click="downloadTemplate">ä¸è½½å¯¼å ¥æ¨¡æ¿</el-button> <el-button link type="primary" @click="downloadTemplate">ä¸è½½å¯¼å ¥æ¨¡æ¿</el-button> </div> </template> </el-upload> </FormDialog> <FormDialog v-model="productFormVisible" :title="productOperationType === 'add' ? 'æ°å¢äº§å' : 'ç¼è¾äº§å'" :width="'40%'" :operation-type="productOperationType" @close="closeProductDia" @confirm="submitProduct" @cancel="closeProductDia"> :title="productOperationType === 'add' ? 'æ°å¢äº§å' : 'ç¼è¾äº§å'" :width="'40%'" :operation-type="productOperationType" @close="closeProductDia" @confirm="submitProduct" @cancel="closeProductDia"> <el-form :model="productForm" label-width="140px" label-position="top" @@ -694,11 +686,9 @@ </el-row> </el-form> </FormDialog> <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" title="éä»¶å表" /> <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" title="éä»¶å表" /> </div> </template> @@ -716,8 +706,10 @@ import { Search, Delete } from "@element-plus/icons-vue"; import { ElMessageBox, ElMessage } from "element-plus"; import { userListNoPage } from "@/api/system/user.js"; import FormDialog from '@/components/Dialog/FormDialog.vue'; import FileListDialog from '@/components/Dialog/FileListDialog.vue'; import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import FileListDialog from "@/components/Dialog/FileListDialog.vue"; import { getSalesLedgerWithProducts, addOrUpdateSalesLedgerProduct, @@ -748,6 +740,7 @@ const productSelectedRows = ref([]); const modelOptions = ref([]); const userList = ref([]); const userListApprove = ref([]); const productOptions = ref([]); const salesContractList = ref([]); const supplierList = ref([]); @@ -770,7 +763,7 @@ const addApproverNode = () => { approverNodes.value.push({ id: nextApproverId++, userId: null }); }; const removeApproverNode = (index) => { const removeApproverNode = index => { approverNodes.value.splice(index, 1); }; @@ -783,12 +776,12 @@ }; // è·å审æ¹ç¶ææ ç¾ç±»å const getApprovalStatusType = (status) => { const getApprovalStatusType = status => { const typeMap = { 1: "info", // å¾ å®¡æ ¸ - ç°è² 2: "warning", // 审æ¹ä¸ - æ©è² 3: "success", // 审æ¹éè¿ - ç»¿è² 4: "danger", // 审æ¹å¤±è´¥ - çº¢è² 1: "info", // å¾ å®¡æ ¸ - ç°è² 2: "warning", // 审æ¹ä¸ - æ©è² 3: "success", // 审æ¹éè¿ - ç»¿è² 4: "danger", // 审æ¹å¤±è´¥ - çº¢è² }; return typeMap[status] || ""; }; @@ -870,7 +863,8 @@ form.value.paymentMethod = matchedTemplate.paymentMethod; } // æ¨¡æ¿æ°æ®ä¸ç产ååæ®µæ¯ productListï¼éè¦è½¬æ¢ä¸º productData productData.value = matchedTemplate.productList || matchedTemplate.productData || []; productData.value = matchedTemplate.productList || matchedTemplate.productData || []; } else { // æªå¹é å°å·²ææ¨¡æ¿ï¼è§ä¸ºæ°æ¨¡æ¿ currentTemplateId.value = null; @@ -1004,7 +998,7 @@ url: import.meta.env.VITE_APP_BASE_API + "/purchase/ledger/import", headers: { Authorization: "Bearer " + getToken() }, isUploading: false, beforeUpload: (file) => { beforeUpload: file => { const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls"); const isLt10M = file.size / 1024 / 1024 < 10; if (!isExcel) { @@ -1053,7 +1047,11 @@ // ä¸è½½å¯¼å ¥æ¨¡æ¿ï¼å¦å端路å¾ä¸åï¼å¯å¨æ¤å¤è°æ´ï¼ const downloadTemplate = () => { proxy.download("/purchase/ledger/exportTemplate", {}, "éè´å°è´¦å¯¼å ¥æ¨¡æ¿.xlsx"); proxy.download( "/purchase/ledger/exportTemplate", {}, "éè´å°è´¦å¯¼å ¥æ¨¡æ¿.xlsx" ); }; const submitImportFile = () => { @@ -1118,8 +1116,8 @@ // æ£æ¥æ¯å¦æäº§åæ°æ® if (!productData.value || productData.value.length === 0) { ElMessage({ message: 'è¯·å æ·»å 产åä¿¡æ¯', type: 'warning', message: "è¯·å æ·»å 产åä¿¡æ¯", type: "warning", }); return; } @@ -1130,7 +1128,7 @@ .filter(node => node.userId) .map(node => node.userId) .join(","); let params = { productData: proxy.HaveJson(productData.value), supplierId: form.value.supplierId, @@ -1140,7 +1138,12 @@ approveUserIds: approveUserIds, templateName: templateName.value.trim(), }; console.log("template params ===>", params, "currentTemplateId:", currentTemplateId.value); console.log( "template params ===>", params, "currentTemplateId:", currentTemplateId.value ); // 妿 currentTemplateId æå¼ï¼è¯´æå½åæ¯âç¼è¾å·²ææ¨¡æ¿â â è°ç¨æ´æ°æ¥å£ // å¦åä¸ºâæ°å»ºæ¨¡æ¿â â è°ç¨æ°å¢æ¥å£ @@ -1279,7 +1282,7 @@ return; } } await getTemplateList(); operationType.value = type; form.value = {}; @@ -1298,7 +1301,9 @@ getSalesNo(), getOptions(), ]); approveUserList({ approveType: 5 }).then(res => { userListApprove.value = res.data; }); userList.value = userRes.data || []; salesContractList.value = salesRes || []; // ä¾åºåè¿æ»¤åºisWhite=0 çæ°æ® @@ -1334,7 +1339,7 @@ const approverIds = purchaseRes.approveUserIds.split(","); approverNodes.value = approverIds.map((id, index) => ({ id: index + 1, userId: Number(id) userId: Number(id), })); nextApproverId = approverIds.length + 1; } @@ -1412,8 +1417,10 @@ proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼"); return; } const approveUserIds = approverNodes.value.map(node => node.userId).join(","); const approveUserIds = approverNodes.value .map(node => node.userId) .join(","); if (productData.value.length > 0) { // æ°å¢æ¶ï¼éè¦ä»æ¯ä¸ªäº§å对象ä¸å é¤ id åæ®µ let processedProductData = productData.value; @@ -1470,17 +1477,17 @@ productForm.value = {}; proxy.resetForm("productFormRef"); productFormVisible.value = true; // å è·å产åé项ï¼ç¡®ä¿æ°æ®å è½½å®æ await getProductOptions(); // çå¾ DOM æ´æ° await nextTick(); if (type === "edit") { // å¤å¶è¡æ°æ® productForm.value = { ...row }; // 妿æ¯ä»æ¨¡æ¿å è½½çæ°æ®ï¼å¯è½æ²¡æ productId å productModelId // éè¦æ ¹æ® productCategory å specificationModel æ¥æ¥æ¾å¯¹åºç ID if (!productForm.value.productId && productForm.value.productCategory) { @@ -1491,25 +1498,34 @@ return nodes[i].value; } if (nodes[i].children && nodes[i].children.length > 0) { const found = findProductIdByCategory(nodes[i].children, categoryName); const found = findProductIdByCategory( nodes[i].children, categoryName ); if (found) return found; } } return null; }; const productId = findProductIdByCategory(productOptions.value, productForm.value.productCategory); const productId = findProductIdByCategory( productOptions.value, productForm.value.productCategory ); if (productId) { productForm.value.productId = productId; // è·ååå·å表并çå¾ å®æ const modelRes = await modelList({ id: productId }); modelOptions.value = modelRes; // çå¾ DOM æ´æ° await nextTick(); // æ ¹æ® specificationModel æ¥æ¾ productModelId if (productForm.value.specificationModel && modelOptions.value.length > 0) { if ( productForm.value.specificationModel && modelOptions.value.length > 0 ) { const modelItem = modelOptions.value.find( item => item.model === productForm.value.specificationModel ); @@ -1523,15 +1539,15 @@ } else if (productForm.value.productId) { // 妿æ productIdï¼æ£å¸¸å è½½åå·å表 await getModels(productForm.value.productId); // çå¾ DOM æ´æ° await nextTick(); if (productForm.value.productModelId) { getProductModel(productForm.value.productModelId); } } // æååçå¾ ä¸æ¬¡ DOM æ´æ°ï¼ç¡®ä¿æææ°æ®é½å·²è®¾ç½® await nextTick(); } @@ -1654,11 +1670,9 @@ delProduct(ids).then(res => { proxy.$modal.msgSuccess("å 餿å"); closeProductDia(); getPurchaseById({ id: currentId.value, type: 2 }).then( res => { productData.value = res.productData; } ); getPurchaseById({ id: currentId.value, type: 2 }).then(res => { productData.value = res.productData; }); }); }) .catch(() => { @@ -1866,12 +1880,12 @@ }; // å 餿¨¡æ¿ const handleDeleteTemplate = async (item) => { const handleDeleteTemplate = async item => { if (!item.id) { proxy.$modal.msgWarning("æ æ³å é¤è¯¥æ¨¡æ¿"); return; } try { await ElMessageBox.confirm( `ç¡®å®è¦å 餿¨¡æ¿"${item.templateName}"åï¼`, @@ -1882,7 +1896,7 @@ type: "warning", } ); const res = await delPurchaseTemplate([item.id]); if (res && res.code === 200) { ElMessage({ @@ -1935,7 +1949,7 @@ display: flex; align-items: center; } // 审æ¹äººèç¹å®¹å¨æ ·å¼ .approver-nodes-container { display: flex; @@ -1946,7 +1960,7 @@ border-radius: 4px; border: 1px solid #e4e7ed; } .approver-node-item { flex: 0 0 calc(33.333% - 12px); min-width: 200px; @@ -1955,38 +1969,38 @@ border-radius: 4px; border: 1px solid #dcdfe6; transition: all 0.3s; &:hover { border-color: #409eff; box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1); } } .approver-node-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .approver-node-label { font-size: 13px; font-weight: 500; color: #606266; } @media (max-width: 1200px) { .approver-node-item { flex: 0 0 calc(50% - 8px); } } @media (max-width: 768px) { .approver-node-item { flex: 0 0 100%; } } // å é¤å¾æ æ ·å¼ .delete-icon { transition: all 0.3s; src/views/salesManagement/salesLedger/index.vue
ÎļþÌ«´ó src/views/salesManagement/salesQuotation/index.vue
@@ -2,350 +2,444 @@ <div class="app-container"> <el-card class="box-card"> <!-- æç´¢åºå --> <el-row :gutter="20" class="search-row"> <el-row :gutter="20" class="search-row"> <el-col :span="8"> <el-input v-model="searchForm.quotationNo" placeholder="请è¾å ¥æ¥ä»·åå·" clearable @keyup.enter="handleSearch" > <el-input v-model="searchForm.quotationNo" placeholder="请è¾å ¥æ¥ä»·åå·" clearable @keyup.enter="handleSearch"> <template #prefix> <el-icon><Search /></el-icon> <el-icon> <Search /> </el-icon> </template> </el-input> </el-col> <el-col :span="8"> <el-select v-model="searchForm.customer" placeholder="è¯·éæ©å®¢æ·" clearable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> {{ <el-select v-model="searchForm.customer" placeholder="è¯·éæ©å®¢æ·" clearable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> {{ item.customerName + "ââ" + item.taxpayerIdentificationNumber }} </el-option> </el-option> </el-select> </el-col> <!-- <el-col :span="6">--> <!-- <el-select v-model="searchForm.status" placeholder="è¯·éæ©æ¥ä»·ç¶æ" clearable>--> <!-- <el-option label="è稿" value="è稿"></el-option>--> <!-- <el-option label="å·²åé" value="å·²åé"></el-option>--> <!-- <el-option label="客æ·ç¡®è®¤" value="客æ·ç¡®è®¤"></el-option>--> <!-- <el-option label="å·²è¿æ" value="å·²è¿æ"></el-option>--> <!-- </el-select>--> <!-- </el-col>--> <!-- <el-col :span="6">--> <!-- <el-select v-model="searchForm.status" placeholder="è¯·éæ©æ¥ä»·ç¶æ" clearable>--> <!-- <el-option label="è稿" value="è稿"></el-option>--> <!-- <el-option label="å·²åé" value="å·²åé"></el-option>--> <!-- <el-option label="客æ·ç¡®è®¤" value="客æ·ç¡®è®¤"></el-option>--> <!-- <el-option label="å·²è¿æ" value="å·²è¿æ"></el-option>--> <!-- </el-select>--> <!-- </el-col>--> <el-col :span="8"> <el-button type="primary" @click="handleSearch">æç´¢</el-button> <el-button type="primary" @click="handleSearch">æç´¢</el-button> <el-button @click="resetSearch">éç½®</el-button> <el-button style="float: right;" type="primary" @click="handleAdd"> <el-button style="float: right;" type="primary" @click="handleAdd"> æ°å¢æ¥ä»· </el-button> </el-col> </el-row> <!-- æ¥ä»·å表 --> <el-table :data="filteredList" style="width: 100%" v-loading="loading" border stripe height="calc(100vh - 22em)" > <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column prop="quotationNo" label="æ¥ä»·åå·" /> <el-table-column prop="customer" label="客æ·åç§°" /> <el-table-column prop="salesperson" label="ä¸å¡å" width="100" /> <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" width="120" /> <el-table-column prop="validDate" label="æææè³" width="120" /> <el-table-column prop="status" label="审æ¹ç¶æ" width="120" align="center"> <el-table :data="filteredList" style="width: 100%" v-loading="loading" border stripe height="calc(100vh - 22em)"> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column prop="quotationNo" label="æ¥ä»·åå·" /> <el-table-column prop="customer" label="客æ·åç§°" /> <el-table-column prop="salesperson" label="ä¸å¡å" width="100" /> <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" width="120" /> <el-table-column prop="validDate" label="æææè³" width="120" /> <el-table-column prop="status" label="审æ¹ç¶æ" width="120" align="center"> <template #default="{ row }"> <el-tag :type="getStatusType(row.status)" disable-transitions> <el-tag :type="getStatusType(row.status)" disable-transitions> {{ row.status || '--' }} </el-tag> </template> </el-table-column> <el-table-column prop="totalAmount" label="æ¥ä»·éé¢" width="120"> <el-table-column prop="totalAmount" label="æ¥ä»·éé¢" width="120"> <template #default="scope"> Â¥{{ scope.row.totalAmount.toFixed(2) }} </template> </el-table-column> <el-table-column label="æä½" width="200" fixed="right" align="center"> <el-table-column label="æä½" width="200" fixed="right" align="center"> <template #default="scope"> <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['å¾ å®¡æ¹','æç»'].includes(scope.row.status)">ç¼è¾</el-button> <el-button link type="primary" @click="handleView(scope.row)" style="color: #67C23A">æ¥ç</el-button> <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['å¾ å®¡æ¹','æç»'].includes(scope.row.status)">ç¼è¾</el-button> <el-button link type="primary" @click="handleView(scope.row)" style="color: #67C23A">æ¥ç</el-button> <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> </template> </el-table-column> </el-table> <!-- å页 --> <pagination :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" :page="pagination.currentPage" :limit="pagination.pageSize" @pagination="handleCurrentChange" /> <pagination :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" :page="pagination.currentPage" :limit="pagination.pageSize" @pagination="handleCurrentChange" /> </el-card> <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false"> <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false"> <div class="quotation-form-container"> <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form"> <!-- åºæ¬ä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><Document /></el-icon> <span class="card-title">åºæ¬ä¿¡æ¯</span> </div> </template> <div class="form-content"> <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="客æ·åç§°" prop="customer"> <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%" @change="handleCustomerChange" clearable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> {{ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form"> <!-- åºæ¬ä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"> <Document /> </el-icon> <span class="card-title">åºæ¬ä¿¡æ¯</span> </div> </template> <div class="form-content"> <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="客æ·åç§°" prop="customer"> <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%" @change="handleCustomerChange" clearable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> {{ item.customerName + "ââ" + item.taxpayerIdentificationNumber }} </el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸å¡å" prop="salesperson"> <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%" clearable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="æ¥ä»·æ¥æ" prop="quotationDate"> <el-date-picker v-model="form.quotationDate" type="date" placeholder="éæ©æ¥ä»·æ¥æ" style="width: 100%" format="YYYY-MM-DD" value-format="YYYY-MM-DD" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æææè³" prop="validDate"> <el-date-picker v-model="form.validDate" type="date" placeholder="éæ©æææ" style="width: 100%" format="YYYY-MM-DD" value-format="YYYY-MM-DD" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> <el-input v-model="form.paymentMethod" placeholder="请è¾å ¥ä»æ¬¾æ¹å¼" clearable /> </el-form-item> </el-col> </el-row> </div> </el-card> <!-- 审æ¹äººä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><UserFilled /></el-icon> <span class="card-title">审æ¹äººéæ©</span> <el-button type="primary" size="small" @click="addApproverNode" class="header-btn"> <el-icon><Plus /></el-icon> æ°å¢èç¹ </el-button> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸å¡å" prop="salesperson"> <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%" clearable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="æ¥ä»·æ¥æ" prop="quotationDate"> <el-date-picker v-model="form.quotationDate" type="date" placeholder="éæ©æ¥ä»·æ¥æ" style="width: 100%" format="YYYY-MM-DD" value-format="YYYY-MM-DD" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æææè³" prop="validDate"> <el-date-picker v-model="form.validDate" type="date" placeholder="éæ©æææ" style="width: 100%" format="YYYY-MM-DD" value-format="YYYY-MM-DD" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> <el-input v-model="form.paymentMethod" placeholder="请è¾å ¥ä»æ¬¾æ¹å¼" clearable /> </el-form-item> </el-col> </el-row> </div> </template> <div class="form-content"> <el-row> <el-col :span="24"> <el-form-item> <div class="approver-nodes-container"> <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item" > <div class="approver-node-label"> <span class="node-step">{{ index + 1 }}</span> <span class="node-text">审æ¹äºº</span> <el-icon class="arrow-icon"><ArrowRight /></el-icon> </el-card> <!-- 审æ¹äººä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"> <UserFilled /> </el-icon> <span class="card-title">审æ¹äººéæ©</span> <el-button type="primary" size="small" @click="addApproverNode" class="header-btn"> <el-icon> <Plus /> </el-icon> æ°å¢èç¹ </el-button> </div> </template> <div class="form-content"> <el-row> <el-col :span="24"> <el-form-item> <div class="approver-nodes-container"> <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item"> <div class="approver-node-label"> <span class="node-step">{{ index + 1 }}</span> <span class="node-text">审æ¹äºº</span> <el-icon class="arrow-icon"> <ArrowRight /> </el-icon> </div> <el-select v-model="node.userId" placeholder="éæ©äººå" class="approver-select" clearable> <el-option v-for="user in userListApprove" :key="user.userId" :label="user.userName" :value="user.userId" /> </el-select> <el-button type="danger" size="small" :icon="Delete" @click="removeApproverNode(index)" v-if="approverNodes.length > 1" class="remove-btn">å é¤</el-button> </div> <el-select v-model="node.userId" placeholder="éæ©äººå" class="approver-select" clearable > <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> <el-button type="danger" size="small" :icon="Delete" @click="removeApproverNode(index)" v-if="approverNodes.length > 1" class="remove-btn" >å é¤</el-button> </div> </div> </el-form-item> </el-col> </el-row> </div> </el-card> <!-- 产åä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><Box /></el-icon> <span class="card-title">产åä¿¡æ¯</span> <el-button type="primary" size="small" @click="addProduct" class="header-btn"> <el-icon><Plus /></el-icon> æ·»å 产å </el-button> </el-form-item> </el-col> </el-row> </div> </template> <div class="form-content"> <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0"> <el-table-column prop="product" label="产ååç§°" width="200"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item"> <el-tree-select v-model="scope.row.productId" placeholder="è¯·éæ©" clearable check-strictly @change="getModels($event, scope.row)" :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-form-item> </template> </el-table-column> <el-table-column prop="specification" label="è§æ ¼åå·" width="200"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item"> <el-select v-model="scope.row.specificationId" placeholder="è¯·éæ©" clearable @change="getProductModel($event, scope.row)" style="width: 100%" > <el-option v-for="item in scope.row.modelOptions || []" :key="item.id" :label="item.model" :value="item.id" /> </el-select> </el-form-item> </template> </el-table-column> <el-table-column prop="unit" label="åä½"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item"> <el-input v-model="scope.row.unit" placeholder="åä½" clearable/> </el-form-item> </template> </el-table-column> <el-table-column prop="unitPrice" label="åä»·"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item"> <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </template> </el-table-column> <el-table-column label="æä½" width="80" align="center"> <template #default="scope"> <el-button link type="danger" @click="removeProduct(scope.$index)">å é¤</el-button> </template> </el-table-column> </el-table> <el-empty v-else description="ææ äº§åï¼è¯·ç¹å»æ·»å 产å" :image-size="80" /> </div> </el-card> <!-- 夿³¨ä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><EditPen /></el-icon> <span class="card-title">夿³¨ä¿¡æ¯</span> </el-card> <!-- 产åä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"> <Box /> </el-icon> <span class="card-title">产åä¿¡æ¯</span> <el-button type="primary" size="small" @click="addProduct" class="header-btn"> <el-icon> <Plus /> </el-icon> æ·»å 产å </el-button> </div> </template> <div class="form-content"> <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0"> <el-table-column prop="product" label="产ååç§°" width="200"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item"> <el-tree-select v-model="scope.row.productId" placeholder="è¯·éæ©" clearable check-strictly @change="getModels($event, scope.row)" :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-form-item> </template> </el-table-column> <el-table-column prop="specification" label="è§æ ¼åå·" width="200"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item"> <el-select v-model="scope.row.specificationId" placeholder="è¯·éæ©" clearable @change="getProductModel($event, scope.row)" style="width: 100%"> <el-option v-for="item in scope.row.modelOptions || []" :key="item.id" :label="item.model" :value="item.id" /> </el-select> </el-form-item> </template> </el-table-column> <el-table-column prop="unit" label="åä½"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item"> <el-input v-model="scope.row.unit" placeholder="åä½" clearable /> </el-form-item> </template> </el-table-column> <el-table-column prop="unitPrice" label="åä»·"> <template #default="scope"> <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item"> <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" /> </el-form-item> </template> </el-table-column> <el-table-column label="æä½" width="80" align="center"> <template #default="scope"> <el-button link type="danger" @click="removeProduct(scope.$index)">å é¤</el-button> </template> </el-table-column> </el-table> <el-empty v-else description="ææ äº§åï¼è¯·ç¹å»æ·»å 产å" :image-size="80" /> </div> </template> <div class="form-content"> <el-form-item label="夿³¨" prop="remark"> <el-input type="textarea" v-model="form.remark" placeholder="请è¾å ¥å¤æ³¨ä¿¡æ¯ï¼éå¡«ï¼" :rows="4" maxlength="500" show-word-limit ></el-input> </el-form-item> </div> </el-card> </el-form> </el-card> <!-- 夿³¨ä¿¡æ¯ --> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"> <EditPen /> </el-icon> <span class="card-title">夿³¨ä¿¡æ¯</span> </div> </template> <div class="form-content"> <el-form-item label="夿³¨" prop="remark"> <el-input type="textarea" v-model="form.remark" placeholder="请è¾å ¥å¤æ³¨ä¿¡æ¯ï¼éå¡«ï¼" :rows="4" maxlength="500" show-word-limit></el-input> </el-form-item> </div> </el-card> </el-form> </div> </FormDialog> <!-- æ¥ç详æ å¯¹è¯æ¡ --> <el-dialog v-model="viewDialogVisible" title="æ¥ä»·è¯¦æ " width="800px"> <el-descriptions :column="2" border> <el-dialog v-model="viewDialogVisible" title="æ¥ä»·è¯¦æ " width="800px"> <el-descriptions :column="2" border> <el-descriptions-item label="æ¥ä»·åå·">{{ currentQuotation.quotationNo }}</el-descriptions-item> <el-descriptions-item label="客æ·åç§°">{{ currentQuotation.customer }}</el-descriptions-item> <el-descriptions-item label="ä¸å¡å">{{ currentQuotation.salesperson }}</el-descriptions-item> <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> <el-descriptions-item label="æææè³">{{ currentQuotation.validDate }}</el-descriptions-item> <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentQuotation.paymentMethod }}</el-descriptions-item> <!-- <el-descriptions-item label="æ¥ä»·ç¶æ">--> <!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>--> <!-- </el-descriptions-item>--> <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> <!-- <el-descriptions-item label="æ¥ä»·ç¶æ">--> <!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>--> <!-- </el-descriptions-item>--> <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span> </el-descriptions-item> </el-descriptions> <div style="margin: 20px 0;"> <h4>产åæç»</h4> <el-table :data="currentQuotation.products" border style="width: 100%"> <el-table-column prop="product" label="产ååç§°" /> <el-table-column prop="specification" label="è§æ ¼åå·" /> <el-table-column prop="unit" label="åä½" /> <el-table-column prop="unitPrice" label="åä»·"> <el-table :data="currentQuotation.products" border style="width: 100%"> <el-table-column prop="product" label="产ååç§°" /> <el-table-column prop="specification" label="è§æ ¼åå·" /> <el-table-column prop="unit" label="åä½" /> <el-table-column prop="unitPrice" label="åä»·"> <template #default="scope"> Â¥{{ scope.row.unitPrice.toFixed(2) }} </template> </el-table-column> </el-table> </div> <div v-if="currentQuotation.remark" style="margin-top: 20px;"> <div v-if="currentQuotation.remark" style="margin-top: 20px;"> <h4>夿³¨</h4> <p>{{ currentQuotation.remark }}</p> </div> @@ -354,742 +448,782 @@ </template> <script setup> import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete } from '@element-plus/icons-vue' import Pagination from '@/components/PIMTable/Pagination.vue' import FormDialog from '@/components/Dialog/FormDialog.vue' import {getQuotationList,addQuotation,updateQuotation,deleteQuotation} from '@/api/salesManagement/salesQuotation.js' import {userListNoPage} from "@/api/system/user.js"; import {customerList} from "@/api/salesManagement/salesLedger.js"; import {modelList, productTreeList} from "@/api/basicData/product.js"; import { ref, reactive, computed, onMounted, markRaw, shallowRef } from "vue"; import { ElMessage, ElMessageBox } from "element-plus"; import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete, } from "@element-plus/icons-vue"; import Pagination from "@/components/PIMTable/Pagination.vue"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import { getQuotationList, addQuotation, updateQuotation, deleteQuotation, } from "@/api/salesManagement/salesQuotation.js"; import { userListNoPage } from "@/api/system/user.js"; import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js"; // ååºå¼æ°æ® const loading = ref(false) const searchForm = reactive({ quotationNo: '', customer: '', status: '' }) import { customerList } from "@/api/salesManagement/salesLedger.js"; import { modelList, productTreeList } from "@/api/basicData/product.js"; const quotationList = ref([]) const productOptions = ref([]); const modelOptions = ref([]); const pagination = reactive({ total: 3, currentPage: 1, pageSize: 100 }) // ååºå¼æ°æ® const loading = ref(false); const searchForm = reactive({ quotationNo: "", customer: "", status: "", }); const dialogVisible = ref(false) const viewDialogVisible = ref(false) const dialogTitle = ref('æ°å¢æ¥ä»·') const form = reactive({ quotationNo: '', customer: '', salesperson: '', quotationDate: '', validDate: '', paymentMethod: '', status: 'è稿', remark: '', products: [], subtotal: 0, freight: 0, otherFee: 0, discountRate: 0, discountAmount: 0, totalAmount: 0 }) const quotationList = ref([]); const productOptions = ref([]); const modelOptions = ref([]); const pagination = reactive({ total: 3, currentPage: 1, pageSize: 100, }); const baseRules = { customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], quotationDate: [{ required: true, message: 'è¯·éæ©æ¥ä»·æ¥æ', trigger: 'change' }], validDate: [{ required: true, message: 'è¯·éæ©æææ', trigger: 'change' }], paymentMethod: [{ required: true, message: '请è¾å ¥ä»æ¬¾æ¹å¼', trigger: 'blur' }] } const dialogVisible = ref(false); const viewDialogVisible = ref(false); const dialogTitle = ref("æ°å¢æ¥ä»·"); const form = reactive({ quotationNo: "", customer: "", salesperson: "", quotationDate: "", validDate: "", paymentMethod: "", status: "è稿", remark: "", products: [], subtotal: 0, freight: 0, otherFee: 0, discountRate: 0, discountAmount: 0, totalAmount: 0, }); const productRowRules = { productId: [{ required: true, message: 'è¯·éæ©äº§ååç§°', trigger: 'change' }], specificationId: [{ required: true, message: 'è¯·éæ©è§æ ¼åå·', trigger: 'change' }], unit: [{ required: true, message: '请填ååä½', trigger: 'blur' }], unitPrice: [{ required: true, message: '请填ååä»·', trigger: 'change' }] } const rules = computed(() => { const r = { ...baseRules } ;(form.products || []).forEach((_, i) => { r[`products.${i}.productId`] = productRowRules.productId r[`products.${i}.specificationId`] = productRowRules.specificationId r[`products.${i}.unit`] = productRowRules.unit r[`products.${i}.unitPrice`] = productRowRules.unitPrice }) return r }) const userList = ref([]); const customerOption = ref([]); const baseRules = { customer: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], salesperson: [{ required: true, message: "è¯·éæ©ä¸å¡å", trigger: "change" }], quotationDate: [ { required: true, message: "è¯·éæ©æ¥ä»·æ¥æ", trigger: "change" }, ], validDate: [{ required: true, message: "è¯·éæ©æææ", trigger: "change" }], paymentMethod: [ { required: true, message: "请è¾å ¥ä»æ¬¾æ¹å¼", trigger: "blur" }, ], }; // 审æ¹äººèç¹ç¸å ³ const approverNodes = ref([ { id: 1, userId: null } ]) let nextApproverId = 2 const productRowRules = { productId: [{ required: true, message: "è¯·éæ©äº§ååç§°", trigger: "change" }], specificationId: [ { required: true, message: "è¯·éæ©è§æ ¼åå·", trigger: "change" }, ], unit: [{ required: true, message: "请填ååä½", trigger: "blur" }], unitPrice: [{ required: true, message: "请填ååä»·", trigger: "change" }], }; const rules = computed(() => { const r = { ...baseRules }; (form.products || []).forEach((_, i) => { r[`products.${i}.productId`] = productRowRules.productId; r[`products.${i}.specificationId`] = productRowRules.specificationId; r[`products.${i}.unit`] = productRowRules.unit; r[`products.${i}.unitPrice`] = productRowRules.unitPrice; }); return r; }); const userList = ref([]); const userListApprove = ref([]); const customerOption = ref([]); const isEdit = ref(false) const editId = ref(null) const currentQuotation = ref({}) const formRef = ref() // 审æ¹äººèç¹ç¸å ³ const approverNodes = ref([{ id: 1, userId: null }]); let nextApproverId = 2; // æ·»å 审æ¹äººèç¹ function addApproverNode() { approverNodes.value.push({ id: nextApproverId++, userId: null }) } const isEdit = ref(false); const editId = ref(null); const currentQuotation = ref({}); const formRef = ref(); // å é¤å®¡æ¹äººèç¹ function removeApproverNode(index) { approverNodes.value.splice(index, 1) } // 计ç®å±æ§ const filteredList = computed(() => { let list = quotationList.value return list }) // æ¹æ³ const getStatusType = (status) => { const statusMap = { 'å¾ å®¡æ¹': 'info', 'å®¡æ ¸ä¸': 'primary', 'éè¿': 'success', 'æç»': 'danger' // æ·»å 审æ¹äººèç¹ function addApproverNode() { approverNodes.value.push({ id: nextApproverId++, userId: null }); } return statusMap[status] || 'info' } const resetSearch = () => { searchForm.quotationNo = '' searchForm.customer = '' searchForm.status = '' // éç½®å°ç¬¬ä¸é¡µå¹¶éæ°æ¥è¯¢ pagination.currentPage = 1 handleSearch() } const handleAdd = async () => { dialogTitle.value = 'æ°å¢æ¥ä»·' isEdit.value = false resetForm() // é置审æ¹äººèç¹ approverNodes.value = [{ id: 1, userId: null }] nextApproverId = 2 dialogVisible.value = true let userLists = await userListNoPage(); // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ userList.value = (userLists.data || []).map(item => ({ userId: item.userId, nickName: item.nickName || '', userName: item.userName || '' })); getProductOptions(); customerList().then((res) => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ id: item.id, customerName: item.customerName || '', taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' })) }); } const getProductOptions = () => { // è¿å Promiseï¼ä¾¿äºç¼è¾æ¶ await ç¡®ä¿è½åæ¾ return productTreeList().then((res) => { productOptions.value = convertIdToValue(res); return productOptions.value }); }; 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; }); } // æ ¹æ®åç§°åæ¥èç¹ idï¼ä¾¿äºä» ååç§°æ¶çåæ¾ function findNodeIdByLabel(nodes, label) { if (!label) return null; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (node.label === label) return node.value; if (node.children && node.children.length > 0) { const found = findNodeIdByLabel(node.children, label); if (found !== null && found !== undefined) return found; } } return null; } const getModels = (value, row) => { if (!row) return; // å¦ææ¸ ç©ºéæ©ï¼åæ¸ ç©ºç¸å ³å段 if (!value) { row.productId = ''; row.product = ''; row.modelOptions = []; row.specificationId = ''; row.specification = ''; row.unit = ''; return; } // æ´æ° productIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ row.productId = value; // æ¾å°å¯¹åºç label å¹¶èµå¼ç» row.product const label = findNodeById(productOptions.value, value); if (label) { row.product = label; } // è·åè§æ ¼åå·å表ï¼è®¾ç½®å°å½åè¡ç modelOptions modelList({ id: value }).then((res) => { row.modelOptions = res || []; }); }; const getProductModel = (value, row) => { if (!row) return; // å¦ææ¸ ç©ºéæ©ï¼åæ¸ ç©ºç¸å ³å段 if (!value) { row.specificationId = ''; row.specification = ''; row.unit = ''; return; } // æ´æ° specificationIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ row.specificationId = value; const modelOptions = row.modelOptions || []; const index = modelOptions.findIndex((item) => item.id === value); if (index !== -1) { row.specification = modelOptions[index].model; row.unit = modelOptions[index].unit; } else { row.specification = ''; row.unit = ''; } }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { return nodes[i].label; // æ¾å°èç¹ï¼è¿å label } if (nodes[i].children && nodes[i].children.length > 0) { const foundLabel = findNodeById(nodes[i].children, productId); if (foundLabel) { return foundLabel; // å¨åèç¹ä¸æ¾å°ï¼è¿å label } } } return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull }; const handleView = (row) => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ currentQuotation.value = { quotationNo: row.quotationNo || '', customer: row.customer || '', salesperson: row.salesperson || '', quotationDate: row.quotationDate || '', validDate: row.validDate || '', paymentMethod: row.paymentMethod || '', status: row.status || '', remark: row.remark || '', products: row.products ? row.products.map(product => ({ productId: product.productId || '', product: product.product || product.productName || '', specificationId: product.specificationId || '', specification: product.specification || '', quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0, amount: product.amount || 0 })) : [], totalAmount: row.totalAmount || 0 // å é¤å®¡æ¹äººèç¹ function removeApproverNode(index) { approverNodes.value.splice(index, 1); } viewDialogVisible.value = true } const handleEdit = async (row) => { dialogTitle.value = 'ç¼è¾æ¥ä»·' isEdit.value = true editId.value = row.id form.id = row.id || form.id || null // å å è½½äº§åæ æ°æ®ï¼å¦å el-tree-select æ æ³åæ¾äº§ååç§° await getProductOptions() // 计ç®å±æ§ const filteredList = computed(() => { let list = quotationList.value; return list; }); // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ form.quotationNo = row.quotationNo || '' form.customer = row.customer || '' form.salesperson = row.salesperson || '' form.quotationDate = row.quotationDate || '' form.validDate = row.validDate || '' form.paymentMethod = row.paymentMethod || '' form.status = row.status || 'è稿' form.remark = row.remark || '' form.products = row.products ? await Promise.all(row.products.map(async (product) => { const productName = product.product || product.productName || '' // ä¼å ç¨ productIdï¼å¦æåªæåç§°ï¼å°è¯åæ¥ id 以便æ éæ©å¨åæ¾ const resolvedProductId = product.productId ? Number(product.productId) : findNodeIdByLabel(productOptions.value, productName) || '' // 妿æäº§åIDï¼å 载对åºçè§æ ¼åå·å表 let modelOptions = []; let resolvedSpecificationId = product.specificationId || ''; if (resolvedProductId) { try { const res = await modelList({ id: resolvedProductId }); modelOptions = res || []; // 妿è¿åçæ°æ®æ²¡æ specificationIdï¼ä½æ specification åç§°ï¼æ ¹æ®åç§°æ¥æ¾ ID if (!resolvedSpecificationId && product.specification) { const foundModel = modelOptions.find(item => item.model === product.specification); if (foundModel) { resolvedSpecificationId = foundModel.id; } } } catch (error) { console.error('å è½½è§æ ¼åå·å¤±è´¥:', error); } } return { productId: resolvedProductId, product: productName, specificationId: resolvedSpecificationId, specification: product.specification || '', quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0, amount: product.amount || 0, modelOptions: modelOptions // 为æ¯è¡æ·»å ç¬ç«çè§æ ¼åå·å表 } })) : [] form.subtotal = row.subtotal || 0 form.freight = row.freight || 0 form.otherFee = row.otherFee || 0 form.discountRate = row.discountRate || 0 form.discountAmount = row.discountAmount || 0 form.totalAmount = row.totalAmount || 0 // 忾审æ¹äºº if (row.approveUserIds) { const userIds = row.approveUserIds.split(',') approverNodes.value = userIds.map((userId, idx) => ({ id: idx + 1, userId: parseInt(userId.trim()) })) nextApproverId = userIds.length + 1 } else { approverNodes.value = [{ id: 1, userId: null }] nextApproverId = 2 } // å è½½ç¨æ·å表 let userLists = await userListNoPage(); userList.value = (userLists.data || []).map(item => ({ userId: item.userId, nickName: item.nickName || '', userName: item.userName || '' })); dialogVisible.value = true } // æ¹æ³ const getStatusType = status => { const statusMap = { å¾ å®¡æ¹: "info", å®¡æ ¸ä¸: "primary", éè¿: "success", æç»: "danger", }; return statusMap[status] || "info"; }; const resetSearch = () => { searchForm.quotationNo = ""; searchForm.customer = ""; searchForm.status = ""; // éç½®å°ç¬¬ä¸é¡µå¹¶éæ°æ¥è¯¢ pagination.currentPage = 1; handleSearch(); }; const handleDelete = (row) => { ElMessageBox.confirm('确认å é¤è¯¥æ¥ä»·ååï¼', 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }).then(() => { const index = quotationList.value.findIndex(item => item.id === row.id) if (index > -1) { deleteQuotation(row.id).then(res=>{ // console.log(res) if(res.code===200){ ElMessage.success('å 餿å') handleSearch() } }) // quotationList.value.splice(index, 1) // pagination.total-- // ElMessage.success('å 餿å') } }) } const resetForm = () => { form.customer = '' form.salesperson = '' form.quotationDate = '' form.validDate = '' form.paymentMethod = '' form.status = 'è稿' form.remark = '' form.products = [] form.subtotal = 0 form.freight = 0 form.otherFee = 0 form.discountRate = 0 form.discountAmount = 0 form.totalAmount = 0 } const addProduct = () => { form.products.push({ productId: '', product: '', productName: '', specificationId: '', specification: '', quantity: 1, unit: '', unitPrice: 0, amount: 0, modelOptions: [] // 为æ¯è¡æ·»å ç¬ç«çè§æ ¼åå·å表 }) } const removeProduct = (index) => { form.products.splice(index, 1) calculateSubtotal() } const calculateAmount = (product) => { product.amount = product.quantity * product.unitPrice calculateSubtotal() } const calculateSubtotal = () => { form.subtotal = form.products.reduce((sum, product) => sum + product.amount, 0) calculateTotal() } const calculateTotal = () => { form.discountAmount = form.subtotal * (form.discountRate / 100) form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount } const handleCustomerChange = () => { // å¯ä»¥æ ¹æ®å®¢æ·ä¿¡æ¯èªå¨å¡«å ä¸äºé»è®¤å¼ } const handleSubmit = () => { formRef.value.validate((valid) => { if (valid) { if (form.products.length === 0) { ElMessage.warning('请è³å°æ·»å ä¸ä¸ªäº§å') return } // 审æ¹äººå¿ å¡«æ ¡éª const hasEmptyApprover = approverNodes.value.some(node => !node.userId) if (hasEmptyApprover) { ElMessage.error('请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼') return } // æ¶éææèç¹ç审æ¹äººid form.approveUserIds = approverNodes.value.map(node => node.userId).join(',') // è®¡ç®ææäº§åçåä»·æ»å form.totalAmount = form.products.reduce((sum, product) => { const price = Number(product.unitPrice) || 0 return sum + price }, 0) if (isEdit.value) { // ç¼è¾ const index = quotationList.value.findIndex(item => item.id === editId.value) if (index > -1) { updateQuotation(form).then(res=>{ // console.log(res) if(res.code===200){ ElMessage.success('ç¼è¾æå') dialogVisible.value = false handleSearch() } }) } } else { // æ°å¢ addQuotation(form).then(res=>{ if(res.code===200){ ElMessage.success('æ°å¢æå') dialogVisible.value = false handleSearch() } }) } } }) } const handleCurrentChange = (val) => { pagination.currentPage = val.page pagination.pageSize = val.limit // å页ååæ¶éæ°æ¥è¯¢å表 handleSearch() } const handleSearch = ()=>{ const params = { // å端å页忰ï¼current / size current: pagination.currentPage, size: pagination.pageSize, ...searchForm } getQuotationList(params).then(res=>{ // console.log(res) if(res.code===200){ // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æå ¶ä»å¯¹è±¡æ¾å ¥ååºå¼å¯¹è±¡ quotationList.value = (res.data.records || []).map(item => ({ const handleAdd = async () => { dialogTitle.value = "æ°å¢æ¥ä»·"; isEdit.value = false; resetForm(); // é置审æ¹äººèç¹ approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; dialogVisible.value = true; let userLists = await userListNoPage(); // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ userList.value = (userLists.data || []).map(item => ({ userId: item.userId, nickName: item.nickName || "", userName: item.userName || "", })); approveUserList({ approveType: 6 }).then(res => { userListApprove.value = res.data; }); getProductOptions(); customerList().then(res => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ id: item.id, quotationNo: item.quotationNo || '', customer: item.customer || '', salesperson: item.salesperson || '', quotationDate: item.quotationDate || '', validDate: item.validDate || '', paymentMethod: item.paymentMethod || '', status: item.status || 'è稿', // 审æ¹äººï¼ç¨äºç¼è¾æ¶åæ¾ï¼ approveUserIds: item.approveUserIds || '', remark: item.remark || '', products: item.products ? item.products.map(product => ({ productId: product.productId || '', product: product.product || product.productName || '', specificationId: product.specificationId || '', specification: product.specification || '', quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0, amount: product.amount || 0 })) : [], subtotal: item.subtotal || 0, freight: item.freight || 0, otherFee: item.otherFee || 0, discountRate: item.discountRate || 0, discountAmount: item.discountAmount || 0, totalAmount: item.totalAmount || 0 })) pagination.total = res.data.total } }) customerList().then((res) => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ id: item.id, customerName: item.customerName || '', taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' })) }); } customerName: item.customerName || "", taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || "", })); }); }; const getProductOptions = () => { // è¿å Promiseï¼ä¾¿äºç¼è¾æ¶ await ç¡®ä¿è½åæ¾ return productTreeList().then(res => { productOptions.value = convertIdToValue(res); return productOptions.value; }); }; 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); } onMounted(()=>{ handleSearch() }) return newItem; }); } // æ ¹æ®åç§°åæ¥èç¹ idï¼ä¾¿äºä» ååç§°æ¶çåæ¾ function findNodeIdByLabel(nodes, label) { if (!label) return null; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (node.label === label) return node.value; if (node.children && node.children.length > 0) { const found = findNodeIdByLabel(node.children, label); if (found !== null && found !== undefined) return found; } } return null; } const getModels = (value, row) => { if (!row) return; // å¦ææ¸ ç©ºéæ©ï¼åæ¸ ç©ºç¸å ³å段 if (!value) { row.productId = ""; row.product = ""; row.modelOptions = []; row.specificationId = ""; row.specification = ""; row.unit = ""; return; } // æ´æ° productIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ row.productId = value; // æ¾å°å¯¹åºç label å¹¶èµå¼ç» row.product const label = findNodeById(productOptions.value, value); if (label) { row.product = label; } // è·åè§æ ¼åå·å表ï¼è®¾ç½®å°å½åè¡ç modelOptions modelList({ id: value }).then(res => { row.modelOptions = res || []; }); }; const getProductModel = (value, row) => { if (!row) return; // å¦ææ¸ ç©ºéæ©ï¼åæ¸ ç©ºç¸å ³å段 if (!value) { row.specificationId = ""; row.specification = ""; row.unit = ""; return; } // æ´æ° specificationIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ row.specificationId = value; const modelOptions = row.modelOptions || []; const index = modelOptions.findIndex(item => item.id === value); if (index !== -1) { row.specification = modelOptions[index].model; row.unit = modelOptions[index].unit; } else { row.specification = ""; row.unit = ""; } }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { return nodes[i].label; // æ¾å°èç¹ï¼è¿å label } if (nodes[i].children && nodes[i].children.length > 0) { const foundLabel = findNodeById(nodes[i].children, productId); if (foundLabel) { return foundLabel; // å¨åèç¹ä¸æ¾å°ï¼è¿å label } } } return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull }; const handleView = row => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ currentQuotation.value = { quotationNo: row.quotationNo || "", customer: row.customer || "", salesperson: row.salesperson || "", quotationDate: row.quotationDate || "", validDate: row.validDate || "", paymentMethod: row.paymentMethod || "", status: row.status || "", remark: row.remark || "", products: row.products ? row.products.map(product => ({ productId: product.productId || "", product: product.product || product.productName || "", specificationId: product.specificationId || "", specification: product.specification || "", quantity: product.quantity || 0, unit: product.unit || "", unitPrice: product.unitPrice || 0, amount: product.amount || 0, })) : [], totalAmount: row.totalAmount || 0, }; viewDialogVisible.value = true; }; const handleEdit = async row => { dialogTitle.value = "ç¼è¾æ¥ä»·"; isEdit.value = true; editId.value = row.id; form.id = row.id || form.id || null; // å å è½½äº§åæ æ°æ®ï¼å¦å el-tree-select æ æ³åæ¾äº§ååç§° await getProductOptions(); // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ form.quotationNo = row.quotationNo || ""; form.customer = row.customer || ""; form.salesperson = row.salesperson || ""; form.quotationDate = row.quotationDate || ""; form.validDate = row.validDate || ""; form.paymentMethod = row.paymentMethod || ""; form.status = row.status || "è稿"; form.remark = row.remark || ""; form.products = row.products ? await Promise.all( row.products.map(async product => { const productName = product.product || product.productName || ""; // ä¼å ç¨ productIdï¼å¦æåªæåç§°ï¼å°è¯åæ¥ id 以便æ éæ©å¨åæ¾ const resolvedProductId = product.productId ? Number(product.productId) : findNodeIdByLabel(productOptions.value, productName) || ""; // 妿æäº§åIDï¼å 载对åºçè§æ ¼åå·å表 let modelOptions = []; let resolvedSpecificationId = product.specificationId || ""; if (resolvedProductId) { try { const res = await modelList({ id: resolvedProductId }); modelOptions = res || []; // 妿è¿åçæ°æ®æ²¡æ specificationIdï¼ä½æ specification åç§°ï¼æ ¹æ®åç§°æ¥æ¾ ID if (!resolvedSpecificationId && product.specification) { const foundModel = modelOptions.find( item => item.model === product.specification ); if (foundModel) { resolvedSpecificationId = foundModel.id; } } } catch (error) { console.error("å è½½è§æ ¼åå·å¤±è´¥:", error); } } return { productId: resolvedProductId, product: productName, specificationId: resolvedSpecificationId, specification: product.specification || "", quantity: product.quantity || 0, unit: product.unit || "", unitPrice: product.unitPrice || 0, amount: product.amount || 0, modelOptions: modelOptions, // 为æ¯è¡æ·»å ç¬ç«çè§æ ¼åå·å表 }; }) ) : []; form.subtotal = row.subtotal || 0; form.freight = row.freight || 0; form.otherFee = row.otherFee || 0; form.discountRate = row.discountRate || 0; form.discountAmount = row.discountAmount || 0; form.totalAmount = row.totalAmount || 0; // 忾审æ¹äºº if (row.approveUserIds) { const userIds = row.approveUserIds.split(","); approverNodes.value = userIds.map((userId, idx) => ({ id: idx + 1, userId: parseInt(userId.trim()), })); nextApproverId = userIds.length + 1; } else { approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; } // å è½½ç¨æ·å表 let userLists = await userListNoPage(); userList.value = (userLists.data || []).map(item => ({ userId: item.userId, nickName: item.nickName || "", userName: item.userName || "", })); dialogVisible.value = true; }; const handleDelete = row => { ElMessageBox.confirm("确认å é¤è¯¥æ¥ä»·ååï¼", "æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }).then(() => { const index = quotationList.value.findIndex(item => item.id === row.id); if (index > -1) { deleteQuotation(row.id).then(res => { // console.log(res) if (res.code === 200) { ElMessage.success("å 餿å"); handleSearch(); } }); // quotationList.value.splice(index, 1) // pagination.total-- // ElMessage.success('å 餿å') } }); }; const resetForm = () => { form.customer = ""; form.salesperson = ""; form.quotationDate = ""; form.validDate = ""; form.paymentMethod = ""; form.status = "è稿"; form.remark = ""; form.products = []; form.subtotal = 0; form.freight = 0; form.otherFee = 0; form.discountRate = 0; form.discountAmount = 0; form.totalAmount = 0; }; const addProduct = () => { form.products.push({ productId: "", product: "", productName: "", specificationId: "", specification: "", quantity: 1, unit: "", unitPrice: 0, amount: 0, modelOptions: [], // 为æ¯è¡æ·»å ç¬ç«çè§æ ¼åå·å表 }); }; const removeProduct = index => { form.products.splice(index, 1); calculateSubtotal(); }; const calculateAmount = product => { product.amount = product.quantity * product.unitPrice; calculateSubtotal(); }; const calculateSubtotal = () => { form.subtotal = form.products.reduce( (sum, product) => sum + product.amount, 0 ); calculateTotal(); }; const calculateTotal = () => { form.discountAmount = form.subtotal * (form.discountRate / 100); form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount; }; const handleCustomerChange = () => { // å¯ä»¥æ ¹æ®å®¢æ·ä¿¡æ¯èªå¨å¡«å ä¸äºé»è®¤å¼ }; const handleSubmit = () => { formRef.value.validate(valid => { if (valid) { if (form.products.length === 0) { ElMessage.warning("请è³å°æ·»å ä¸ä¸ªäº§å"); return; } // 审æ¹äººå¿ å¡«æ ¡éª const hasEmptyApprover = approverNodes.value.some(node => !node.userId); if (hasEmptyApprover) { ElMessage.error("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼"); return; } // æ¶éææèç¹ç审æ¹äººid form.approveUserIds = approverNodes.value .map(node => node.userId) .join(","); // è®¡ç®ææäº§åçåä»·æ»å form.totalAmount = form.products.reduce((sum, product) => { const price = Number(product.unitPrice) || 0; return sum + price; }, 0); if (isEdit.value) { // ç¼è¾ const index = quotationList.value.findIndex( item => item.id === editId.value ); if (index > -1) { updateQuotation(form).then(res => { // console.log(res) if (res.code === 200) { ElMessage.success("ç¼è¾æå"); dialogVisible.value = false; handleSearch(); } }); } } else { // æ°å¢ addQuotation(form).then(res => { if (res.code === 200) { ElMessage.success("æ°å¢æå"); dialogVisible.value = false; handleSearch(); } }); } } }); }; const handleCurrentChange = val => { pagination.currentPage = val.page; pagination.pageSize = val.limit; // å页ååæ¶éæ°æ¥è¯¢å表 handleSearch(); }; const handleSearch = () => { const params = { // å端å页忰ï¼current / size current: pagination.currentPage, size: pagination.pageSize, ...searchForm, }; getQuotationList(params).then(res => { // console.log(res) if (res.code === 200) { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æå ¶ä»å¯¹è±¡æ¾å ¥ååºå¼å¯¹è±¡ quotationList.value = (res.data.records || []).map(item => ({ id: item.id, quotationNo: item.quotationNo || "", customer: item.customer || "", salesperson: item.salesperson || "", quotationDate: item.quotationDate || "", validDate: item.validDate || "", paymentMethod: item.paymentMethod || "", status: item.status || "è稿", // 审æ¹äººï¼ç¨äºç¼è¾æ¶åæ¾ï¼ approveUserIds: item.approveUserIds || "", remark: item.remark || "", products: item.products ? item.products.map(product => ({ productId: product.productId || "", product: product.product || product.productName || "", specificationId: product.specificationId || "", specification: product.specification || "", quantity: product.quantity || 0, unit: product.unit || "", unitPrice: product.unitPrice || 0, amount: product.amount || 0, })) : [], subtotal: item.subtotal || 0, freight: item.freight || 0, otherFee: item.otherFee || 0, discountRate: item.discountRate || 0, discountAmount: item.discountAmount || 0, totalAmount: item.totalAmount || 0, })); pagination.total = res.data.total; } }); customerList().then(res => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ id: item.id, customerName: item.customerName || "", taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || "", })); }); }; onMounted(() => { handleSearch(); }); </script> <style scoped lang="scss"> .search-row { margin-bottom: 20px; } .quotation-form-container { padding: 10px 0; max-height: calc(100vh - 200px); overflow-y: auto; &::-webkit-scrollbar { width: 6px; height: 6px; .search-row { margin-bottom: 20px; } &::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; &:hover { background: #a8a8a8; .quotation-form-container { padding: 10px 0; max-height: calc(100vh - 200px); overflow-y: auto; &::-webkit-scrollbar { width: 6px; height: 6px; } &::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; &:hover { background: #a8a8a8; } } } } .quotation-form { .el-form-item { margin-bottom: 22px; .quotation-form { .el-form-item { margin-bottom: 22px; } } } .form-card { margin-bottom: 24px; border-radius: 8px; transition: all 0.3s ease; &:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important; } :deep(.el-card__header) { padding: 16px 20px; background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%); border-bottom: 1px solid #ebeef5; } :deep(.el-card__body) { padding: 20px; } } .form-card { margin-bottom: 24px; border-radius: 8px; transition: all 0.3s ease; .card-header-wrapper { display: flex; align-items: center; gap: 8px; .card-icon { font-size: 18px; color: #409eff; } .card-title { font-weight: 600; font-size: 16px; color: #303133; flex: 1; } .header-btn { margin-left: auto; } } &:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important; } .form-content { padding: 8px 0; } :deep(.el-card__header) { padding: 16px 20px; background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%); border-bottom: 1px solid #ebeef5; } .product-table-form-item { margin-bottom: 0; :deep(.el-form-item__content) { margin-left: 0 !important; :deep(.el-card__body) { padding: 20px; } } :deep(.el-form-item__label) { width: auto; min-width: auto; } } .approver-nodes-container { display: flex; flex-wrap: wrap; gap: 24px; padding: 12px 0; } .approver-node-item { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e4e7ed; transition: all 0.3s ease; min-width: 180px; &:hover { border-color: #409eff; background: #f0f7ff; box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1); } } .approver-node-label { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #606266; .node-step { display: inline-flex; .card-header-wrapper { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; background: #409eff; color: #fff; border-radius: 50%; font-size: 12px; font-weight: 600; } .node-text { font-weight: 500; } .arrow-icon { color: #909399; font-size: 14px; } } gap: 8px; .approver-select { width: 100%; min-width: 150px; } .card-icon { font-size: 18px; color: #409eff; } .remove-btn { margin-top: 4px; } .product-table { :deep(.el-table__header) { background-color: #f5f7fa; th { background-color: #f5f7fa !important; color: #606266; .card-title { font-weight: 600; font-size: 16px; color: #303133; flex: 1; } .header-btn { margin-left: auto; } } :deep(.el-table__row) { &:hover { background-color: #f5f7fa; .form-content { padding: 8px 0; } .product-table-form-item { margin-bottom: 0; :deep(.el-form-item__content) { margin-left: 0 !important; } :deep(.el-form-item__label) { width: auto; min-width: auto; } } :deep(.el-table__cell) { .approver-nodes-container { display: flex; flex-wrap: wrap; gap: 24px; padding: 12px 0; } } .dialog-footer { text-align: right; } // ååºå¼ä¼å @media (max-width: 1200px) { .approver-nodes-container { gap: 16px; } .approver-node-item { min-width: 160px; display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e4e7ed; transition: all 0.3s ease; min-width: 180px; &:hover { border-color: #409eff; background: #f0f7ff; box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1); } } } .approver-node-label { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #606266; .node-step { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; background: #409eff; color: #fff; border-radius: 50%; font-size: 12px; font-weight: 600; } .node-text { font-weight: 500; } .arrow-icon { color: #909399; font-size: 14px; } } .approver-select { width: 100%; min-width: 150px; } .remove-btn { margin-top: 4px; } .product-table { :deep(.el-table__header) { background-color: #f5f7fa; th { background-color: #f5f7fa !important; color: #606266; font-weight: 600; } } :deep(.el-table__row) { &:hover { background-color: #f5f7fa; } } :deep(.el-table__cell) { padding: 12px 0; } } .dialog-footer { text-align: right; } // ååºå¼ä¼å @media (max-width: 1200px) { .approver-nodes-container { gap: 16px; } .approver-node-item { min-width: 160px; } } </style>