| 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
@@ -61,3 +61,27 @@ 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,26 +2,20 @@ <div class="app-container product-view"> <div class="left"> <div> <el-input v-model="search" <el-input v-model="search" style="width: 210px" placeholder="输入关键字进行搜索" @input="searchFilter" @change="searchFilter" @clear="searchFilter" clearable prefix-icon="Search" /> <el-button type="primary" prefix-icon="Search" /> <el-button type="primary" @click="openProDia('addOne')" style="margin-left: 10px" >新增产品大类</el-button > style="margin-left: 10px">新增产品大类</el-button> </div> <div ref="containerRef"> <el-tree ref="tree" <el-tree ref="tree" v-loading="treeLoad" :data="list" @node-click="handleNodeClick" @@ -32,8 +26,7 @@ highlight-current node-key="id" class="product-tree-scroll" style="height: calc(100vh - 190px); overflow-y: auto" > 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" <el-button type="primary" link @click="openProDia('edit', data)" > @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" <el-button v-if="!node.childNodes.length" style="margin-left: 4px" type="danger" link @click="remove(node, data)" > @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" <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" /> <el-button type="danger" @click="handleDelete" style="margin-left: 10px" plain > plain> 删除 </el-button> </div> <PIMTable rowKey="id" <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" ></PIMTable> @pagination="pagination"></PIMTable> </div> <el-dialog v-model="productDia" title="产品" width="400px" @keydown.enter.prevent> <el-form :model="form" <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" > ref="formRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="产品名称:" prop="productName"> <el-input v-model="form.productName" <el-form-item label="产品名称:" prop="productName"> <el-input v-model="form.productName" placeholder="请输入产品名称" maxlength="20" show-word-limit clearable @keydown.enter.prevent /> @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" <el-dialog v-model="modelDia" title="规格型号" width="400px" @close="closeModelDia" @keydown.enter.prevent > <el-form :model="modelForm" @keydown.enter.prevent> <el-form :model="modelForm" label-width="140px" label-position="top" :rules="modelRules" ref="modelFormRef" > ref="modelFormRef"> <el-row> <el-col :span="24"> <el-form-item label="规格型号:" prop="model"> <el-input v-model="modelForm.model" <el-form-item label="规格型号:" prop="model"> <el-input v-model="modelForm.model" placeholder="请输入规格型号" clearable @keydown.enter.prevent /> @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" <el-form-item label="厚度:" prop="thickness"> <el-input v-model="modelForm.thickness" placeholder="请输入厚度" clearable @keydown.enter.prevent @blur="modelForm.thickness = formatThicknessTo15(modelForm.thickness)" /> @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" <el-form-item label="单位:" prop="unit"> <el-input v-model="modelForm.unit" placeholder="请输入单位" clearable @keydown.enter.prevent /> @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,8 +197,10 @@ </template> <script setup> import { ref } from "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, @@ -206,6 +217,9 @@ 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(""); @@ -223,7 +237,7 @@ label: "厚度", prop: "thickness", // 列表展示时统一保留 15 位小数 formatData: (val) => formatThicknessTo15(val), formatData: val => formatThicknessTo15(val), }, { label: "单位", @@ -237,8 +251,15 @@ { name: "编辑", type: "text", clickFun: (row) => { clickFun: row => { openModelDia("edit", row); }, }, { name: "生成二维码", type: "text", clickFun: row => { generateQrcode(row); }, }, ], @@ -275,7 +296,7 @@ const { form, rules, modelForm, modelRules } = toRefs(data); // 把厚度格式化成固定 15 位小数(用于展示/提交) const formatThicknessTo15 = (val) => { const formatThicknessTo15 = val => { if (val === null || val === undefined) return ""; const s = String(val).trim(); if (s === "") return ""; @@ -287,14 +308,14 @@ const getProductTreeList = () => { treeLoad.value = true; productTreeList() .then((res) => { .then(res => { list.value = res; list.value.forEach((a) => { list.value.forEach(a => { expandedKeys.value.push(a.label); }); treeLoad.value = false; }) .catch((err) => { .catch(err => { treeLoad.value = false; }); }; @@ -324,7 +345,7 @@ }; // 提交产品名称修改 const submitForm = () => { proxy.$refs.formRef.validate((valid) => { proxy.$refs.formRef.validate(valid => { if (valid) { if (operationType.value === "add") { form.value.parentId = currentId.value; @@ -336,7 +357,7 @@ form.value.id = currentId.value; form.value.parentId = ""; } addOrEditProduct(form.value).then((res) => { addOrEditProduct(form.value).then(res => { proxy.$modal.msgSuccess("提交成功"); closeProDia(); getProductTreeList(); @@ -362,7 +383,7 @@ .then(() => { tableLoading.value = true; delProduct(ids) .then((res) => { .then(res => { proxy.$modal.msgSuccess("删除成功"); getProductTreeList(); }) @@ -374,6 +395,7 @@ proxy.$modal.msg("已取消"); }); }; const fatherName = ref(""); // 选择产品 const handleNodeClick = (val, node, el) => { // 判断是否为叶子节点 @@ -381,16 +403,19 @@ // 只有叶子节点才执行以下逻辑 currentId.value = val.id; currentParentId.value = val.parentId; fatherName.value = val.label; getModelList(); }; // 提交规格型号修改 const submitModelForm = () => { proxy.$refs.modelFormRef.validate((valid) => { proxy.$refs.modelFormRef.validate(valid => { if (valid) { modelForm.value.productId = currentId.value; modelForm.value.thickness = formatThicknessTo15(modelForm.value.thickness); addOrEditProductModel(modelForm.value).then((res) => { modelForm.value.thickness = formatThicknessTo15( modelForm.value.thickness ); addOrEditProductModel(modelForm.value).then(res => { proxy.$modal.msgSuccess("提交成功"); closeModelDia(); getModelList(); @@ -404,12 +429,12 @@ modelDia.value = false; }; // 表格选择数据 const handleSelectionChange = (selection) => { const handleSelectionChange = selection => { selectedRows.value = selection; }; // 查询规格型号 const pagination = (obj) => { const pagination = obj => { page.current = obj.page; page.size = obj.limit; getModelList(); @@ -420,7 +445,7 @@ id: currentId.value, current: page.current, size: page.size, }).then((res) => { }).then(res => { console.log("res", res); tableData.value = res.records; page.total = res.total; @@ -431,7 +456,7 @@ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map((item) => item.id); ids = selectedRows.value.map(item => item.id); } else { proxy.$modal.msgWarning("请选择数据"); return; @@ -444,7 +469,7 @@ .then(() => { tableLoading.value = true; delProductModel(ids) .then((res) => { .then(res => { proxy.$modal.msgSuccess("删除成功"); getModelList(); }) @@ -495,6 +520,57 @@ // 没匹配到返回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> @@ -563,4 +639,23 @@ .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" <el-dialog v-model="dialogFormVisible" :title="operationType === 'approval' ? '审批' : '详情'" width="700px" @close="closeDia" > <el-form :model="form" label-width="140px" label-position="top" ref="formRef"> @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 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 <el-select disabled v-model="form.approveDeptId" placeholder="选择部门" > <el-option v-for="user in productOptions" placeholder="选择部门"> <el-option v-for="user in productOptions" :key="user.deptId" :label="user.deptName" :value="user.deptId" /> :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 :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" <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" disabled> <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> :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" <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 /> 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,22 +198,41 @@ </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" <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)" > :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> <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> @@ -190,29 +240,36 @@ <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,40 +278,52 @@ </template> <script setup> import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue"; import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs, } from "vue"; import { approveProcessDetails, getDept, updateApproveNode 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 { 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 emit = defineEmits(["close"]); const { proxy } = getCurrentInstance(); const props = defineProps({ approveType: { type: [Number, String], default: 0 } }) default: 0, }, }); const dialogFormVisible = ref(false); const operationType = ref('') const activities = ref([]) const operationType = ref(""); const activities = ref([]); const formRef = ref(null); const userStore = useUserStore() 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 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: { @@ -270,8 +339,8 @@ // 节点标题 const getNodeTitle = (index, len) => { if (index === len - 1) return '结束'; return '审批'; if (index === len - 1) return "结束"; return "审批"; }; // 获取当前激活步骤 @@ -284,21 +353,21 @@ }; // 步骤icon const getNodeIcon = (activity, index) => { if (activity.approveNodeStatus === 2) return 'el-icon-warning'; // 不通过 if (activity.isShen) return 'Edit'; return ''; 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) => { currentQuotation.value = {}; currentPurchase.value = {}; approveUserList({ approveType: props.approveType }).then(res => { userList.value = res.data; }); form.value = {...row} form.value = { ...row }; // 立即清除表单验证状态(因为字段是disabled的,不需要验证) nextTick(() => { if (formRef.value) { @@ -309,7 +378,8 @@ getProductOptions().then(() => { // 确保值类型匹配(如果选项已加载) if (productOptions.value.length > 0 && form.value.approveDeptId) { const matchedOption = productOptions.value.find(opt => const matchedOption = productOptions.value.find( opt => opt.deptId == form.value.approveDeptId || String(opt.deptId) === String(form.value.approveDeptId) ); @@ -329,13 +399,15 @@ 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 quotationLoading.value = true; getQuotationList({ quotationNo }) .then(res => { const records = res?.data?.records || []; currentQuotation.value = records[0] || {}; }) .finally(() => { quotationLoading.value = false; }); } } @@ -343,45 +415,50 @@ 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 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 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') if (item.url && item.url.includes("word")) { item.urlTem = item.url.replaceAll("word", "img"); } else { item.urlTem = item.url item.urlTem = item.url; } if (item.approveNodeStatus === 2) { item.isApproval = '已驳回'; item.isApproval = "已驳回"; } else if (item.approveNodeStatus === 1) { item.isApproval = '已同意'; item.isApproval = "已同意"; } else { item.isApproval = '未审批'; item.isApproval = "未审批"; } }) }) } }); }); }; const getProductOptions = () => { return getDept().then((res) => { return getDept().then(res => { productOptions.value = res.data; }); }; // 提交审批 const submitForm = (status) => { const filteredActivities = activities.value.filter(activity => activity.isShen); const submitForm = status => { const filteredActivities = activities.value.filter( activity => activity.isShen ); if (!filteredActivities || filteredActivities.length === 0) { proxy.$modal.msgError("未找到待审批的节点"); return; @@ -393,7 +470,8 @@ } currentActivity.approveNodeStatus = status; // 判断是否为最后一步 const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length - 1; updateApproveNode({ ...currentActivity, isLast }).then(() => { proxy.$modal.msgSuccess("提交成功"); closeDia(); @@ -403,11 +481,11 @@ const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; quotationLoading.value = false currentQuotation.value = {} purchaseLoading.value = false currentPurchase.value = {} emit('close') quotationLoading.value = false; currentQuotation.value = {}; purchaseLoading.value = false; currentPurchase.value = {}; emit("close"); }; defineExpose({ openDialog, @@ -415,7 +493,6 @@ </script> <style scoped> .node-user { margin: 10px 0; font-size: 16px; src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -1,99 +1,103 @@ <template> <div> <el-dialog v-model="dialogFormVisible" <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"> @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-form-item label="申请部门:" prop="approveDeptName"> <!-- <el-input v-model="form.approveDeptName" placeholder="请输入" clearable/>--> <el-select v-model="form.approveDeptId" <el-select v-model="form.approveDeptId" placeholder="选择部门" @change="handleDeptChange" > <el-option v-for="user in productOptions" @change="handleDeptChange"> <el-option v-for="user in productOptions" :key="user.deptId" :label="user.deptName" :value="user.deptId" /> :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" <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%" /> 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" <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%" /> 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" <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 /> 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" <el-form-item label="出差地点:" prop="location"> <el-input v-model="form.location" placeholder="请输入出差地点" clearable /> 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" <div v-for="(node, index) in approverNodes" :key="node.id" style="margin-right: 30px; text-align: center; margin-bottom: 10px;" > style="margin-right: 30px; text-align: center; margin-bottom: 10px;"> <div> <span>审批人</span> → </div> <el-select v-model="node.userId" <el-select v-model="node.userId" placeholder="选择人员" style="width: 120px; margin-bottom: 8px;" > <el-option v-for="user in userList" style="width: 120px; margin-bottom: 8px;"> <el-option v-for="user in userListApproval" :key="user.userId" :label="user.nickName" :value="user.userId" /> :label="user.userName" :value="user.userId" /> </el-select> <div> <el-button type="danger" <el-button type="danger" size="small" @click="removeApproverNode(index)" v-if="approverNodes.length > 1" >删除</el-button> 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" <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" :reserve-keyword="false"> <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> :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" <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%" /> 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> @@ -204,24 +209,24 @@ <script setup> import {ref, reactive, toRefs, getCurrentInstance} from "vue"; import { approveProcessAdd, approveProcessGetInfo, approveProcessAdd, approveProcessGetInfo, approveProcessUpdate, getDept getDept, } from "@/api/collaborativeApproval/approvalProcess.js"; import { delLedgerFile, } from "@/api/salesManagement/salesLedger.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']) 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 operationType = ref(""); const fileList = ref([]); const upload = reactive({ // 上传的地址 @@ -243,100 +248,110 @@ startDate: "", // 请假开始时间 endDate: "", // 请假结束时间 price: null, // 报销金额 location: "" // 出差地点 location: "", // 出差地点 }, rules: { approveTime: [{ required: false, message: "请输入", trigger: "change" },], 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" }], 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 currentApproveStatus = ref(0); const props = defineProps({ approveType: { type: [Number, String], default: 0 } }) default: 0, }, }); // 审批人节点相关 const approverNodes = ref([ { id: 1, userId: null } ]) let nextApproverId = 2 const userList = ref([]) 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 }) approverNodes.value.push({ id: nextApproverId++, userId: null }); } function removeApproverNode(index) { approverNodes.value.splice(index, 1) approverNodes.value.splice(index, 1); } // 处理部门选择变化 const handleDeptChange = (deptId) => { const handleDeptChange = deptId => { if (deptId) { const selectedDept = productOptions.value.find(dept => dept.deptId === deptId); const selectedDept = productOptions.value.find( dept => dept.deptId === deptId ); if (selectedDept) { form.value.approveDeptName = selectedDept.deptName; } } else { form.value.approveDeptName = ''; form.value.approveDeptName = ""; } }; // 打开弹框 const openDialog = (type, row) => { operationType.value = type; dialogFormVisible.value = true; userListNoPageByTenantId().then((res) => { userListNoPageByTenantId().then(res => { userList.value = res.data; }); form.value = {} approverNodes.value = [ { id: 1, userId: null } ] 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(); // 获取当前用户信息并设置部门ID form.value.approveDeptId = userStore.currentDeptId 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 (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(',') const userIds = res.data.approveUserIds.split(","); approverNodes.value = userIds.map((userId, idx) => ({ id: idx + 1, userId: parseInt(userId.trim()) })) nextApproverId = userIds.length + 1 userId: parseInt(userId.trim()), })); nextApproverId = userIds.length + 1; } else { approverNodes.value = [{ id: 1, userId: null }] nextApproverId = 2 } }) approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; } } ); } }; const getProductOptions = () => { return getDept().then((res) => { return getDept().then(res => { productOptions.value = res.data; // 如果已有部门ID,自动设置部门名称(用于验证) if (form.value.approveDeptId && productOptions.value.length > 0) { const matchedDept = productOptions.value.find(dept => const matchedDept = productOptions.value.find( dept => dept.deptId == form.value.approveDeptId || String(dept.deptId) === String(form.value.approveDeptId) ); @@ -347,7 +362,7 @@ }); }; function convertIdToValue(data) { return data.map((item) => { return data.map(item => { const { id, children, ...rest } = item; const newItem = { ...rest, @@ -363,42 +378,44 @@ // 提交产品表单 const submitForm = () => { // 收集所有节点的审批人id form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') form.value.approveType = props.approveType form.value.approveUserIds = approverNodes.value .map(node => node.userId) .join(","); form.value.approveType = props.approveType; // 审批人必填校验 const hasEmptyApprover = approverNodes.value.some(node => !node.userId) const hasEmptyApprover = approverNodes.value.some(node => !node.userId); if (hasEmptyApprover) { proxy.$modal.msgError("请为所有审批节点选择审批人!") return proxy.$modal.msgError("请为所有审批节点选择审批人!"); return; } // 当 approveType 为 2 时,校验请假时间 if (props.approveType == 2) { if (!form.value.startDate) { proxy.$modal.msgError("请选择请假开始时间!") return proxy.$modal.msgError("请选择请假开始时间!"); return; } if (!form.value.endDate) { proxy.$modal.msgError("请选择请假结束时间!") return proxy.$modal.msgError("请选择请假结束时间!"); return; } // 校验结束时间不能早于开始时间 if (new Date(form.value.endDate) < new Date(form.value.startDate)) { proxy.$modal.msgError("请假结束时间不能早于开始时间!") return proxy.$modal.msgError("请假结束时间不能早于开始时间!"); return; } } // 当 approveType 为 3 时,校验出差地点 if (props.approveType == 3) { if (!form.value.location || form.value.location.trim() === '') { proxy.$modal.msgError("请输入出差地点!") return 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.$modal.msgError("请输入有效的报销金额!"); return; } } proxy.$refs.formRef.validate(valid => { @@ -407,22 +424,22 @@ approveProcessAdd(form.value).then(res => { proxy.$modal.msgSuccess("提交成功"); closeDia(); }) }); } else { approveProcessUpdate(form.value).then(res => { proxy.$modal.msgSuccess("提交成功"); closeDia(); }) }); } } }) } }); }; // 关闭弹框 const closeDia = () => { fileList.value = [] fileList.value = []; proxy.resetForm("formRef"); dialogFormVisible.value = false; emit('close') emit("close"); }; // 上传前校检 @@ -460,7 +477,7 @@ if (operationType.value === "edit") { let ids = []; ids.push(file.id); delLedgerFile(ids).then((res) => { delLedgerFile(ids).then(res => { proxy.$modal.msgSuccess("删除成功"); }); } @@ -472,5 +489,4 @@ </script> <style scoped> </style> src/views/collaborativeApproval/approvalProcess/index.vue
@@ -1,57 +1,67 @@ <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" <el-input v-model="searchForm.approveId" style="width: 240px" placeholder="请输入流程编号搜索" @change="handleQuery" clearable :prefix-icon="Search" /> :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 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-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button> </div> <div> <el-button type="primary" <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="handleOut">导出</el-button> <el-button type="danger" 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> v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7">删除</el-button> </div> </div> <div class="table_list"> <PIMTable rowKey="id" <PIMTable rowKey="id" :column="tableColumnCopy" :tableData="tableData" :page="page" @@ -59,31 +69,95 @@ @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" :total="page.total" ></PIMTable> :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 { onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance, } from "vue"; import {ElMessageBox} from "element-plus"; import { useRoute } from 'vue-router'; 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 { 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 activeTab = ref('1'); const activeTab = ref("1"); // 当前审批类型,根据选中的标签页计算 const currentApproveType = computed(() => { @@ -91,14 +165,13 @@ }); // 标签页切换处理 const handleTabChange = (tabName) => { const handleTabChange = tabName => { // 切换标签页时重置搜索条件和分页,并重新加载数据 searchForm.value.approveId = ''; searchForm.value.approveStatus = ''; searchForm.value.approveId = ""; searchForm.value.approveStatus = ""; page.current = 1; getList(); }; const data = reactive({ searchForm: { @@ -122,7 +195,7 @@ prop: "approveStatus", dataType: "tag", width: 100, formatData: (params) => { formatData: params => { if (params == 0) { return "待审核"; } else if (params == 1) { @@ -132,10 +205,10 @@ } else if (params == 4) { return "已重新提交"; } else { return '不通过'; return "不通过"; } }, formatType: (params) => { formatType: params => { if (params == 0) { return "warning"; } else if (params == 1) { @@ -145,29 +218,33 @@ } else if (params == 4) { return "info"; } else { return 'danger'; return "danger"; } }, }, { label: "流程编号", prop: "approveId", width: 170 width: 170, }, { label: "申请部门", prop: "approveDeptName", width: 220 width: 220, }, { label: isQuotationType ? "报价单号" : isPurchaseType ? "采购合同号" : "审批事由", label: isQuotationType ? "报价单号" : isPurchaseType ? "采购合同号" : "审批事由", prop: "approveReason", }, { label: "申请人", prop: "approveUserName", width: 120 } width: 120, }, ]; // 金额列(仅报销管理显示) @@ -175,7 +252,7 @@ baseColumns.push({ label: "金额(元)", prop: "price", width: 120 width: 120, }); } @@ -184,12 +261,12 @@ { label: isLeaveType ? "开始日期" : "申请日期", prop: isLeaveType ? "startDate" : "approveTime", width: 200 width: 200, }, { label: "结束日期", prop: isLeaveType ? "endDate" : "approveOverTime", width: 120 width: 120, } ); @@ -197,7 +274,7 @@ baseColumns.push({ label: "当前审批人", prop: "approveUserCurrentName", width: 120 width: 120, }); // 操作列 @@ -205,34 +282,34 @@ { name: "编辑", type: "text", clickFun: (row) => { clickFun: row => { openForm("edit", row); }, disabled: (row) => disabled: row => currentApproveType.value === 5 || currentApproveType.value === 6 || currentApproveType.value === 7 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4 row.approveStatus == 4, }, { name: "审核", type: "text", clickFun: (row) => { clickFun: row => { openApprovalDia("approval", row); }, disabled: (row) => disabled: row => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id row.approveUserCurrentId !== userStore.id, }, { name: "详情", type: "text", clickFun: (row) => { clickFun: row => { openApprovalDia("view", row); }, }, @@ -243,7 +320,7 @@ actionOperations.push({ name: "附件", type: "text", clickFun: (row) => { clickFun: row => { downLoadFile(row); }, }); @@ -266,11 +343,21 @@ const page = reactive({ current: 1, size: 100, total: 0 total: 0, }); const infoFormDia = ref() const approvalDia = ref() const { proxy } = getCurrentInstance() const infoFormDia = ref(); const approvalDia = ref(); const { proxy } = getCurrentInstance(); // 审批人维护对话框 const approverDialogVisible = ref(false); const selectedApprovers = ref([]); const existingApprovers = ref([]); // 已有的审批人列表 const approverLoading = ref(false); // 加载状态 // 审批人列表数据 const approverList = ref([]); const approverTable = ref(null); // 查询列表 /** 搜索按钮操作 */ @@ -278,29 +365,34 @@ page.current = 1; getList(); }; const fileListRef = ref(null) const downLoadFile = (row) => { fileListRef.value.open(row.commonFileList) } const pagination = (obj) => { 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; 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 handleExport = () => { const type = currentApproveType.value; const urlMap = { 0: "/approveProcess/exportZero", 1: "/approveProcess/exportOne", @@ -310,8 +402,8 @@ 5: "/approveProcess/exportFive", 6: "/approveProcess/exportSix", 7: "/approveProcess/exportSeven", } const url = urlMap[type] || urlMap[0] }; const url = urlMap[type] || urlMap[0]; const nameMap = { 0: "协同审批管理表", 1: "公出管理审批表", @@ -321,33 +413,166 @@ 5: "采购申请审批表", 6: "报价审批表", 7: "发货审批表", }; const fileName = nameMap[type] || nameMap[0]; proxy.download(url, {}, `${fileName}.xlsx`); }; // 审批人维护 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); } const fileName = nameMap[type] || nameMap[0] proxy.download(url, {}, `${fileName}.xlsx`) }); } }); }) .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(() => { // 然后添加新的审批人 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) => { const handleSelectionChange = selection => { selectedRows.value = selection; }; // 打开新增、编辑弹框 const openForm = (type, row) => { nextTick(() => { infoFormDia.value?.openDialog(type, row) }) infoFormDia.value?.openDialog(type, row); }); }; // 打开新增检验弹框 const openApprovalDia = (type, row) => { nextTick(() => { approvalDia.value?.openDialog(type, row) }) approvalDia.value?.openDialog(type, row); }); }; // 删除 const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map((item) => item.approveId); ids = selectedRows.value.map(item => item.approveId); } else { proxy.$modal.msgWarning("请选择数据"); return; @@ -358,7 +583,7 @@ type: "warning", }) .then(() => { approveProcessDelete(ids).then((res) => { approveProcessDelete(ids).then(res => { proxy.$modal.msgSuccess("删除成功"); getList(); }); @@ -391,4 +616,40 @@ .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="含税单价(元)" @@ -134,8 +135,7 @@ width="100" show-overflow-tooltip> <template #default="scope"> <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" size="small"> {{ approvalStatusText[scope.row.approvalStatus] || '未知状态' }} </el-tag> @@ -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" <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item" > class="approver-node-item"> <div class="approver-node-header"> <span class="approver-node-label">审批节点 {{ index + 1 }}</span> <el-button v-if="approverNodes.length > 1" <el-button v-if="approverNodes.length > 1" type="danger" size="small" text @click="removeApproverNode(index)" icon="Delete" >删除</el-button> icon="Delete">删除</el-button> </div> <el-select v-model="node.userId" <el-select v-model="node.userId" placeholder="请选择审批人" filterable style="width: 100%;" > <el-option v-for="user in userList" style="width: 100%;"> <el-option v-for="user in userListApprove" :key="user.userId" :label="user.nickName" :value="user.userId" /> :label="user.userName" :value="user.userId" /> </el-select> </div> </div> @@ -373,8 +368,7 @@ :value="item.templateName"> <div style="display: flex; justify-content: space-between; align-items: center;"> <span>{{ item.templateName }}</span> <el-icon v-if="item.id" <el-icon v-if="item.id" class="delete-icon" @click.stop="handleDeleteTemplate(item)" style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;"> @@ -493,16 +487,13 @@ </el-form> </FormDialog> <!-- 导入弹窗 --> <FormDialog v-model="importUpload.open" <FormDialog v-model="importUpload.open" :title="importUpload.title" :width="'600px'" @close="importUpload.open = false" @confirm="submitImportFile" @cancel="importUpload.open = false" > <el-upload ref="importUploadRef" @cancel="importUpload.open = false"> <el-upload ref="importUploadRef" :limit="1" accept=".xlsx,.xls" :action="importUpload.url" @@ -513,8 +504,7 @@ :on-progress="importUpload.onProgress" :on-change="importUpload.onChange" :auto-upload="false" drag > drag> <i class="el-icon-upload"></i> <div class="el-upload__text"> 将文件拖到此处,或<em>点击上传</em> @@ -522,7 +512,9 @@ <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> @@ -694,11 +686,9 @@ </el-row> </el-form> </FormDialog> <FileListDialog ref="fileListRef" <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" title="附件列表" /> 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,7 +776,7 @@ }; // 获取审批状态标签类型 const getApprovalStatusType = (status) => { const getApprovalStatusType = status => { const typeMap = { 1: "info", // 待审核 - 灰色 2: "warning", // 审批中 - 橙色 @@ -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; } @@ -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 有值,说明当前是“编辑已有模板” → 调用更新接口 // 否则为“新建模板” → 调用新增接口 @@ -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,7 +1417,9 @@ 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 字段 @@ -1491,14 +1498,20 @@ 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; // 获取型号列表并等待完成 @@ -1509,7 +1522,10 @@ 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 ); @@ -1654,11 +1670,9 @@ delProduct(ids).then(res => { proxy.$modal.msgSuccess("删除成功"); closeProductDia(); getPurchaseById({ id: currentId.value, type: 2 }).then( res => { getPurchaseById({ id: currentId.value, type: 2 }).then(res => { productData.value = res.productData; } ); }); }); }) .catch(() => { @@ -1866,7 +1880,7 @@ }; // 删除模板 const handleDeleteTemplate = async (item) => { const handleDeleteTemplate = async item => { if (!item.id) { proxy.$modal.msgWarning("无法删除该模板"); return; src/views/salesManagement/salesLedger/index.vue
@@ -1,54 +1,75 @@ <template> <div class="app-container"> <div class="search_form"> <el-form :model="searchForm" :inline="true"> <el-form :model="searchForm" :inline="true"> <el-form-item label="客户名称:"> <el-select v-model="searchForm.customerId" <el-select v-model="searchForm.customerId" filterable placeholder="请选择客户名称" clearable style="width: 220px" @change="handleQuery" > <el-option v-for="item in customerOption" @change="handleQuery"> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id" > :value="item.id"> {{ item.customerName + "——" + item.taxpayerIdentificationNumber }} </el-option> </el-select> </el-form-item> <el-form-item label="销售合同号:"> <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" @change="handleQuery" /> </el-form-item> <el-form-item label="项目名称:"> <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search" <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search" @change="handleQuery" /> </el-form-item> <el-form-item label="录入日期:"> <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" placeholder="请选择" clearable @change="changeDaterange" /> <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" placeholder="请选择" clearable @change="changeDaterange" /> </el-form-item> <el-form-item label="发货状态:"> <el-select v-model="searchForm.deliveryStatus" placeholder="请选择" clearable style="width: 140px"> <el-option label="未发货" :value="1" /> <el-option label="审批中" :value="2" /> <el-option label="审批失败" :value="3" /> <el-option label="已发货" :value="4" /> <el-select v-model="searchForm.deliveryStatus" placeholder="请选择" clearable style="width: 140px"> <el-option label="未发货" :value="1" /> <el-option label="审批中" :value="2" /> <el-option label="审批失败" :value="3" /> <el-option label="已发货" :value="4" /> </el-select> </el-form-item> <el-form-item label="入库状态:"> <el-select v-model="searchForm.stockStatus" placeholder="请选择" clearable style="width: 140px"> <el-option label="未入库" :value="0" /> <el-option label="已入库" :value="1" /> <el-select v-model="searchForm.stockStatus" placeholder="请选择" clearable style="width: 140px"> <el-option label="未入库" :value="0" /> <el-option label="已入库" :value="1" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery"> 搜索 </el-button> <el-button type="primary" @click="handleQuery"> 搜索 </el-button> </el-form-item> </el-form> </div> @@ -58,23 +79,27 @@ <OtherAmountMaintenanceButton /> <ProcessFlowMaintenanceButton /> </div> <ProcessFlowConfigSelectDialog v-model:visible="processFlowSelectDialogVisible" <ProcessFlowConfigSelectDialog v-model:visible="processFlowSelectDialogVisible" :default-route-id="processFlowSelectDefaultRouteId" :bound-route-name="processFlowSelectBoundRouteName" @confirm="handleProcessFlowSelectConfirm" /> @confirm="handleProcessFlowSelectConfirm" /> <el-space wrap> <el-button type="primary" @click="handleSalesStock">入库</el-button> <el-button type="primary" @click="openForm('add')">新增台账</el-button> <el-button type="primary" @click="handleBulkDelivery">发货</el-button> <el-button type="primary" plain @click="handleImport">导入</el-button> <el-button type="primary" @click="handleSalesStock">入库</el-button> <el-button type="primary" @click="openForm('add')">新增台账</el-button> <el-button type="primary" @click="handleBulkDelivery">发货</el-button> <el-button type="primary" plain @click="handleImport">导入</el-button> <el-button @click="handleOut">导出</el-button> <el-button type="danger" plain @click="handleDelete">删除</el-button> <el-button type="danger" plain @click="handleDelete">删除</el-button> <el-dropdown @command="handlePrintCommand"> <el-button type="primary" plain> <el-button type="primary" plain> 打印单据<el-icon class="el-icon--right"> <ArrowDown /> </el-icon> @@ -87,49 +112,92 @@ </el-dropdown-menu> </template> </el-dropdown> <el-button type="primary" plain @click="handlePrintLabel">打印标签</el-button> <el-button type="primary" plain @click="handlePrintLabel">打印标签</el-button> </el-space> </div> <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" :row-class-name="tableRowClassName" show-summary style="width: 100%" :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)"> <el-table-column align="center" type="selection" width="55" fixed="left"/> <el-table-column type="expand" width="60" fixed="left"> <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" :row-class-name="tableRowClassName" show-summary style="width: 100%" :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)"> <el-table-column align="center" type="selection" width="55" fixed="left" /> <el-table-column type="expand" width="60" fixed="left"> <template #default="props"> <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable"> <el-table-column align="center" label="序号" type="index"/> <el-table-column label="楼层编号" prop="floorCode" min-width="100" show-overflow-tooltip /> <el-table-column label="产品大类" prop="productCategory" /> <el-table-column label="规格型号" prop="specificationModel" /> <el-table-column label="厚度" prop="thickness" min-width="90"> <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable"> <el-table-column align="center" label="序号" type="index" /> <el-table-column label="楼层编号" prop="floorCode" min-width="100" show-overflow-tooltip /> <el-table-column label="产品大类" prop="productCategory" /> <el-table-column label="规格型号" prop="specificationModel" /> <el-table-column label="厚度" prop="thickness" min-width="90"> <template #default="scope"> {{ scope.row.thickness ?? "" }} </template> </el-table-column> <el-table-column label="宽(mm)" prop="width" min-width="80"> <el-table-column label="宽(mm)" prop="width" min-width="80"> <template #default="scope"> {{ scope.row.width ?? "" }} </template> </el-table-column> <el-table-column label="高(mm)" prop="height" min-width="80"> <el-table-column label="高(mm)" prop="height" min-width="80"> <template #default="scope"> {{ scope.row.height ?? "" }} </template> </el-table-column> <el-table-column label="周长(cm)" prop="perimeter" min-width="90"> <el-table-column label="周长(cm)" prop="perimeter" min-width="90"> <template #default="scope"> {{ scope.row.perimeter ?? "" }} </template> </el-table-column> <el-table-column label="总面积(cm²)" prop="actualTotalArea" min-width="100"> <el-table-column label="总面积(cm²)" prop="actualTotalArea" min-width="100"> <template #default="scope"> {{ scope.row.actualTotalArea ?? "" }} </template> </el-table-column> <el-table-column label="加工要求" prop="processRequirement" min-width="120" <el-table-column label="加工要求" prop="processRequirement" min-width="120" show-overflow-tooltip /> <el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip /> <el-table-column label="重箱" prop="heavyBox" min-width="80"> <el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip /> <el-table-column label="重箱" prop="heavyBox" min-width="80"> <template #default="scope"> {{ scope.row.heavyBox ?? "" }} </template> @@ -138,12 +206,12 @@ width="100px" align="center"> <template #default="scope"> <el-tag v-if="scope.row.approveStatus === 1 && (!scope.row.shippingDate || !scope.row.shippingCarNumber)" type="success">充足</el-tag> <el-tag v-else-if="scope.row.approveStatus === 0 && (scope.row.shippingDate || scope.row.shippingCarNumber)" type="success">已出库</el-tag> <el-tag v-else type="danger">不足</el-tag> <el-tag v-else type="danger">不足</el-tag> </template> </el-table-column> <!-- <el-table-column label="发货状态" width="140" align="center"> @@ -153,13 +221,21 @@ </el-tag> </template> </el-table-column> --> <el-table-column label="快递公司" prop="expressCompany" show-overflow-tooltip /> <el-table-column label="快递单号" prop="expressNumber" show-overflow-tooltip /> <el-table-column label="发货车牌" minWidth="100px" align="center"> <el-table-column label="快递公司" prop="expressCompany" show-overflow-tooltip /> <el-table-column label="快递单号" prop="expressNumber" show-overflow-tooltip /> <el-table-column label="发货车牌" minWidth="100px" align="center"> <template #default="scope"> <div> <el-tag type="success" v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag> <el-tag v-else type="info">-</el-tag> <el-tag type="success" v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag> <el-tag v-else type="info">-</el-tag> </div> </template> </el-table-column> @@ -174,11 +250,19 @@ </div> </template> </el-table-column> <el-table-column label="数量" prop="quantity" /> <el-table-column label="税率(%)" prop="taxRate" /> <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column label="数量" prop="quantity" /> <el-table-column label="税率(%)" prop="taxRate" /> <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> <!--操作--> <!-- <el-table-column Width="60px" label="操作" align="center"> <template #default="scope"> @@ -194,67 +278,151 @@ </el-table> </template> </el-table-column> <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column label="销售合同号" prop="salesContractNo" width="180" show-overflow-tooltip /> <el-table-column label="客户名称" prop="customerName" width="300" show-overflow-tooltip /> <el-table-column label="业务员" prop="salesman" width="100" show-overflow-tooltip /> <el-table-column label="项目名称" prop="projectName" width="180" show-overflow-tooltip /> <el-table-column label="合同金额(元)" prop="contractAmount" width="220" show-overflow-tooltip <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column label="销售合同号" prop="salesContractNo" width="180" show-overflow-tooltip /> <el-table-column label="客户名称" prop="customerName" width="300" show-overflow-tooltip /> <el-table-column label="业务员" prop="salesman" width="100" show-overflow-tooltip /> <el-table-column label="项目名称" prop="projectName" width="180" show-overflow-tooltip /> <el-table-column label="合同金额(元)" prop="contractAmount" width="220" show-overflow-tooltip :formatter="formattedNumber" /> <el-table-column label="发货状态" width="140" align="center"> <el-table-column label="发货状态" width="140" align="center"> <template #default="scope"> <el-tag v-if="Number(scope.row.deliveryStatus) === 1" type="info">未发货</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 2" type="warning">审批中</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 3" type="danger">审批不通过</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 4" type="primary">审批通过</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 5" type="success">已发货</el-tag> <el-tag v-else type="info">-</el-tag> <el-tag v-if="Number(scope.row.deliveryStatus) === 1" type="info">未发货</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 2" type="warning">审批中</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 3" type="danger">审批不通过</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 4" type="primary">审批通过</el-tag> <el-tag v-else-if="Number(scope.row.deliveryStatus) === 5" type="success">已发货</el-tag> <el-tag v-else type="info">-</el-tag> </template> </el-table-column> <el-table-column label="入库状态" width="120" align="center"> <el-table-column label="入库状态" width="120" align="center"> <template #default="scope"> <el-tag v-if="Number(scope.row.stockStatus) === 0" type="info">未入库</el-tag> <el-tag v-else-if="Number(scope.row.stockStatus) === 1" type="success">已入库</el-tag> <el-tag v-else type="info">-</el-tag> <el-tag v-if="Number(scope.row.stockStatus) === 0" type="info">未入库</el-tag> <el-tag v-else-if="Number(scope.row.stockStatus) === 1" type="success">已入库</el-tag> <el-tag v-else type="info">-</el-tag> </template> </el-table-column> <el-table-column label="录入人" prop="entryPersonName" width="100" show-overflow-tooltip /> <el-table-column label="录入日期" prop="entryDate" width="120" show-overflow-tooltip /> <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip /> <el-table-column label="交付日期" prop="deliveryDate" width="120" show-overflow-tooltip /> <el-table-column label="备注" prop="remarks" width="200" show-overflow-tooltip /> <el-table-column fixed="right" label="操作" width="200" align="center"> <el-table-column label="录入人" prop="entryPersonName" width="100" show-overflow-tooltip /> <el-table-column label="录入日期" prop="entryDate" width="120" show-overflow-tooltip /> <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip /> <el-table-column label="交付日期" prop="deliveryDate" width="120" show-overflow-tooltip /> <el-table-column label="备注" prop="remarks" width="200" show-overflow-tooltip /> <el-table-column fixed="right" label="操作" width="200" align="center"> <template #default="scope"> <el-button link type="primary" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit">编辑</el-button> <el-button link type="primary" @click="openProcessFlowSelect(scope.row)" :disabled="!scope.row.isEdit">工艺路线</el-button> <el-button link type="primary" @click="downLoadFile(scope.row)">附件</el-button> <el-button link type="primary" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit">编辑</el-button> <el-button link type="primary" @click="openProcessFlowSelect(scope.row)" :disabled="!scope.row.isEdit">工艺路线</el-button> <el-button link type="primary" @click="downLoadFile(scope.row)">附件</el-button> </template> </el-table-column> </el-table> <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> </div> <FormDialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'" :width="'70%'" :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia"> <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> <FormDialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'" :width="'70%'" :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia"> <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> <!-- 报价单导入入口:放在表单顶部,选择后反显客户/业务员等 --> <el-row v-if="operationType === 'add'" style="margin-bottom: 10px;"> <el-col :span="24" style="text-align: right;"> <el-button type="primary" plain @click="openQuotationDialog"> <el-row v-if="operationType === 'add'" style="margin-bottom: 10px;"> <el-col :span="24" style="text-align: right;"> <el-button type="primary" plain @click="openQuotationDialog"> 从销售报价导入 </el-button> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="销售合同号:" prop="salesContractNo"> <el-input v-model="form.salesContractNo" placeholder="自动生成" clearable disabled /> <el-form-item label="销售合同号:" prop="salesContractNo"> <el-input v-model="form.salesContractNo" placeholder="自动生成" clearable disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="业务员:" prop="salesman"> <el-select v-model="form.salesman" placeholder="请选择" clearable :disabled="operationType === 'view'"> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" <el-form-item label="业务员:" prop="salesman"> <el-select v-model="form.salesman" placeholder="请选择" clearable :disabled="operationType === 'view'"> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> </el-select> </el-form-item> @@ -262,9 +430,17 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="客户名称:" prop="customerId"> <el-select v-model="form.customerId" filterable placeholder="请选择" clearable :disabled="operationType === 'view'"> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> <el-form-item label="客户名称:" prop="customerId"> <el-select v-model="form.customerId" filterable placeholder="请选择" clearable :disabled="operationType === 'view'"> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> {{ item.customerName + "——" + item.taxpayerIdentificationNumber }} @@ -273,65 +449,107 @@ </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="项目名称:" prop="projectName"> <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" /> <el-form-item label="项目名称:" prop="projectName"> <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="签订日期:" prop="executionDate"> <el-date-picker style="width: 100%" v-model="form.executionDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable :disabled="operationType === 'view'" /> <el-form-item label="签订日期:" prop="executionDate"> <el-date-picker style="width: 100%" v-model="form.executionDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="交货日期:" prop="deliveryDate"> <el-date-picker style="width: 100%" v-model="form.deliveryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable /> <el-form-item label="交货日期:" prop="deliveryDate"> <el-date-picker style="width: 100%" v-model="form.deliveryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="录入人:" prop="entryPerson"> <el-form-item label="录入人:" prop="entryPerson"> <el-select v-model="form.entryPerson" filterable default-first-option :reserve-keyword="false" placeholder="请选择" clearable @change="changs"> <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> :reserve-keyword="false" placeholder="请选择" clearable @change="changs"> <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="录入日期:" prop="entryDate"> <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable /> <el-form-item label="录入日期:" prop="entryDate"> <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable /> </el-form-item> </el-col> </el-row> <el-row> <el-form-item label="产品信息:" prop="entryDate"> <el-button v-if="operationType !== 'view'" <el-form-item label="产品信息:" prop="entryDate"> <el-button v-if="operationType !== 'view'" type="primary" :disabled="hasEditingProductRow()" @click="addProductInline" > @click="addProductInline"> 添加 </el-button> <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >删除</el-button> <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct">删除</el-button> </el-form-item> </el-row> <el-table :data="productData" border @selection-change="productSelected" show-summary <el-table :data="productData" border @selection-change="productSelected" show-summary :summary-method="summarizeMainTable"> <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" :selectable="(row) => !isProductShipped(row)" /> <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column label="产品大类" prop="productCategory" min-width="160"> <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column label="产品大类" prop="productCategory" min-width="160"> <template #default="scope"> <el-tree-select v-if="scope.row.__editing" <el-tree-select v-if="scope.row.__editing" v-model="scope.row.__productCategoryId" placeholder="请选择" clearable @@ -341,46 +559,49 @@ :render-after-expand="false" style="width: 100%" :filter-node-method="filterProductCategoryNode" @change="(val) => handleInlineProductCategoryChange(scope.row, val)" /> @change="(val) => handleInlineProductCategoryChange(scope.row, val)" /> <span v-else>{{ scope.row.productCategory ?? "" }}</span> </template> </el-table-column> <el-table-column label="规格型号" prop="specificationModel" min-width="160"> <el-table-column label="规格型号" prop="specificationModel" min-width="160"> <template #default="scope"> <el-select v-if="scope.row.__editing" <el-select v-if="scope.row.__editing" v-model="scope.row.productModelId" placeholder="请选择" clearable filterable style="width: 100%" @change="(val) => handleInlineProductModelChange(scope.row, val)" > <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> @change="(val) => handleInlineProductModelChange(scope.row, val)"> <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> </el-select> <span v-else>{{ scope.row.specificationModel ?? "" }}</span> </template> </el-table-column> <el-table-column label="厚度(mm)" prop="thickness" min-width="160"> <el-table-column label="厚度(mm)" prop="thickness" min-width="160"> <template #default="scope"> <el-input-number v-if="scope.row.__editing" <el-input-number v-if="scope.row.__editing" v-model="scope.row.thickness" :min="0" :step="0.000000000000001" :precision="15" style="width: 100%" placeholder="请输入" clearable /> clearable /> <span v-else>{{ scope.row.thickness ?? "" }}</span> </template> </el-table-column> <el-table-column label="宽(mm)" prop="width" min-width="160"> <el-table-column label="宽(mm)" prop="width" min-width="160"> <template #default="scope"> <el-input-number v-if="scope.row.__editing" <el-input-number v-if="scope.row.__editing" v-model="scope.row.width" :min="0" :step="1" @@ -389,15 +610,15 @@ placeholder="请输入" clearable @change="() => handleInlineSizeChange(scope.row)" @input="() => handleInlineSizeChange(scope.row)" /> @input="() => handleInlineSizeChange(scope.row)" /> <span v-else>{{ scope.row.width ?? "" }}</span> </template> </el-table-column> <el-table-column label="高(mm)" prop="height" min-width="160"> <el-table-column label="高(mm)" prop="height" min-width="160"> <template #default="scope"> <el-input-number v-if="scope.row.__editing" <el-input-number v-if="scope.row.__editing" v-model="scope.row.height" :min="0" :step="1" @@ -406,15 +627,15 @@ placeholder="请输入" clearable @change="() => handleInlineSizeChange(scope.row)" @input="() => handleInlineSizeChange(scope.row)" /> @input="() => handleInlineSizeChange(scope.row)" /> <span v-else>{{ scope.row.height ?? "" }}</span> </template> </el-table-column> <el-table-column label="结算单片面积(㎡)" prop="settlePieceArea" min-width="160"> <el-table-column label="结算单片面积(㎡)" prop="settlePieceArea" min-width="160"> <template #default="scope"> <el-input-number v-if="scope.row.__editing" <el-input-number v-if="scope.row.__editing" v-model="scope.row.settlePieceArea" :min="0" :step="0.00001" @@ -422,15 +643,15 @@ style="width: 100%" placeholder="请输入" clearable @change="() => handleInlineSettleAreaChange(scope.row)" /> @change="() => handleInlineSettleAreaChange(scope.row)" /> <span v-else>{{ scope.row.settlePieceArea ?? "" }}</span> </template> </el-table-column> <el-table-column label="数量" prop="quantity" min-width="150"> <el-table-column label="数量" prop="quantity" min-width="150"> <template #default="scope"> <el-input-number v-if="scope.row.__editing" <el-input-number v-if="scope.row.__editing" v-model="scope.row.quantity" :step="0.1" :min="0" @@ -439,29 +660,29 @@ placeholder="请输入" clearable @change="() => handleInlineQuantityChange(scope.row)" @input="() => handleInlineQuantityChange(scope.row)" /> @input="() => handleInlineQuantityChange(scope.row)" /> <span v-else>{{ scope.row.quantity ?? "" }}</span> </template> </el-table-column> <el-table-column label="面积(m²)" prop="actualTotalArea" min-width="160"> <el-table-column label="面积(m²)" prop="actualTotalArea" min-width="160"> <template #default="scope"> <el-input-number v-if="scope.row.__editing" <el-input-number v-if="scope.row.__editing" v-model="scope.row.actualTotalArea" :min="0" :step="0.00001" :precision="5" style="width: 100%" placeholder="自动计算" /> placeholder="自动计算" /> <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span> </template> </el-table-column> <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" min-width="140"> <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" min-width="140"> <template #default="scope"> <el-input-number v-if="scope.row.__editing" <el-input-number v-if="scope.row.__editing" :step="0.01" :min="0" :precision="2" @@ -470,310 +691,296 @@ placeholder="请输入" clearable @change="() => handleInlineUnitPriceChange(scope.row)" @input="() => handleInlineUnitPriceChange(scope.row)" /> @input="() => handleInlineUnitPriceChange(scope.row)" /> <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice ?? 0) }}</span> </template> </el-table-column> <el-table-column label="税率(%)" prop="taxRate" min-width="120"> <el-table-column label="税率(%)" prop="taxRate" min-width="120"> <template #default="scope"> <el-select v-if="scope.row.__editing" <el-select v-if="scope.row.__editing" v-model="scope.row.taxRate" placeholder="请选择" clearable style="width: 100%" @change="() => handleInlineTaxRateChange(scope.row)" > <el-option label="1" value="1" /> <el-option label="3" value="3" /> <el-option label="6" value="6" /> <el-option label="9" value="9" /> <el-option label="13" value="13" /> @change="() => handleInlineTaxRateChange(scope.row)"> <el-option label="1" value="1" /> <el-option label="3" value="3" /> <el-option label="6" value="6" /> <el-option label="9" value="9" /> <el-option label="13" value="13" /> </el-select> <span v-else>{{ scope.row.taxRate ?? "" }}</span> </template> </el-table-column> <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" min-width="120"/> <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" min-width="120"/> <el-table-column label="发票类型" prop="invoiceType" min-width="120"> <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" min-width="120" /> <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" min-width="120" /> <el-table-column label="发票类型" prop="invoiceType" min-width="120"> <template #default="scope"> <el-select v-if="scope.row.__editing" <el-select v-if="scope.row.__editing" v-model="scope.row.invoiceType" placeholder="请选择" clearable style="width: 100%" > <el-option label="增普票" value="增普票" /> <el-option label="增专票" value="增专票" /> style="width: 100%"> <el-option label="增普票" value="增普票" /> <el-option label="增专票" value="增专票" /> </el-select> <span v-else>{{ scope.row.invoiceType ?? "" }}</span> </template> </el-table-column> <el-table-column label="加工要求" prop="processRequirement" min-width="160" show-overflow-tooltip> <el-table-column label="加工要求" prop="processRequirement" min-width="160" show-overflow-tooltip> <template #default="scope"> <el-input v-if="scope.row.__editing" <el-input v-if="scope.row.__editing" v-model="scope.row.processRequirement" placeholder="请输入" clearable style="width: 100%" /> style="width: 100%" /> <span v-else>{{ scope.row.processRequirement ?? "" }}</span> </template> </el-table-column> <el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip> <el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip> <template #default="scope"> <el-input v-if="scope.row.__editing" <el-input v-if="scope.row.__editing" v-model="scope.row.remark" placeholder="请输入" clearable style="width: 100%" /> style="width: 100%" /> <span v-else>{{ scope.row.remark ?? "" }}</span> </template> </el-table-column> <el-table-column label="楼层编号" prop="floorCode" min-width="140" show-overflow-tooltip> <el-table-column label="楼层编号" prop="floorCode" min-width="140" show-overflow-tooltip> <template #default="scope"> <el-input v-if="scope.row.__editing" <el-input v-if="scope.row.__editing" v-model="scope.row.floorCode" placeholder="请输入" clearable style="width: 100%" /> style="width: 100%" /> <span v-else>{{ scope.row.floorCode ?? "" }}</span> </template> </el-table-column> <el-table-column label="重箱" prop="heavyBox" min-width="100"> <el-table-column label="重箱" prop="heavyBox" min-width="100"> <template #default="scope"> <el-input v-if="scope.row.__editing" <el-input v-if="scope.row.__editing" v-model="scope.row.heavyBox" placeholder="请输入" clearable style="width: 100%" /> style="width: 100%" /> <span v-else>{{ scope.row.heavyBox ?? "" }}</span> </template> </el-table-column> <el-table-column fixed="right" label="操作" min-width="220" align="center" v-if="operationType !== 'view'"> <el-table-column fixed="right" label="操作" min-width="220" align="center" v-if="operationType !== 'view'"> <template #default="scope"> <template v-if="scope.row.__editing"> <el-button link type="primary" size="small" @click="saveProductInline(scope.row, scope.$index)">保存</el-button> <el-button link type="danger" size="small" @click="cancelProductInline(scope.row, scope.$index)">取消</el-button> <el-popover :width="560" <el-button link type="primary" size="small" @click="saveProductInline(scope.row, scope.$index)">保存</el-button> <el-button link type="danger" size="small" @click="cancelProductInline(scope.row, scope.$index)">取消</el-button> <el-popover :width="560" trigger="click" :hide-after="0" :visible="scope.row.__otherAmountPopoverVisible" @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)" > @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)"> <template #reference> <el-button link <el-button link type="primary" size="small" @click="openOtherAmountInline(scope.row)" > @click="openOtherAmountInline(scope.row)"> 其他金额({{ (scope.row.salesProductProcessList || []).length || 0 }}) </el-button> </template> <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;"> <div style="font-weight: 600; color:#303133;"> 其他金额 </div> <el-button type="primary" plain size="small" @click="startAddOtherAmountForRow(scope.row)"> <el-button type="primary" plain size="small" @click="startAddOtherAmountForRow(scope.row)"> 新增 </el-button> </div> <div v-if="scope.row.__inlineOtherAmountAdding" <div v-if="scope.row.__inlineOtherAmountAdding" style="display:flex; flex-direction:column; gap: 8px; margin-bottom: 10px;" @click.stop > <el-select v-model="scope.row.__inlineOtherAmountAddId" @click.stop> <el-select v-model="scope.row.__inlineOtherAmountAddId" filterable clearable placeholder="请选择其他金额项目" style="width: 100%;" > <el-option v-for="item in otherAmountSelectOptions" style="width: 100%;"> <el-option v-for="item in otherAmountSelectOptions" :key="item.id" :label="item.processName" :value="item.id" /> :value="item.id" /> </el-select> <div style="display:flex; justify-content:flex-end; gap: 8px;"> <el-button size="small" @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null" > <el-button size="small" @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null"> 取消 </el-button> <el-button type="primary" <el-button type="primary" size="small" :disabled="scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''" @click="confirmAddOtherAmountForRow(scope.row)" > @click="confirmAddOtherAmountForRow(scope.row)"> 确认添加 </el-button> </div> </div> <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0" style="display:flex; flex-wrap:wrap; gap: 8px;" > <div v-for="(item, idx) in scope.row.salesProductProcessList" style="display:flex; flex-wrap:wrap; gap: 8px;"> <div v-for="(item, idx) in scope.row.salesProductProcessList" :key="String(item.id) + '_' + idx" style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);" > <el-tag type="info" style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);"> <el-tag type="info" style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> {{ item.processName }} </el-tag> <el-input-number v-model="item.quantity" <el-input-number v-model="item.quantity" :min="0" :step="1" :precision="0" style="width: 120px;" placeholder="数量" :disabled="operationType === 'view'" @change="handleOtherAmountQuantityChange(scope.row)" /> <el-button type="danger" link size="small" @click="removeOtherAmountAtForRow(scope.row, idx)"> @change="handleOtherAmountQuantityChange(scope.row)" /> <el-button type="danger" link size="small" @click="removeOtherAmountAtForRow(scope.row, idx)"> 删除 </el-button> </div> </div> <div v-else style="color:#909399; font-size: 13px;"> <div v-else style="color:#909399; font-size: 13px;"> 暂无其他金额 </div> </el-popover> </template> <template v-else> <el-button link <el-button link type="primary" size="small" :disabled="isProductShipped(scope.row)" @click="editProductInline(scope.row, scope.$index)" > @click="editProductInline(scope.row, scope.$index)"> 编辑 </el-button> <el-popover :width="560" <el-popover :width="560" trigger="click" :hide-after="0" :visible="scope.row.__otherAmountPopoverVisible" @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)" > @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)"> <template #reference> <el-button link <el-button link type="primary" size="small" :disabled="isProductShipped(scope.row)" @click="openOtherAmountInline(scope.row)" > @click="openOtherAmountInline(scope.row)"> 其他金额({{ (scope.row.salesProductProcessList || []).length || 0 }}) </el-button> </template> <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;"> <div style="font-weight: 600; color:#303133;"> 其他金额 </div> <el-button type="primary" <el-button type="primary" plain size="small" :disabled="isProductShipped(scope.row)" @click="startAddOtherAmountForRow(scope.row)" > @click="startAddOtherAmountForRow(scope.row)"> 新增 </el-button> </div> <div v-if="scope.row.__inlineOtherAmountAdding" <div v-if="scope.row.__inlineOtherAmountAdding" style="display:flex; flex-direction:column; gap: 8px; margin-bottom: 10px;" @click.stop > <el-select v-model="scope.row.__inlineOtherAmountAddId" @click.stop> <el-select v-model="scope.row.__inlineOtherAmountAddId" filterable clearable placeholder="请选择其他金额项目" style="width: 100%;" :disabled="isProductShipped(scope.row)" > <el-option v-for="item in otherAmountSelectOptions" :disabled="isProductShipped(scope.row)"> <el-option v-for="item in otherAmountSelectOptions" :key="item.id" :label="item.processName" :value="item.id" /> :value="item.id" /> </el-select> <div style="display:flex; justify-content:flex-end; gap: 8px;"> <el-button size="small" <el-button size="small" :disabled="isProductShipped(scope.row)" @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null" > @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null"> 取消 </el-button> <el-button type="primary" <el-button type="primary" size="small" :disabled="isProductShipped(scope.row) || scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''" @click="confirmAddOtherAmountForRow(scope.row)" > @click="confirmAddOtherAmountForRow(scope.row)"> 确认添加 </el-button> </div> </div> <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0" style="display:flex; flex-wrap:wrap; gap: 8px;" > <div v-for="(item, idx) in scope.row.salesProductProcessList" style="display:flex; flex-wrap:wrap; gap: 8px;"> <div v-for="(item, idx) in scope.row.salesProductProcessList" :key="String(item.id) + '_' + idx" style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);" > <el-tag type="info" style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);"> <el-tag type="info" style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> {{ item.processName }} </el-tag> <el-input-number v-model="item.quantity" <el-input-number v-model="item.quantity" :min="0" :step="1" :precision="0" style="width: 120px;" placeholder="数量" :disabled="operationType === 'view' || isProductShipped(scope.row)" @change="handleOtherAmountQuantityChange(scope.row)" /> <el-button type="danger" @change="handleOtherAmountQuantityChange(scope.row)" /> <el-button type="danger" link size="small" :disabled="isProductShipped(scope.row)" @click="removeOtherAmountAtForRow(scope.row, idx)" > @click="removeOtherAmountAtForRow(scope.row, idx)"> 删除 </el-button> </div> </div> <div v-else style="color:#909399; font-size: 13px;"> <div v-else style="color:#909399; font-size: 13px;"> 暂无其他金额 </div> </el-popover> @@ -783,33 +990,48 @@ </el-table> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="备注:" prop="remarks"> <el-input v-model="form.remarks" placeholder="请输入" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="客户备注:" prop="customerRemarks"> <el-input v-model="form.customerRemarks" <el-form-item label="备注:" prop="remarks"> <el-input v-model="form.remarks" placeholder="请输入" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="附件材料:" prop="salesLedgerFiles"> <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="customerRemarks"> <el-input v-model="form.customerRemarks" placeholder="请输入" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="附件材料:" prop="salesLedgerFiles"> <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 @@ -821,86 +1043,101 @@ </el-row> </el-form> </FormDialog> <!-- 从报价单导入(仅审批通过) --> <el-dialog v-model="quotationDialogVisible" <el-dialog v-model="quotationDialogVisible" title="选择审批通过的销售报价单" width="80%" :close-on-click-modal="false" > :close-on-click-modal="false"> <div style="margin-bottom: 12px; display:flex; gap: 12px; align-items:center;"> <el-input v-model="quotationSearchForm.quotationNo" <el-input v-model="quotationSearchForm.quotationNo" placeholder="请输入报价单号" clearable style="max-width: 260px;" @change="fetchQuotationList" /> <el-input v-model="quotationSearchForm.customer" @change="fetchQuotationList" /> <el-input v-model="quotationSearchForm.customer" placeholder="请输入客户名称" clearable style="max-width: 260px;" @change="fetchQuotationList" /> <el-button type="primary" @click="fetchQuotationList">搜索</el-button> @change="fetchQuotationList" /> <el-button type="primary" @click="fetchQuotationList">搜索</el-button> <el-button @click="resetQuotationSearch">重置</el-button> </div> <el-table :data="quotationList" <el-table :data="quotationList" border stripe v-loading="quotationLoading" height="420px" > <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column prop="quotationNo" label="报价单号" width="180" show-overflow-tooltip /> <el-table-column prop="customer" label="客户名称" min-width="220" show-overflow-tooltip /> <el-table-column prop="salesperson" label="业务员" width="120" show-overflow-tooltip /> <el-table-column prop="quotationDate" label="报价日期" width="140" /> <el-table-column prop="status" label="审批状态" width="120" align="center" /> <el-table-column prop="totalAmount" label="报价金额(元)" width="160" align="right"> height="420px"> <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column prop="quotationNo" label="报价单号" width="180" show-overflow-tooltip /> <el-table-column prop="customer" label="客户名称" min-width="220" show-overflow-tooltip /> <el-table-column prop="salesperson" label="业务员" width="120" show-overflow-tooltip /> <el-table-column prop="quotationDate" label="报价日期" width="140" /> <el-table-column prop="status" label="审批状态" width="120" align="center" /> <el-table-column prop="totalAmount" label="报价金额(元)" width="160" align="right"> <template #default="scope"> {{ Number(scope.row.totalAmount ?? 0).toFixed(2) }} </template> </el-table-column> <el-table-column fixed="right" label="操作" width="120" align="center"> <el-table-column fixed="right" label="操作" width="120" align="center"> <template #default="scope"> <el-button type="primary" link @click="applyQuotation(scope.row)">选择</el-button> <el-button type="primary" link @click="applyQuotation(scope.row)">选择</el-button> </template> </el-table-column> </el-table> <pagination v-show="quotationPage.total > 0" <pagination v-show="quotationPage.total > 0" :total="quotationPage.total" layout="total, sizes, prev, pager, next, jumper" :page="quotationPage.current" :limit="quotationPage.size" @pagination="quotationPaginationChange" /> @pagination="quotationPaginationChange" /> <template #footer> <el-button @click="quotationDialogVisible = false">关闭</el-button> </template> </el-dialog> <FormDialog v-model="productFormVisible" <FormDialog v-model="productFormVisible" :title="productOperationType === 'add' ? '新增产品' : '编辑产品'" :width="'60%'" :operation-type="productOperationType" @close="closeProductDia" @confirm="submitProduct" @cancel="closeProductDia"> <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef"> <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef"> <!-- 每行三个:产品大类/规格型号/单位 --> <el-row :gutter="30"> <el-col :span="8"> <el-form-item label="产品大类:" prop="productCategory"> <el-tree-select v-model="productForm.productCategory" <el-form-item label="产品大类:" prop="productCategory"> <el-tree-select v-model="productForm.productCategory" placeholder="请选择" clearable filterable @@ -909,246 +1146,271 @@ @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> style="width: 100%" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="规格型号:" prop="productModelId"> <el-select v-model="productForm.productModelId" <el-form-item label="规格型号:" prop="productModelId"> <el-select v-model="productForm.productModelId" placeholder="请选择" clearable @change="getProductModel" filterable style="width: 100%" > <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> style="width: 100%"> <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="厚度:" prop="thickness"> <el-input-number v-model="productForm.thickness" <el-form-item label="厚度:" prop="thickness"> <el-input-number v-model="productForm.thickness" :min="0" :step="0.000000000000001" :precision="15" style="width: 100%;" placeholder="请输入" clearable /> clearable /> </el-form-item> </el-col> </el-row> <!-- 每行三个:税率/含税单价/数量 --> <el-row :gutter="30"> <el-col :span="8"> <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice"> <el-input-number :step="0.01" <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice"> <el-input-number :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%" :precision="2" placeholder="请输入" clearable @change="calculateFromUnitPrice" /> @change="calculateFromUnitPrice" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="税率(%):" prop="taxRate"> <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%"> <el-option label="1" value="1" /> <el-option label="3" value="3" /> <el-option label="6" value="6" /> <el-option label="9" value="9" /> <el-option label="13" value="13" /> <el-form-item label="税率(%):" prop="taxRate"> <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%"> <el-option label="1" value="1" /> <el-option label="3" value="3" /> <el-option label="6" value="6" /> <el-option label="9" value="9" /> <el-option label="13" value="13" /> </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="数量:" prop="quantity"> <el-input-number :step="0.1" <el-form-item label="数量:" prop="quantity"> <el-input-number :step="0.1" :min="0" v-model="productForm.quantity" placeholder="请输入" clearable :precision="2" @change="() => { calculateFromQuantity(); recalcAreaTotals(); }" style="width: 100%" /> style="width: 100%" /> </el-form-item> </el-col> </el-row> <!-- 每行三个:含税总价/不含税总价/发票类型 --> <el-row :gutter="30"> <el-col :span="8"> <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice"> <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromTotalPrice" /> <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice"> <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromTotalPrice" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="不含税总价(元):" prop="taxExclusiveTotalPrice"> <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromExclusiveTotalPrice" /> <el-form-item label="不含税总价(元):" prop="taxExclusiveTotalPrice"> <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromExclusiveTotalPrice" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="发票类型:" prop="invoiceType"> <el-select v-model="productForm.invoiceType" placeholder="请选择" clearable style="width: 100%"> <el-option label="增普票" value="增普票" /> <el-option label="增专票" value="增专票" /> <el-form-item label="发票类型:" prop="invoiceType"> <el-select v-model="productForm.invoiceType" placeholder="请选择" clearable style="width: 100%"> <el-option label="增普票" value="增普票" /> <el-option label="增专票" value="增专票" /> </el-select> </el-form-item> </el-col> </el-row> <!-- 每行三个:宽/高/实际单片面积 --> <el-row :gutter="30"> <el-col :span="8"> <el-form-item label="宽(mm):" prop="width"> <el-input-number v-model="productForm.width" <el-form-item label="宽(mm):" prop="width"> <el-input-number v-model="productForm.width" :min="0" :step="1" :precision="2" style="width: 100%" placeholder="请输入宽(mm)" clearable @change="recalcAreaFromWidthHeight" /> @change="recalcAreaFromWidthHeight" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="高(mm):" prop="height"> <el-input-number v-model="productForm.height" <el-form-item label="高(mm):" prop="height"> <el-input-number v-model="productForm.height" :min="0" :step="1" :precision="2" style="width: 100%" placeholder="请输入高(mm)" clearable @change="recalcAreaFromWidthHeight" /> @change="recalcAreaFromWidthHeight" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="周长(cm):" prop="perimeter"> <el-input-number v-model="productForm.perimeter" <el-form-item label="周长(cm):" prop="perimeter"> <el-input-number v-model="productForm.perimeter" :min="0" :step="0.01" :precision="2" style="width: 100%" placeholder="请输入" clearable :disabled="true" /> :disabled="true" /> </el-form-item> </el-col> </el-row> <!-- 每行三个:实际单片面积/实际总面积/结算单片面积 --> <el-row :gutter="30"> <el-col :span="8"> <el-form-item label="实际单片面积(㎡):" prop="actualPieceArea"> <el-input-number v-model="productForm.actualPieceArea" <el-form-item label="实际单片面积(㎡):" prop="actualPieceArea"> <el-input-number v-model="productForm.actualPieceArea" :min="0" :step="0.00001" :precision="5" style="width: 100%" placeholder="请输入" clearable @change="recalcAreaTotals" /> @change="recalcAreaTotals" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="实际总面积(㎡):" prop="actualTotalArea"> <el-input-number v-model="productForm.actualTotalArea" <el-form-item label="实际总面积(㎡):" prop="actualTotalArea"> <el-input-number v-model="productForm.actualTotalArea" :min="0" :step="0.00001" :precision="5" style="width: 100%" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="结算单片面积(㎡):" prop="settlePieceArea"> <el-input-number v-model="productForm.settlePieceArea" :min="0" :step="0.00001" :precision="5" style="width: 100%" placeholder="请输入" clearable /> @change="() => { recalcAreaTotals(); calculateFromUnitPrice(true); }" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="结算单片面积(㎡):" prop="settlePieceArea"> <el-input-number v-model="productForm.settlePieceArea" <el-form-item label="结算总面积(㎡):" prop="settleTotalArea"> <el-input-number v-model="productForm.settleTotalArea" :min="0" :step="0.00001" :precision="5" style="width: 100%" placeholder="请输入" clearable @change="() => { recalcAreaTotals(); calculateFromUnitPrice(true); }" /> clearable /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="结算总面积(㎡):" prop="settleTotalArea"> <el-input-number v-model="productForm.settleTotalArea" <el-form-item label="结算总面积(㎡):" prop="settleTotalArea"> <el-input-number v-model="productForm.settleTotalArea" :min="0" :step="0.00001" :precision="5" style="width: 100%" placeholder="请输入" clearable /> clearable /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="结算总面积(㎡):" prop="settleTotalArea"> <el-input-number v-model="productForm.settleTotalArea" :min="0" :step="0.00001" :precision="5" style="width: 100%" <el-form-item label="重箱:" prop="heavyBox"> <el-input v-model="productForm.heavyBox" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="重箱:" prop="heavyBox"> <el-input v-model="productForm.heavyBox" placeholder="请输入" clearable @change="calculateFromExclusiveTotalPrice" /> @change="calculateFromExclusiveTotalPrice" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="楼层编号:" prop="floorCode"> <el-input v-model="productForm.floorCode" placeholder="请输入楼层编号" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> <el-form-item label="楼层编号:" prop="floorCode"> <el-input v-model="productForm.floorCode" placeholder="请输入楼层编号" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <!-- 其他金额(占满一行:等同于 3 列网格的整行) --> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="加工要求:" prop="processRequirement"> <el-input v-model="productForm.processRequirement" placeholder="请输入加工要求" clearable /> <el-form-item label="加工要求:" prop="processRequirement"> <el-input v-model="productForm.processRequirement" placeholder="请输入加工要求" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="备注:" prop="remark"> <el-input v-model="productForm.remark" placeholder="请输入备注" clearable /> <el-form-item label="备注:" prop="remark"> <el-input v-model="productForm.remark" placeholder="请输入备注" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item> @@ -1158,58 +1420,49 @@ <div style="color:#909399; font-size: 13px; flex: 1;"> 已选择 {{ productForm?.salesProductProcessList?.length || 0 }} 项 </div> <el-button v-if="operationType !== 'view'" <el-button v-if="operationType !== 'view'" type="primary" plain size="small" @click="startAddOtherAmount" > @click="startAddOtherAmount"> 新增 </el-button> </div> </template> <div style="display:flex; flex-direction:column; gap: 12px;"> <div v-if="Array.isArray(productForm?.salesProductProcessList) && productForm.salesProductProcessList.length > 0" style="display:flex; flex-wrap:wrap; gap: 12px; align-items:flex-start;" > <div v-for="(item, index) in productForm.salesProductProcessList" style="display:flex; flex-wrap:wrap; gap: 12px; align-items:flex-start;"> <div v-for="(item, index) in productForm.salesProductProcessList" :key="String(item.id) + '_' + index" style="display:flex; gap: 10px; align-items:center; padding: 10px 12px; border: 1px solid #ebeef5; border-radius: 8px; box-sizing:border-box; min-width: 0;" :style="getOtherAmountCardFlexStyle()" > :style="getOtherAmountCardFlexStyle()"> <div style="flex: 1; min-width: 0;"> <el-tag type="info" style="width: 100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> <el-tag type="info" style="width: 100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> {{ item.processName }} </el-tag> </div> <div style="flex: 1;"> <el-input-number v-model="item.quantity" <el-input-number v-model="item.quantity" :min="0" :step="1" :precision="0" style="width: 100%;" placeholder="请输入数量" :disabled="operationType === 'view'" @change="calculateFromUnitPrice(true)" /> @change="calculateFromUnitPrice(true)" /> </div> <el-button v-if="operationType !== 'view'" <el-button v-if="operationType !== 'view'" type="danger" link size="small" @click="removeOtherAmountAt(index)" > @click="removeOtherAmountAt(index)"> 删除 </el-button> </div> </div> <div v-else style="color:#909399; font-size: 13px;"> <div v-else style="color:#909399; font-size: 13px;"> 暂无其他金额 </div> </div> @@ -1219,55 +1472,43 @@ </el-form> </FormDialog> <!-- 其他金额:新增弹框 --> <el-dialog v-model="otherAmountAddDialogVisible" <el-dialog v-model="otherAmountAddDialogVisible" title="新增其他金额" width="520px" :close-on-click-modal="false" > :close-on-click-modal="false"> <div style="padding: 4px 0 10px;"> <div style="font-size: 14px; color: #606266; margin-bottom: 10px;"> 请选择要新增的其他金额项目 </div> <el-select v-model="otherAmountAddId" <el-select v-model="otherAmountAddId" filterable clearable placeholder="请选择其他金额项目" style="width: 100%;" :disabled="operationType === 'view'" > <el-option v-for="item in otherAmountSelectOptions" :disabled="operationType === 'view'"> <el-option v-for="item in otherAmountSelectOptions" :key="item.id" :label="item.processName" :value="item.id" /> :value="item.id" /> </el-select> </div> <template #footer> <el-button @click="cancelAddOtherAmount">取消</el-button> <el-button type="primary" <el-button type="primary" @click="confirmAddOtherAmount" :disabled="operationType === 'view' || otherAmountAddId === null || otherAmountAddId === undefined || otherAmountAddId === ''" > :disabled="operationType === 'view' || otherAmountAddId === null || otherAmountAddId === undefined || otherAmountAddId === ''"> 确认添加 </el-button> </template> </el-dialog> <!-- 导入弹窗 --> <FormDialog v-model="importUpload.open" <FormDialog v-model="importUpload.open" :title="importUpload.title" :width="'600px'" @close="importUpload.open = false" @confirm="submitImportFile" @cancel="importUpload.open = false" > <el-upload ref="importUploadRef" @cancel="importUpload.open = false"> <el-upload ref="importUploadRef" :limit="1" accept=".xlsx,.xls" :action="importUpload.url" @@ -1278,8 +1519,7 @@ :on-progress="importUpload.onProgress" :on-change="importUpload.onChange" :auto-upload="false" drag > drag> <i class="el-icon-upload"></i> <div class="el-upload__text"> 将文件拖到此处,或<em>点击上传</em> @@ -1287,77 +1527,73 @@ <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> <!-- 附件列表弹窗 --> <FileListDialog ref="fileListRef" <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" title="附件列表" /> title="附件列表" /> <!-- 发货弹框 --> <el-dialog v-model="deliveryFormVisible" <el-dialog v-model="deliveryFormVisible" title="发货信息" width="40%" @close="closeDeliveryDia" > <el-form :model="deliveryForm" label-width="120px" label-position="top" :rules="deliveryRules" ref="deliveryFormRef"> @close="closeDeliveryDia"> <el-form :model="deliveryForm" label-width="120px" label-position="top" :rules="deliveryRules" ref="deliveryFormRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="发货类型:" prop="type"> <el-select v-model="deliveryForm.type" <el-form-item label="发货类型:" prop="type"> <el-select v-model="deliveryForm.type" placeholder="请选择发货类型" style="width: 100%" > <el-option label="货车" value="货车" /> <el-option label="快递" value="快递" /> style="width: 100%"> <el-option label="货车" value="货车" /> <el-option label="快递" value="快递" /> </el-select> </el-form-item> </el-col> </el-row> <!-- 审批人选择(仿协同审批里的审批人节点选择) --> <el-row> <el-col :span="24"> <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" <div v-for="(node, index) in approverNodes" :key="node.id" style="margin-right: 20px; text-align: center; margin-bottom: 10px;" > style="margin-right: 20px; text-align: center; margin-bottom: 10px;"> <div> <span>审批人</span> → </div> <el-select v-model="node.userId" <el-select v-model="node.userId" placeholder="选择人员" filterable style="width: 140px; margin-bottom: 8px;" > <el-option v-for="user in userList" style="width: 140px; margin-bottom: 8px;"> <el-option v-for="user in userListApprove" :key="user.userId" :label="user.nickName" :value="user.userId" /> :label="user.userName" :value="user.userId" /> </el-select> <div> <el-button type="danger" <el-button type="danger" @click="removeApproverNode(index)" v-if="approverNodes.length > 1" >删除</el-button> v-if="approverNodes.length > 1">删除</el-button> </div> </div> </div> @@ -1367,12 +1603,12 @@ </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitDelivery">确认发货</el-button> <el-button type="primary" @click="submitDelivery">确认发货</el-button> <el-button @click="closeDeliveryDia">取消</el-button> </div> </template> </el-dialog> </div> </template> @@ -1384,9 +1620,10 @@ import { ElMessageBox, ElMessage } from "element-plus"; import { ArrowDown } from "@element-plus/icons-vue"; import useUserStore from "@/store/modules/user"; import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js"; import { userListNoPage } from "@/api/system/user.js"; import FileListDialog from '@/components/Dialog/FileListDialog.vue'; import FormDialog from '@/components/Dialog/FormDialog.vue'; import FileListDialog from "@/components/Dialog/FileListDialog.vue"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import OtherAmountMaintenanceButton from "./components/OtherAmountMaintenanceButton.vue"; import ProcessFlowMaintenanceButton from "./components/ProcessFlowMaintenanceButton.vue"; import ProcessFlowConfigSelectDialog from "./components/ProcessFlowConfigSelectDialog.vue"; @@ -1428,6 +1665,7 @@ const selectedRows = ref([]); const productSelectedRows = ref([]); const userList = ref([]); const userListApprove = ref([]); const customerOption = ref([]); const productOptions = ref([]); const modelOptions = ref([]); @@ -1535,39 +1773,67 @@ // 产品行内编辑:只允许同时编辑一行 const editingProductRow = ref(null); const ensureProductRowDefaults = (row) => { const ensureProductRowDefaults = row => { if (!row || typeof row !== "object") return; if (!Array.isArray(row.salesProductProcessList)) row.salesProductProcessList = []; if (row.__otherAmountPopoverVisible === undefined || row.__otherAmountPopoverVisible === null) row.__otherAmountPopoverVisible = false; if (row.__inlineOtherAmountAdding === undefined || row.__inlineOtherAmountAdding === null) row.__inlineOtherAmountAdding = false; if (row.__inlineOtherAmountAddId === undefined) row.__inlineOtherAmountAddId = null; if (!Array.isArray(row.salesProductProcessList)) row.salesProductProcessList = []; if ( row.__otherAmountPopoverVisible === undefined || row.__otherAmountPopoverVisible === null ) row.__otherAmountPopoverVisible = false; if ( row.__inlineOtherAmountAdding === undefined || row.__inlineOtherAmountAdding === null ) row.__inlineOtherAmountAdding = false; if (row.__inlineOtherAmountAddId === undefined) row.__inlineOtherAmountAddId = null; if (row.width === undefined || row.width === null) row.width = 0; if (row.height === undefined || row.height === null) row.height = 0; if (row.perimeter === undefined || row.perimeter === null) row.perimeter = 0; if (row.actualPieceArea === undefined || row.actualPieceArea === null) row.actualPieceArea = 0; if (row.actualTotalArea === undefined || row.actualTotalArea === null) row.actualTotalArea = 0; if (row.settlePieceArea === undefined || row.settlePieceArea === null) row.settlePieceArea = 0; if (row.settleTotalArea === undefined || row.settleTotalArea === null) row.settleTotalArea = 0; if (row.processRequirement === undefined || row.processRequirement === null) row.processRequirement = ""; if (row.actualPieceArea === undefined || row.actualPieceArea === null) row.actualPieceArea = 0; if (row.actualTotalArea === undefined || row.actualTotalArea === null) row.actualTotalArea = 0; if (row.settlePieceArea === undefined || row.settlePieceArea === null) row.settlePieceArea = 0; if (row.settleTotalArea === undefined || row.settleTotalArea === null) row.settleTotalArea = 0; if (row.processRequirement === undefined || row.processRequirement === null) row.processRequirement = ""; if (row.remark === undefined || row.remark === null) row.remark = ""; if (row.floorCode === undefined || row.floorCode === null) row.floorCode = ""; if (row.invoiceType === undefined || row.invoiceType === null) row.invoiceType = ""; if (row.invoiceType === undefined || row.invoiceType === null) row.invoiceType = ""; if (row.taxRate === undefined || row.taxRate === null) row.taxRate = ""; if (row.quantity === undefined || row.quantity === null) row.quantity = 0; if (row.taxInclusiveUnitPrice === undefined || row.taxInclusiveUnitPrice === null) row.taxInclusiveUnitPrice = 0; if (row.taxInclusiveTotalPrice === undefined || row.taxInclusiveTotalPrice === null) row.taxInclusiveTotalPrice = 0; if (row.taxExclusiveTotalPrice === undefined || row.taxExclusiveTotalPrice === null) row.taxExclusiveTotalPrice = 0; if ( row.taxInclusiveUnitPrice === undefined || row.taxInclusiveUnitPrice === null ) row.taxInclusiveUnitPrice = 0; if ( row.taxInclusiveTotalPrice === undefined || row.taxInclusiveTotalPrice === null ) row.taxInclusiveTotalPrice = 0; if ( row.taxExclusiveTotalPrice === undefined || row.taxExclusiveTotalPrice === null ) row.taxExclusiveTotalPrice = 0; }; const stopOtherEditingRows = () => { (productData.value || []).forEach((r) => { (productData.value || []).forEach(r => { if (r && r.__editing) r.__editing = false; }); editingProductRow.value = null; }; const hasEditingProductRow = () => { return (productData.value || []).some((r) => r && r.__editing); return (productData.value || []).some(r => r && r.__editing); }; const addProductInline = async () => { @@ -1626,23 +1892,31 @@ await fetchOtherAmountSelectOptions(true); ensureProductRowDefaults(row); // 产品大类 tree-select 回显:名称 -> id row.__productCategoryId = findNodeIdByLabel(productOptions.value, row.productCategory); row.__productCategoryId = findNodeIdByLabel( productOptions.value, row.productCategory ); // 兼容后端字段命名(保持原逻辑) row.actualPieceArea = row?.actualPieceArea ?? row?.actual_piece_area ?? 0; row.actualTotalArea = row?.actualTotalArea ?? row?.actual_total_area ?? 0; row.settlePieceArea = row?.settlePieceArea ?? row?.settle_piece_area ?? 0; row.settleTotalArea = row?.settleTotalArea ?? row?.settle_total_area ?? 0; row.processRequirement = row?.processRequirement ?? row?.process_requirement ?? ""; row.processRequirement = row?.processRequirement ?? row?.process_requirement ?? ""; row.remark = row?.remark ?? row?.remarks ?? ""; row.floorCode = row?.floorCode ?? row?.floor_code ?? ""; row.processFlowConfigId = row?.processFlowConfigId ?? row?.process_flow_config_id ?? null; row.perimeter = row?.perimeter ?? row?.heavyBoxPerimeter ?? row?.heavyboxPerimeter ?? 0; row.processFlowConfigId = row?.processFlowConfigId ?? row?.process_flow_config_id ?? null; row.perimeter = row?.perimeter ?? row?.heavyBoxPerimeter ?? row?.heavyboxPerimeter ?? 0; row.thickness = row?.thickness; row.salesProductProcessList = normalizeOtherAmountsFromRow(row); mergeOtherAmountOptionsBySelection(row.salesProductProcessList); row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); row.salesProductProcessList = fillOtherAmountProcessName( row.salesProductProcessList ); // 备份用于取消 row.__backup = JSON.parse(JSON.stringify(row)); @@ -1652,12 +1926,17 @@ // 根据产品大类名称反查 tree 节点 id,并加载规格型号列表 try { const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); const categoryId = findNodeIdByLabel(options, row.productCategory); if (categoryId) { const models = await modelList({ id: categoryId }); modelOptions.value = models || []; const currentModel = (modelOptions.value || []).find((m) => m.model === row.specificationModel); const currentModel = (modelOptions.value || []).find( m => m.model === row.specificationModel ); if (currentModel) row.productModelId = currentModel.id; } } catch (e) { @@ -1669,7 +1948,7 @@ recalcAreaFromWidthHeight(); }; const validateInlineProductRow = (row) => { const validateInlineProductRow = row => { if (!row) return false; if (!row.productCategory) { proxy.$modal.msgWarning("请选择产品大类"); @@ -1696,7 +1975,11 @@ if (!validateInlineProductRow(row)) return; // 厚度精度处理 if (row.thickness !== null && row.thickness !== undefined && row.thickness !== "") { if ( row.thickness !== null && row.thickness !== undefined && row.thickness !== "" ) { row.thickness = Number(Number(row.thickness).toFixed(15)); } @@ -1707,17 +1990,23 @@ row.quantity = Number(row.quantity ?? 0) || 0; // 规范化其他金额提交结构 row.salesProductProcessList = (Array.isArray(row.salesProductProcessList) ? row.salesProductProcessList : []) .map((it) => ({ row.salesProductProcessList = ( Array.isArray(row.salesProductProcessList) ? row.salesProductProcessList : [] ) .map(it => ({ id: it?.id, processName: it?.processName ?? "", unitPrice: Number(it?.unitPrice ?? 0) || 0, quantity: Number(it?.quantity ?? 0) || 0, })) .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); .filter(it => it.id !== null && it.id !== undefined && it.id !== ""); // 规格型号:根据 productModelId 回填名称 const model = (modelOptions.value || []).find((m) => String(m.id) === String(row.productModelId)); const model = (modelOptions.value || []).find( m => String(m.id) === String(row.productModelId) ); if (model?.model) row.specificationModel = model.model; if (operationType.value === "edit") { @@ -1730,9 +2019,11 @@ delete payload.__tempKey; await addOrUpdateSalesLedgerProduct(payload); proxy.$modal.msgSuccess("提交成功"); await getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => { await getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then( res => { productData.value = res.productData; }); } ); } else { // 新增台账:仅在本地 productData 生效,最终随台账一起提交 row.__isNew = false; @@ -1751,7 +2042,7 @@ const restored = JSON.parse(JSON.stringify(row.__backup)); // 保留 id 与状态字段 const keepId = row.id; Object.keys(row).forEach((k) => delete row[k]); Object.keys(row).forEach(k => delete row[k]); Object.assign(row, restored); row.id = keepId; row.__editing = false; @@ -1760,7 +2051,7 @@ stopOtherEditingRows(); }; const openOtherAmountInline = async (row) => { const openOtherAmountInline = async row => { if (!row) return; if (operationType.value === "view") return; if (isProductShipped(row)) { @@ -1772,7 +2063,9 @@ otherAmountAddTargetRow.value = row; await fetchOtherAmountSelectOptions(true); mergeOtherAmountOptionsBySelection(row.salesProductProcessList); row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); row.salesProductProcessList = fillOtherAmountProcessName( row.salesProductProcessList ); // 只做数据准备与打开浮层(新增由浮层内按钮触发) row.__otherAmountPopoverVisible = true; }; @@ -1780,7 +2073,7 @@ const keepOtherAmountPopoverOpenKey = ref(null); const keepOtherAmountPopoverOpenUntil = ref(0); const getOtherAmountRowKey = (row) => String(row?.__tempKey ?? row?.id ?? ""); const getOtherAmountRowKey = row => String(row?.__tempKey ?? row?.id ?? ""); const lockOtherAmountPopoverOpen = (row, durationMs = 1200) => { const key = getOtherAmountRowKey(row); @@ -1808,7 +2101,7 @@ row.__otherAmountPopoverVisible = shouldKeepOpen; }; const startAddOtherAmountForRow = async (row) => { const startAddOtherAmountForRow = async row => { if (!row) return; if (operationType.value === "view") return; if (isProductShipped(row)) { @@ -1819,22 +2112,27 @@ productForm.value = row; await fetchOtherAmountSelectOptions(true); mergeOtherAmountOptionsBySelection(row.salesProductProcessList); row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); row.salesProductProcessList = fillOtherAmountProcessName( row.salesProductProcessList ); row.__inlineOtherAmountAddId = null; row.__inlineOtherAmountAdding = true; row.__otherAmountPopoverVisible = true; }; const confirmAddOtherAmountForRow = (row) => { const confirmAddOtherAmountForRow = row => { if (!row) return; ensureProductRowDefaults(row); productForm.value = row; const selectedId = row.__inlineOtherAmountAddId; if (selectedId === null || selectedId === undefined || selectedId === "") return; const opt = otherAmountSelectOptions.value.find((o) => String(o.id) === String(selectedId)); if (selectedId === null || selectedId === undefined || selectedId === "") return; const opt = otherAmountSelectOptions.value.find( o => String(o.id) === String(selectedId) ); if (!opt) return; const exists = (row.salesProductProcessList ?? []).some( (it) => String(it?.id) === String(opt.id) it => String(it?.id) === String(opt.id) ); if (exists) { proxy.$modal.msgWarning("该其他金额项目已添加"); @@ -1860,7 +2158,7 @@ removeOtherAmountAt(index); }; const handleOtherAmountQuantityChange = (row) => { const handleOtherAmountQuantityChange = row => { if (!row) return; productForm.value = row; calculateFromUnitPrice(true); @@ -1882,7 +2180,7 @@ getProductModel(val); }; const handleInlineSizeChange = (row) => { const handleInlineSizeChange = row => { if (!row) return; productForm.value = row; recalcPerimeterFromWidthHeight(); @@ -1890,27 +2188,27 @@ recalcAreaTotals(); }; const handleInlineUnitPriceChange = (row) => { const handleInlineUnitPriceChange = row => { if (!row) return; productForm.value = row; calculateFromUnitPrice(); recalcAreaTotals(); }; const handleInlineQuantityChange = (row) => { const handleInlineQuantityChange = row => { if (!row) return; productForm.value = row; calculateFromQuantity(); recalcAreaTotals(); }; const handleInlineTaxRateChange = (row) => { const handleInlineTaxRateChange = row => { if (!row) return; productForm.value = row; calculateFromTaxRate(); }; const handleInlineSettleAreaChange = (row) => { const handleInlineSettleAreaChange = row => { if (!row) return; productForm.value = row; recalcAreaTotals(); @@ -1946,9 +2244,7 @@ type: "货车", // 货车, 快递 }, deliveryRules: { type: [ { required: true, message: "请选择发货类型", trigger: "change" } ] type: [{ required: true, message: "请选择发货类型", trigger: "change" }], }, }); const { deliveryForm, deliveryRules } = toRefs(deliveryFormData); @@ -1969,7 +2265,7 @@ const res = await salesLedgerProductProcessList(params); const records = res?.records ?? res?.data?.records ?? []; otherAmountSelectOptions.value = records.map((item) => ({ otherAmountSelectOptions.value = records.map(item => ({ id: item.id, processName: item.processName ?? "", unitPrice: item.unitPrice ?? 0, @@ -1979,7 +2275,7 @@ } }; const normalizeOtherAmountsFromRow = (row) => { const normalizeOtherAmountsFromRow = row => { if (!row) return []; const raw = row.other_amounts ?? @@ -1995,7 +2291,7 @@ // 情况1:后端直接返回 [{id, processName}...] if (typeof raw[0] === "object") { return raw .map((it) => { .map(it => { const id = it?.id ?? it?.processId ?? it?.otherAmountId ?? null; const quantity = Number( @@ -2011,23 +2307,25 @@ quantity, }; }) .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); .filter(it => it.id !== null && it.id !== undefined && it.id !== ""); } // 情况2:后端只返回 ids: [1,2,3] return raw .map((id) => ({ .map(id => ({ id, processName: "", quantity: 0, })) .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); .filter(it => it.id !== null && it.id !== undefined && it.id !== ""); }; const mergeOtherAmountOptionsBySelection = (selected) => { const mergeOtherAmountOptionsBySelection = selected => { const list = Array.isArray(selected) ? selected : []; for (const s of list) { const exists = otherAmountSelectOptions.value.some((o) => String(o.id) === String(s.id)); const exists = otherAmountSelectOptions.value.some( o => String(o.id) === String(s.id) ); if (!exists) { otherAmountSelectOptions.value.push({ id: s.id, @@ -2037,10 +2335,12 @@ } }; const fillOtherAmountProcessName = (selected) => { const fillOtherAmountProcessName = selected => { const list = Array.isArray(selected) ? selected : []; return list.map((s) => { const opt = otherAmountSelectOptions.value.find((o) => String(o.id) === String(s.id)); return list.map(s => { const opt = otherAmountSelectOptions.value.find( o => String(o.id) === String(s.id) ); return { id: s.id, processName: opt?.processName ?? s.processName ?? "", @@ -2089,14 +2389,17 @@ keepOtherAmountPopoverOpenUntil.value = 0; }; const handleOtherAmountSelected = (id) => { const handleOtherAmountSelected = id => { const selectedId = id ?? otherAmountAddId.value; if (selectedId === null || selectedId === undefined || selectedId === "") return; const opt = otherAmountSelectOptions.value.find((o) => String(o.id) === String(selectedId)); if (selectedId === null || selectedId === undefined || selectedId === "") return; const opt = otherAmountSelectOptions.value.find( o => String(o.id) === String(selectedId) ); if (!opt) return; const exists = (productForm.value?.salesProductProcessList ?? []).some( (it) => String(it?.id) === String(opt.id) it => String(it?.id) === String(opt.id) ); if (exists) { proxy.$modal.msgWarning("该其他金额项目已添加"); @@ -2119,7 +2422,7 @@ const rowKey = otherAmountAddTargetRowKey.value; if (rowKey) { const matchedRow = (productData.value || []).find( (it) => String(it?.__tempKey ?? it?.id ?? "") === rowKey it => String(it?.__tempKey ?? it?.id ?? "") === rowKey ); if (matchedRow) targetRow = matchedRow; } @@ -2141,7 +2444,7 @@ handleOtherAmountSelected(otherAmountAddId.value); }; const removeOtherAmountAt = (index) => { const removeOtherAmountAt = index => { if (operationType.value === "view") return; if (!Array.isArray(productForm.value?.salesProductProcessList)) return; productForm.value.salesProductProcessList.splice(index, 1); @@ -2154,7 +2457,7 @@ const addApproverNode = () => { approverNodes.value.push({ id: nextApproverId++, userId: null }); }; const removeApproverNode = (index) => { const removeApproverNode = index => { approverNodes.value.splice(index, 1); }; @@ -2166,8 +2469,8 @@ url: import.meta.env.VITE_APP_BASE_API + "/sales/ledger/import", headers: { Authorization: "Bearer " + getToken() }, isUploading: false, beforeUpload: (file) => { const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls'); beforeUpload: file => { const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls"); const isLt10M = file.size / 1024 / 1024 < 10; if (!isExcel) { proxy.$modal.msgError("上传文件只能是 xlsx/xls 格式!"); @@ -2180,13 +2483,13 @@ return true; }, onChange: (file, fileList) => { console.log('文件状态改变', file, fileList); console.log("文件状态改变", file, fileList); }, onProgress: (event, file, fileList) => { console.log('上传中...', event.percent); console.log("上传中...", event.percent); }, onSuccess: (response, file, fileList) => { console.log('上传成功', response, file, fileList); console.log("上传成功", response, file, fileList); importUpload.isUploading = false; if (response.code === 200) { proxy.$modal.msgSuccess("导入成功"); @@ -2200,13 +2503,13 @@ } }, onError: (error, file, fileList) => { console.error('上传失败', error, file, fileList); console.error("上传失败", error, file, fileList); importUpload.isUploading = false; proxy.$modal.msgError("导入失败,请重试"); }, }); const changeDaterange = (value) => { const changeDaterange = value => { if (value) { searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); @@ -2228,7 +2531,7 @@ expandedRowKeys.value = []; getList(); }; const paginationChange = (obj) => { const paginationChange = obj => { page.current = obj.page; page.size = obj.limit; getList(); @@ -2242,12 +2545,13 @@ delete params.entryDate; // 查询客户名称与新增保持一致:先选 customerId,再映射为 customerName 查询 const selectedCustomer = (customerOption.value || []).find( (item) => String(item?.id ?? "") === String(params.customerId ?? "") item => String(item?.id ?? "") === String(params.customerId ?? "") ); if (selectedCustomer?.customerName) { params.customerName = String(selectedCustomer.customerName).trim(); } else { const cn = params.customerName != null ? String(params.customerName).trim() : ""; const cn = params.customerName != null ? String(params.customerName).trim() : ""; if (cn) { params.customerName = cn; } else { @@ -2256,10 +2560,10 @@ } delete params.customerId; return ledgerListPage(params) .then((res) => { .then(res => { tableLoading.value = false; tableData.value = res.records; tableData.value.map((item) => { tableData.value.map(item => { item.children = []; }); total.value = res.total; @@ -2308,7 +2612,7 @@ }; // 打开“工艺路线配置”选择弹窗(必须显式选择) const openProcessFlowSelect = async (ledgerRow) => { const openProcessFlowSelect = async ledgerRow => { if (!ledgerRow) return; if (!ledgerRow.isEdit) return; @@ -2320,16 +2624,9 @@ try { const res = await getSaleProcessBindInfo(ledgerRow.id); const info = res?.data ?? res ?? {}; const boundId = info?.processRouteId ?? info?.routeId ?? info?.id ?? null; const boundId = info?.processRouteId ?? info?.routeId ?? info?.id ?? null; const boundName = info?.processRouteName ?? info?.routeName ?? info?.name ?? ""; info?.processRouteName ?? info?.routeName ?? info?.name ?? ""; processFlowSelectBoundRouteId.value = boundId; processFlowSelectBoundRouteName.value = boundName; processFlowSelectDefaultRouteId.value = boundId; @@ -2344,7 +2641,7 @@ }; // 绑定工艺路线到当前台账数据 const handleProcessFlowSelectConfirm = async (routeId) => { const handleProcessFlowSelectConfirm = async routeId => { const ledgerRow = processFlowSelectLedgerRow.value; if (!ledgerRow?.id) return; @@ -2352,7 +2649,12 @@ if (!finalRouteId) return; const oldRouteId = processFlowSelectBoundRouteId.value; if (oldRouteId !== null && oldRouteId !== undefined && oldRouteId !== "" && String(oldRouteId) !== String(finalRouteId)) { if ( oldRouteId !== null && oldRouteId !== undefined && oldRouteId !== "" && String(oldRouteId) !== String(finalRouteId) ) { try { await ElMessageBox.confirm( "该订单已绑定工艺路线,是否确定更换?", @@ -2389,7 +2691,7 @@ // 获取产品大类tree数据 const getProductOptions = () => { // 返回 Promise,便于在编辑产品时等待加载完成 return productTreeList().then((res) => { return productTreeList().then(res => { productOptions.value = convertIdToValue(res); return productOptions.value; }); @@ -2398,7 +2700,7 @@ return parseFloat(cellValue).toFixed(2); }; // 获取tree子数据 const getModels = (value) => { const getModels = value => { // 产品大类变化时,重置规格型号与厚度,避免旧值残留 productForm.value.productModelId = null; productForm.value.specificationModel = ""; @@ -2411,12 +2713,12 @@ } productForm.value.productCategory = findNodeById(productOptions.value, value); modelList({ id: value }).then((res) => { modelList({ id: value }).then(res => { modelOptions.value = res || []; }); }; const getProductModel = (value) => { const index = modelOptions.value.findIndex((item) => item.id === value); const getProductModel = value => { const index = modelOptions.value.findIndex(item => item.id === value); if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model; const selectedModel = modelOptions.value[index]; @@ -2426,7 +2728,9 @@ selectedModel?.thick ?? null; productForm.value.thickness = modelThickness === null || modelThickness === undefined || modelThickness === "" modelThickness === null || modelThickness === undefined || modelThickness === "" ? null : Number(modelThickness); } else { @@ -2436,7 +2740,9 @@ }; const filterProductCategoryNode = (value, data) => { if (!value) return true; return String(data?.label || "").toLowerCase().includes(String(value).toLowerCase()); return String(data?.label || "") .toLowerCase() .includes(String(value).toLowerCase()); }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { @@ -2453,7 +2759,7 @@ return null; // 没有找到节点,返回null }; function convertIdToValue(data, level = 0) { return data.map((item) => { return data.map(item => { const { id, children, ...rest } = item; const hasChildren = Array.isArray(children) && children.length > 0; const newItem = { @@ -2483,12 +2789,12 @@ return null; } // 表格选择数据 const handleSelectionChange = (selection) => { const handleSelectionChange = selection => { // 过滤掉子数据 selectedRows.value = selection.filter((item) => item.children !== undefined); selectedRows.value = selection.filter(item => item.children !== undefined); console.log("selection", selectedRows.value); }; const productSelected = (selectedRows) => { const productSelected = selectedRows => { productSelectedRows.value = selectedRows; }; const expandedRowKeys = ref([]); @@ -2497,8 +2803,8 @@ if (expandedRows.length > 0) { expandedRowKeys.value = []; try { productList({ salesLedgerId: row.id, type: 1 }).then((res) => { const index = tableData.value.findIndex((item) => item.id === row.id); productList({ salesLedgerId: row.id, type: 1 }).then(res => { const index = tableData.value.findIndex(item => item.id === row.id); if (index > -1) { tableData.value[index].children = res.data; } @@ -2514,22 +2820,22 @@ // 添加表行类名方法 const tableRowClassName = ({ row }) => { if (!row.deliveryDate) return ''; if (row.isFh) return ''; if (!row.deliveryDate) return ""; if (row.isFh) return ""; const diff = row.deliveryDaysDiff; if (diff === 15) { return 'yellow'; return "yellow"; } else if (diff === 10) { return 'pink'; return "pink"; } else if (diff === 2) { return 'purple'; return "purple"; } else if (diff < 2) { return 'red'; return "red"; } }; // 主表合计方法 const summarizeMainTable = (param) => { const summarizeMainTable = param => { return proxy.summarizeTable(param, [ "contractAmount", "taxInclusiveTotalPrice", @@ -2537,7 +2843,7 @@ ]); }; // 子表合计方法 const summarizeChildrenTable = (param) => { const summarizeChildrenTable = param => { return proxy.summarizeTable(param, [ "taxInclusiveUnitPrice", "taxInclusiveTotalPrice", @@ -2552,7 +2858,7 @@ selectedQuotation.value = null; let userLists = await userListNoPage(); userList.value = userLists.data; customerList().then((res) => { customerList().then(res => { customerOption.value = res; }); form.value.entryPerson = userStore.id; @@ -2564,11 +2870,12 @@ form.value.customerRemarks = ""; } else { currentId.value = row.id; getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => { getSalesLedgerWithProducts({ id: row.id, type: 1 }).then(res => { form.value = { ...res }; form.value.entryPerson = Number(res.entryPerson); // 字段名兼容:后端可能返回 customer_remarks form.value.customerRemarks = res?.customerRemarks ?? res?.customer_remarks ?? ""; form.value.customerRemarks = res?.customerRemarks ?? res?.customer_remarks ?? ""; productData.value = form.value.productData; fileList.value = form.value.salesLedgerFiles; }); @@ -2581,7 +2888,9 @@ // }); form.value.entryDate = getCurrentDate(); // 设置默认录入日期为当前日期 if (type === "add") { form.value.deliveryDate = dayjs(form.value.entryDate).add(7, "day").format("YYYY-MM-DD"); form.value.deliveryDate = dayjs(form.value.entryDate) .add(7, "day") .format("YYYY-MM-DD"); } dialogFormVisible.value = true; }; @@ -2630,14 +2939,14 @@ }; // 报价单弹框分页切换 const quotationPaginationChange = (obj) => { const quotationPaginationChange = obj => { quotationPage.current = obj.page; quotationPage.size = obj.limit; fetchQuotationList(); }; // 选中报价单后回填到台账表单 const applyQuotation = (row) => { const applyQuotation = row => { if (!row) return; selectedQuotation.value = row; @@ -2646,9 +2955,13 @@ // 客户名称 -> customerId const qCustomerName = String(row.customer || "").trim(); const customer = (customerOption.value || []).find((c) => { const customer = (customerOption.value || []).find(c => { const name = String(c.customerName || "").trim(); return name === qCustomerName || name.includes(qCustomerName) || qCustomerName.includes(name); return ( name === qCustomerName || name.includes(qCustomerName) || qCustomerName.includes(name) ); }); if (customer?.id) { form.value.customerId = customer.id; @@ -2659,13 +2972,20 @@ // 产品信息映射:报价 products -> 台账 productData const products = Array.isArray(row.products) ? row.products : []; productData.value = products.map((p) => { productData.value = products.map(p => { const quantity = Number(p.quantity ?? 0) || 0; const unitPrice = Number(p.unitPrice ?? 0) || 0; const settlePieceArea = Number(p.settlePieceArea ?? 0) || 1; const taxRate = "13"; // 默认 13%,便于直接提交(如需可在产品中自行修改) const taxInclusiveTotalPrice = (unitPrice * settlePieceArea * quantity).toFixed(2); const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(taxInclusiveTotalPrice, taxRate); const taxInclusiveTotalPrice = ( unitPrice * settlePieceArea * quantity ).toFixed(2); const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( taxInclusiveTotalPrice, taxRate ); return { // 台账字段 productCategory: p.product || p.productName || "", @@ -2727,26 +3047,36 @@ if (operationType.value === "edit") { let ids = []; ids.push(file.id); delLedgerFile(ids).then((res) => { delLedgerFile(ids).then(res => { proxy.$modal.msgSuccess("删除成功"); }); } } // 提交表单 const submitForm = () => { proxy.$refs["formRef"].validate((valid) => { proxy.$refs["formRef"].validate(valid => { if (valid) { console.log('productData.value--', productData.value) console.log("productData.value--", productData.value); // 行内编辑未保存时不允许提交,避免脏数据/临时字段进入后端 const hasEditingRow = (productData.value || []).some((r) => r && r.__editing); const hasEditingRow = (productData.value || []).some( r => r && r.__editing ); if (hasEditingRow) { proxy.$modal.msgWarning("产品信息存在未保存的编辑行,请先保存或取消"); return; } if (productData.value !== null && productData.value.length > 0) { const cleanedProducts = (productData.value || []).map((p) => { const cleanedProducts = (productData.value || []).map(p => { if (!p || typeof p !== "object") return p; const { __editing, __isNew, __backup, __productCategoryId, __tempKey, __otherAmountPopoverVisible, ...rest } = p; const { __editing, __isNew, __backup, __productCategoryId, __tempKey, __otherAmountPopoverVisible, ...rest } = p; rest.taxRate = Number(rest.taxRate ?? 0) || 0; rest.quantity = Number(rest.quantity ?? 0) || 0; return rest; @@ -2758,13 +3088,13 @@ } let tempFileIds = []; if (fileList.value !== null && fileList.value.length > 0) { tempFileIds = fileList.value.map((item) => item.tempId); tempFileIds = fileList.value.map(item => item.tempId); } form.value.tempFileIds = tempFileIds; form.value.type = 1; const submitPayload = { ...form.value }; delete submitPayload.paymentMethod; addOrUpdateSalesLedger(submitPayload).then((res) => { addOrUpdateSalesLedger(submitPayload).then(res => { proxy.$modal.msgSuccess("提交成功"); closeDia(); getList(); @@ -2798,10 +3128,14 @@ productForm.value = { ...row }; // 字段命名兼容:优先驼峰(如 actualPieceArea),兼容后端可能返回下划线(如 actual_piece_area) productForm.value.actualPieceArea = row?.actualPieceArea ?? row?.actual_piece_area ?? 0; productForm.value.actualTotalArea = row?.actualTotalArea ?? row?.actual_total_area ?? 0; productForm.value.settlePieceArea = row?.settlePieceArea ?? row?.settle_piece_area ?? 0; productForm.value.settleTotalArea = row?.settleTotalArea ?? row?.settle_total_area ?? 0; productForm.value.actualPieceArea = row?.actualPieceArea ?? row?.actual_piece_area ?? 0; productForm.value.actualTotalArea = row?.actualTotalArea ?? row?.actual_total_area ?? 0; productForm.value.settlePieceArea = row?.settlePieceArea ?? row?.settle_piece_area ?? 0; productForm.value.settleTotalArea = row?.settleTotalArea ?? row?.settle_total_area ?? 0; // 加工要求/备注兼容:后端可能使用其它命名 productForm.value.processRequirement = @@ -2819,20 +3153,25 @@ // 后端直接返回 thickness productForm.value.thickness = row?.thickness; productForm.value.salesProductProcessList = normalizeOtherAmountsFromRow(row); productForm.value.salesProductProcessList = normalizeOtherAmountsFromRow(row); productIndex.value = index; // 编辑时根据产品大类名称反查 tree 节点 id,并加载规格型号列表 try { const options = productOptions.value && productOptions.value.length > 0 const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); const categoryId = findNodeIdByLabel(options, productForm.value.productCategory); const categoryId = findNodeIdByLabel( options, productForm.value.productCategory ); if (categoryId) { const models = await modelList({ id: categoryId }); modelOptions.value = models || []; // 根据当前规格型号名称反查并设置 productModelId,便于下拉框显示已选值 const currentModel = (modelOptions.value || []).find( (m) => m.model === productForm.value.specificationModel m => m.model === productForm.value.specificationModel ); if (currentModel) { productForm.value.productModelId = currentModel.id; @@ -2849,10 +3188,14 @@ // 回显“其他金额”多选:先拉取下拉选项,再补齐 processName await fetchOtherAmountSelectOptions(true); mergeOtherAmountOptionsBySelection(productForm.value.salesProductProcessList); productForm.value.salesProductProcessList = fillOtherAmountProcessName(productForm.value.salesProductProcessList); mergeOtherAmountOptionsBySelection( productForm.value.salesProductProcessList ); productForm.value.salesProductProcessList = fillOtherAmountProcessName( productForm.value.salesProductProcessList ); } else { getProductOptions() getProductOptions(); // 新增时下拉选项加载一次即可 fetchOtherAmountSelectOptions(true); } @@ -2860,11 +3203,16 @@ }; // 提交产品表单 const submitProduct = () => { proxy.$refs["productFormRef"].validate((valid) => { proxy.$refs["productFormRef"].validate(valid => { if (valid) { // 厚度保留 15 位小数,避免由于浮点计算/输入导致精度偏差 if (productForm.value.thickness !== null && productForm.value.thickness !== undefined) { productForm.value.thickness = Number(Number(productForm.value.thickness).toFixed(15)); if ( productForm.value.thickness !== null && productForm.value.thickness !== undefined ) { productForm.value.thickness = Number( Number(productForm.value.thickness).toFixed(15) ); } // 面积/总计字段在提交前兜底计算一次 @@ -2873,17 +3221,18 @@ productForm.value.taxRate = Number(productForm.value.taxRate ?? 0) || 0; productForm.value.quantity = Number(productForm.value.quantity ?? 0) || 0; // 其他金额只提交 {id, processName, quantity}(后端字段:salesProductProcessList) productForm.value.salesProductProcessList = (Array.isArray(productForm.value.salesProductProcessList) productForm.value.salesProductProcessList = ( Array.isArray(productForm.value.salesProductProcessList) ? productForm.value.salesProductProcessList : [] ) .map((it) => ({ .map(it => ({ id: it?.id, processName: it?.processName ?? "", unitPrice: Number(it?.unitPrice ?? 0) || 0, quantity: Number(it?.quantity ?? 0) || 0, })) .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); .filter(it => it.id !== null && it.id !== undefined && it.id !== ""); if (operationType.value === "edit") { submitProductEdit(); @@ -2891,7 +3240,7 @@ if(productOperationType.value === "add"){ productData.value.push({ ...productForm.value }); }else{ productData.value[productIndex.value] = { ...productForm.value } productData.value[productIndex.value] = { ...productForm.value }; } closeProductDia(); } @@ -2900,11 +3249,11 @@ }; const submitProductEdit = () => { productForm.value.salesLedgerId = currentId.value; productForm.value.type = 1 addOrUpdateSalesLedgerProduct(productForm.value).then((res) => { productForm.value.type = 1; addOrUpdateSalesLedgerProduct(productForm.value).then(res => { proxy.$modal.msgSuccess("提交成功"); closeProductDia(); getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => { getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then(res => { productData.value = res.productData; }); }); @@ -2917,15 +3266,17 @@ } // 检查是否有已发货或审核通过的产品 const shippedProducts = productSelectedRows.value.filter(row => isProductShipped(row)); const shippedProducts = productSelectedRows.value.filter(row => isProductShipped(row) ); if (shippedProducts.length > 0) { proxy.$modal.msgWarning("已发货或审核通过的产品不能删除"); return; } if (operationType.value === "add") { productSelectedRows.value.forEach((selectedRow) => { const index = productData.value.findIndex((product) => { productSelectedRows.value.forEach(selectedRow => { const index = productData.value.findIndex(product => { if (!product || !selectedRow) return false; // 新增行 id 为空时,用临时 key 定位 if (product.id != null && selectedRow.id != null) { @@ -2944,7 +3295,7 @@ } else { let ids = []; if (productSelectedRows.value.length > 0) { ids = productSelectedRows.value.map((item) => item.id); ids = productSelectedRows.value.map(item => item.id); } ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", { confirmButtonText: "确认", @@ -2952,11 +3303,11 @@ type: "warning", }) .then(() => { delProduct(ids).then((res) => { delProduct(ids).then(res => { proxy.$modal.msgSuccess("删除成功"); closeProductDia(); getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then( (res) => { res => { productData.value = res.productData; } ); @@ -3009,7 +3360,7 @@ }); }; /** 判断单个产品是否已发货(根据shippingStatus判断,已发货或审核通过不可编辑和删除) */ const isProductShipped = (product) => { const isProductShipped = product => { if (!product) return false; const status = String(product.shippingStatus || "").trim(); // 如果发货状态是"已发货"或"审核通过",则不可编辑和删除 @@ -3017,14 +3368,16 @@ }; /** 判断销售订单下是否存在已发货/发货完成的产品(不可删除) */ const hasShippedProducts = (products) => { const hasShippedProducts = products => { if (!products || !products.length) return false; return products.some((p) => { return products.some(p => { const status = String(p.shippingStatus || "").trim(); // 有发货日期或车牌号视为已发货 if (p.shippingDate || p.shippingCarNumber) return true; // 已进行发货、发货完成、已发货 均不可删除 return status === "已进行发货" || status === "发货完成" || status === "已发货"; return ( status === "已进行发货" || status === "发货完成" || status === "已发货" ); }); }; @@ -3034,12 +3387,13 @@ proxy.$modal.msgWarning("请选择数据"); return; } const ids = selectedRows.value.map((item) => item.id); const ids = selectedRows.value.map(item => item.id); // 检查是否有已进行发货或发货完成的销售订单,若有则不允许删除 const cannotDeleteNames = []; for (const row of selectedRows.value) { let products = row.children && row.children.length > 0 ? row.children : null; let products = row.children && row.children.length > 0 ? row.children : null; if (!products) { try { const res = await productList({ salesLedgerId: row.id, type: 1 }); @@ -3053,7 +3407,9 @@ } } if (cannotDeleteNames.length > 0) { proxy.$modal.msgWarning("已进行发货或发货完成的销售订单不能删除:" + cannotDeleteNames.join("、")); proxy.$modal.msgWarning( "已进行发货或发货完成的销售订单不能删除:" + cannotDeleteNames.join("、") ); return; } @@ -3063,7 +3419,7 @@ type: "warning", }) .then(() => { delLedger(ids).then((res) => { delLedger(ids).then(res => { proxy.$modal.msgSuccess("删除成功"); getList(); }); @@ -3073,15 +3429,22 @@ }); }; const handlePrintCommand = async (command) => { if (command !== "finishedProcessCard" && command !== "salesOrder" && command !== "salesDeliveryNote") return; const handlePrintCommand = async command => { if ( command !== "finishedProcessCard" && command !== "salesOrder" && command !== "salesDeliveryNote" ) return; if (command === "salesDeliveryNote") { if (selectedRows.value.length === 0) { proxy.$modal.msgWarning("请至少选择一条销售台账数据进行打印"); return; } const customerNames = Array.from( new Set(selectedRows.value.map((item) => String(item?.customerName ?? "").trim())) new Set( selectedRows.value.map(item => String(item?.customerName ?? "").trim()) ) ); if (customerNames.length > 1) { proxy.$modal.msgWarning("仅支持相同客户名称的销售台账合并发货打印"); @@ -3096,8 +3459,8 @@ const selectedId = selectedRow?.id; if (command === "salesDeliveryNote") { const selectedIds = selectedRows.value .map((item) => item?.id) .filter((id) => id !== null && id !== undefined && id !== ""); .map(item => item?.id) .filter(id => id !== null && id !== undefined && id !== ""); if (selectedIds.length !== selectedRows.value.length) { proxy.$modal.msgWarning("当前选择数据存在缺少ID的记录,无法打印"); return; @@ -3201,10 +3564,14 @@ productForm.value.taxInclusiveUnitPrice * settlePieceArea, productForm.value.quantity ); const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { const otherAmountTotal = ( productForm.value.salesProductProcessList || [] ).reduce((total, item) => { return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); }, 0); productForm.value.taxInclusiveTotalPrice = (parseFloat(basePrice) + otherAmountTotal).toFixed(2); productForm.value.taxInclusiveTotalPrice = ( parseFloat(basePrice) + otherAmountTotal ).toFixed(2); if (productForm.value.taxRate) { // 不含税总价计算 productForm.value.taxExclusiveTotalPrice = @@ -3237,7 +3604,9 @@ } // 周长 = (宽 + 高) * 2,单位从 mm 转为 cm:/10 productForm.value.perimeter = Number((((width + height) * 2) / 10).toFixed(2)); productForm.value.perimeter = Number( (((width + height) * 2) / 10).toFixed(2) ); }; const recalcAreaFromWidthHeight = () => { @@ -3256,7 +3625,10 @@ productForm.value.settlePieceArea = 0; } productForm.value.settleTotalArea = Number( ((Number(productForm.value.settlePieceArea ?? 0) || 0) * (Number(productForm.value.quantity ?? 0) || 0)).toFixed(5) ( (Number(productForm.value.settlePieceArea ?? 0) || 0) * (Number(productForm.value.quantity ?? 0) || 0) ).toFixed(5) ); return; } @@ -3287,7 +3659,9 @@ isCalculating.value = true; // 计算含税单价 = (含税总价 - 其他金额总和) / 数量 const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { const otherAmountTotal = ( productForm.value.salesProductProcessList || [] ).reduce((total, item) => { return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); }, 0); const basePrice = totalPrice - otherAmountTotal; @@ -3313,7 +3687,9 @@ // } if (isCalculating.value) return; const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice); const exclusiveTotalPrice = parseFloat( productForm.value.taxExclusiveTotalPrice ); const quantity = parseFloat(productForm.value.quantity); const taxRate = parseFloat(productForm.value.taxRate); @@ -3329,7 +3705,9 @@ productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2); // 计算含税单价 = (含税总价 - 其他金额总和) / 数量 const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { const otherAmountTotal = ( productForm.value.salesProductProcessList || [] ).reduce((total, item) => { return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); }, 0); const basePrice = inclusiveTotalPrice - otherAmountTotal; @@ -3358,10 +3736,14 @@ // 计算含税总价 = 单价 * 结算面积 * 数量 + 其他金额总和 const basePrice = unitPrice * settlePieceArea * quantity; const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { const otherAmountTotal = ( productForm.value.salesProductProcessList || [] ).reduce((total, item) => { return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); }, 0); productForm.value.taxInclusiveTotalPrice = (basePrice + otherAmountTotal).toFixed(2); productForm.value.taxInclusiveTotalPrice = ( basePrice + otherAmountTotal ).toFixed(2); // 如果有税率,计算不含税总价 if (productForm.value.taxRate) { @@ -3395,10 +3777,14 @@ // 计算含税总价 = 单价 * 结算面积 * 数量 + 其他金额总和 const basePrice = unitPrice * settlePieceArea * quantity; const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { const otherAmountTotal = ( productForm.value.salesProductProcessList || [] ).reduce((total, item) => { return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); }, 0); productForm.value.taxInclusiveTotalPrice = (basePrice + otherAmountTotal).toFixed(2); productForm.value.taxInclusiveTotalPrice = ( basePrice + otherAmountTotal ).toFixed(2); // 如果有税率,计算不含税总价 if (productForm.value.taxRate) { @@ -3420,7 +3806,9 @@ // } if (isCalculating.value) return; const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice); const inclusiveTotalPrice = parseFloat( productForm.value.taxInclusiveTotalPrice ); const taxRate = parseFloat(productForm.value.taxRate); if (!inclusiveTotalPrice || !taxRate) { @@ -3431,10 +3819,7 @@ // 计算不含税总价 productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( inclusiveTotalPrice, taxRate ); proxy.calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate); isCalculating.value = false; }; @@ -3442,62 +3827,62 @@ * 获取发货状态文本 * @param row 行数据 */ const getShippingStatusText = (row) => { const getShippingStatusText = row => { // 如果已发货(有发货日期或车牌号),显示"已发货" if (row.shippingDate || row.shippingCarNumber) { return '已发货'; return "已发货"; } // 获取发货状态字段 const status = row.shippingStatus; // 如果状态为空或未定义,默认为"待发货" if (status === null || status === undefined || status === '') { return '待发货'; if (status === null || status === undefined || status === "") { return "待发货"; } // 状态是字符串 const statusStr = String(status).trim(); const statusTextMap = { '待发货': '待发货', '待审核': '待审核', '审核中': '审核中', '审核拒绝': '审核拒绝', '审核通过': '审核通过', '已发货': '已发货' 待发货: "待发货", 待审核: "待审核", 审核中: "审核中", 审核拒绝: "审核拒绝", 审核通过: "审核通过", 已发货: "已发货", }; return statusTextMap[statusStr] || '待发货'; return statusTextMap[statusStr] || "待发货"; }; /** * 获取发货状态标签类型(颜色) * @param row 行数据 */ const getShippingStatusType = (row) => { const getShippingStatusType = row => { // 如果已发货(有发货日期或车牌号),显示绿色 if (row.shippingDate || row.shippingCarNumber) { return 'success'; return "success"; } // 获取发货状态字段 const status = row.shippingStatus; // 如果状态为空或未定义,默认为灰色(待发货) if (status === null || status === undefined || status === '') { return 'info'; if (status === null || status === undefined || status === "") { return "info"; } // 状态是字符串 const statusStr = String(status).trim(); const typeTextMap = { '待发货': 'info', '待审核': 'info', '审核中': 'warning', '审核拒绝': 'danger', '审核通过': 'success', '已发货': 'success' 待发货: "info", 待审核: "info", 审核中: "warning", 审核拒绝: "danger", 审核通过: "success", 已发货: "success", }; return typeTextMap[statusStr] || 'info'; return typeTextMap[statusStr] || "info"; }; /** @@ -3505,8 +3890,7 @@ * 只有在产品状态是充足,发货状态是待发货和审核拒绝的时候才可以发货 * @param row 行数据 */ const canShip = (row) => { const canShip = row => { // 产品状态必须是充足(approveStatus === 1) if (row.approveStatus !== 1) { return false; @@ -3532,8 +3916,8 @@ } // 发货状态必须是"待发货"或"审核拒绝" const statusStr = shippingStatus ? String(shippingStatus).trim() : ''; return statusStr === '待发货' || statusStr === '审核拒绝'; const statusStr = shippingStatus ? String(shippingStatus).trim() : ""; return statusStr === "待发货" || statusStr === "审核拒绝"; }; const handleBulkDelivery = async () => { @@ -3543,7 +3927,7 @@ } // 只允许【未发货/审批失败】进入发货流程 const canDeliveryLedgers = selectedRows.value.filter((r) => { const canDeliveryLedgers = selectedRows.value.filter(r => { const status = Number(r.deliveryStatus); return status === 1 || status === 3; }); @@ -3553,7 +3937,9 @@ } // 已发货台账:弹窗提醒,不能再次发货(4 视为已发货) const shippedLedgers = selectedRows.value.filter((r) => Number(r.deliveryStatus) === 4); const shippedLedgers = selectedRows.value.filter( r => Number(r.deliveryStatus) === 4 ); if (shippedLedgers.length === selectedRows.value.length) { try { await ElMessageBox.alert("所选销售台账均已发货,不能再次发货。", "提示", { @@ -3580,7 +3966,9 @@ } } const customerNames = selectedRows.value.map((r) => String(r.customerName || "").trim()); const customerNames = selectedRows.value.map(r => String(r.customerName || "").trim() ); const uniqueCustomers = Array.from(new Set(customerNames)); // 客户名称不一致不允许发货 @@ -3607,11 +3995,12 @@ try { const targets = []; for (const ledger of selectedRows.value) { //如果已经是“审批中(2)”或“已发货(4)”,则跳过,不允许重复操作 const status = Number(ledger.deliveryStatus); if (status === 2 || status === 4) { console.warn(`台账编号 ${ledger.salesContractNo} 状态为 ${status},跳过发货`); console.warn( `台账编号 ${ledger.salesContractNo} 状态为 ${status},跳过发货` ); continue; } @@ -3621,7 +4010,7 @@ products = res?.data || []; } catch (error) { products = []; console.error('请求发生异常', error); console.error("请求发生异常", error); } for (const product of products) { if (!canShip(product)) continue; @@ -3652,18 +4041,18 @@ * * @param row 下载文件的相关信息对象 */ const fileListRef = ref(null) const fileListDialogVisible = ref(false) const downLoadFile = (row) => { getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => { const fileListRef = ref(null); const fileListDialogVisible = ref(false); const downLoadFile = row => { getSalesLedgerWithProducts({ id: row.id, type: 1 }).then(res => { if (fileListRef.value) { fileListRef.value.open(res.salesLedgerFiles) fileListRef.value.open(res.salesLedgerFiles); } }); } }; // 打开发货弹框(单条) const openDeliveryForm = (row) => { const openDeliveryForm = row => { // 只允许【未发货/审批失败】发货;已发货/审批中不允许 const status = Number(row.deliveryStatus); if (status !== 1 && status !== 3) { @@ -3683,7 +4072,7 @@ // 提交发货表单 const submitDelivery = () => { proxy.$refs["deliveryFormRef"].validate((valid) => { proxy.$refs["deliveryFormRef"].validate(valid => { if (valid) { // 审批人必填校验(所有节点都要选人) const hasEmptyApprover = approverNodes.value.some(node => !node.userId); @@ -3691,7 +4080,9 @@ proxy.$modal.msgError("请为所有审批节点选择审批人!"); return; } const approveUserIds = approverNodes.value.map(node => node.userId).join(","); const approveUserIds = approverNodes.value .map(node => node.userId) .join(","); // 保存当前展开的行ID,以便发货后重新加载子表格数据 const currentExpandedKeys = [...expandedRowKeys.value]; @@ -3702,7 +4093,9 @@ } // 按台账维度去重,每个 salesLedgerId 只调用一次发货接口 const uniqueLedgerIds = [...new Set(targets.map((item) => item.salesLedgerId).filter(Boolean))]; const uniqueLedgerIds = [ ...new Set(targets.map(item => item.salesLedgerId).filter(Boolean)), ]; const run = async () => { for (const salesLedgerId of uniqueLedgerIds) { @@ -3722,13 +4115,17 @@ getList().then(() => { // 如果之前有展开的行,重新加载这些行的子表格数据 if (currentExpandedKeys.length > 0) { const loadPromises = currentExpandedKeys.map((ledgerId) => { return productList({ salesLedgerId: ledgerId, type: 1 }).then((res) => { const index = tableData.value.findIndex((item) => item.id === ledgerId); const loadPromises = currentExpandedKeys.map(ledgerId => { return productList({ salesLedgerId: ledgerId, type: 1 }).then( res => { const index = tableData.value.findIndex( item => item.id === ledgerId ); if (index > -1) { tableData.value[index].children = res.data; } }); } ); }); Promise.all(loadPromises).then(() => { expandedRowKeys.value = currentExpandedKeys; @@ -3756,12 +4153,15 @@ }; onMounted(() => { getList(); customerList().then((res) => { customerList().then(res => { customerOption.value = res; }); userListNoPage().then(res => { userList.value = res.data; }) }); approveUserList({ approveType: 7 }).then(res => { userListApprove.value = res.data; }); getCurrentFactoryName(); }); </script> @@ -3772,19 +4172,19 @@ } ::v-deep .yellow { background-color: #FAF0DE; background-color: #faf0de; } ::v-deep .pink { background-color: #FAE1DE; background-color: #fae1de; } ::v-deep .red { background-color: #FAE1DE; background-color: #fae1de; } ::v-deep .purple{ background-color: #F4DEFA; background-color: #f4defa; } .other-amount-select { src/views/salesManagement/salesQuotation/index.vue
@@ -2,22 +2,28 @@ <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" <el-input v-model="searchForm.quotationNo" placeholder="请输入报价单号" clearable @keyup.enter="handleSearch" > @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 }} @@ -33,78 +39,123 @@ <!-- </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" <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"> 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" <pagination :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" :page="pagination.currentPage" :limit="pagination.pageSize" @pagination="handleCurrentChange" /> @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-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form"> <!-- 基本信息 --> <el-card class="form-card" shadow="hover"> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><Document /></el-icon> <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-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 }} @@ -113,9 +164,15 @@ </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" <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> @@ -123,50 +180,58 @@ </el-row> <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="报价日期" prop="quotationDate"> <el-date-picker v-model="form.quotationDate" <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 /> clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="有效期至" prop="validDate"> <el-date-picker v-model="form.validDate" <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 /> 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 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"> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><UserFilled /></el-icon> <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 type="primary" size="small" @click="addApproverNode" class="header-btn"> <el-icon> <Plus /> </el-icon> 新增节点 </el-button> </div> @@ -176,37 +241,31 @@ <el-col :span="24"> <el-form-item> <div class="approver-nodes-container"> <div v-for="(node, index) in approverNodes" <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item" > 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-icon class="arrow-icon"> <ArrowRight /> </el-icon> </div> <el-select v-model="node.userId" <el-select v-model="node.userId" placeholder="选择人员" class="approver-select" clearable > <el-option v-for="user in userList" clearable> <el-option v-for="user in userListApprove" :key="user.userId" :label="user.nickName" :value="user.userId" /> :label="user.userName" :value="user.userId" /> </el-select> <el-button type="danger" <el-button type="danger" size="small" :icon="Delete" @click="removeApproverNode(index)" v-if="approverNodes.length > 1" class="remove-btn" >删除</el-button> class="remove-btn">删除</el-button> </div> </div> </el-form-item> @@ -214,109 +273,138 @@ </el-row> </div> </el-card> <!-- 产品信息 --> <el-card class="form-card" shadow="hover"> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><Box /></el-icon> <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 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"> <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" <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%" /> style="width: 100%" /> </el-form-item> </template> </el-table-column> <el-table-column prop="specification" label="规格型号" width="200"> <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" <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 || []" style="width: 100%"> <el-option v-for="item in scope.row.modelOptions || []" :key="item.id" :label="item.model" :value="item.id" /> :value="item.id" /> </el-select> </el-form-item> </template> </el-table-column> <el-table-column prop="unit" label="单位"> <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 :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="单价"> <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 :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"> <el-table-column label="操作" width="80" align="center"> <template #default="scope"> <el-button link type="danger" @click="removeProduct(scope.$index)">删除</el-button> <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" /> <el-empty v-else description="暂无产品,请点击添加产品" :image-size="80" /> </div> </el-card> <!-- 备注信息 --> <el-card class="form-card" shadow="hover"> <el-card class="form-card" shadow="hover"> <template #header> <div class="card-header-wrapper"> <el-icon class="card-icon"><EditPen /></el-icon> <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" <el-form-item label="备注" prop="remark"> <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息(选填)" :rows="4" maxlength="500" show-word-limit ></el-input> 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> @@ -326,26 +414,32 @@ <!-- <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="报价总额" :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,162 +448,186 @@ </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 { 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"; import {customerList} from "@/api/salesManagement/salesLedger.js"; import {modelList, productTreeList} from "@/api/basicData/product.js"; // 响应式数据 const loading = ref(false) const loading = ref(false); const searchForm = reactive({ quotationNo: '', customer: '', status: '' }) quotationNo: "", customer: "", status: "", }); const quotationList = ref([]) const quotationList = ref([]); const productOptions = ref([]); const modelOptions = ref([]); const pagination = reactive({ total: 3, currentPage: 1, pageSize: 100 }) pageSize: 100, }); const dialogVisible = ref(false) const viewDialogVisible = ref(false) const dialogTitle = ref('新增报价') const dialogVisible = ref(false); const viewDialogVisible = ref(false); const dialogTitle = ref("新增报价"); const form = reactive({ quotationNo: '', customer: '', salesperson: '', quotationDate: '', validDate: '', paymentMethod: '', status: '草稿', remark: '', quotationNo: "", customer: "", salesperson: "", quotationDate: "", validDate: "", paymentMethod: "", status: "草稿", remark: "", products: [], subtotal: 0, freight: 0, otherFee: 0, discountRate: 0, discountAmount: 0, totalAmount: 0 }) totalAmount: 0, }); 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' }] } 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 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' }] } 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 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 approverNodes = ref([ { id: 1, userId: null } ]) let nextApproverId = 2 const approverNodes = ref([{ id: 1, userId: null }]); let nextApproverId = 2; const isEdit = ref(false) const editId = ref(null) const currentQuotation = ref({}) const formRef = ref() const isEdit = ref(false); const editId = ref(null); const currentQuotation = ref({}); const formRef = ref(); // 添加审批人节点 function addApproverNode() { approverNodes.value.push({ id: nextApproverId++, userId: null }) approverNodes.value.push({ id: nextApproverId++, userId: null }); } // 删除审批人节点 function removeApproverNode(index) { approverNodes.value.splice(index, 1) approverNodes.value.splice(index, 1); } // 计算属性 const filteredList = computed(() => { let list = quotationList.value return list }) let list = quotationList.value; return list; }); // 方法 const getStatusType = (status) => { const getStatusType = status => { const statusMap = { '待审批': 'info', '审核中': 'primary', '通过': 'success', '拒绝': 'danger' } return statusMap[status] || 'info' } 待审批: "info", 审核中: "primary", 通过: "success", 拒绝: "danger", }; return statusMap[status] || "info"; }; const resetSearch = () => { searchForm.quotationNo = '' searchForm.customer = '' searchForm.status = '' searchForm.quotationNo = ""; searchForm.customer = ""; searchForm.status = ""; // 重置到第一页并重新查询 pagination.currentPage = 1 handleSearch() } pagination.currentPage = 1; handleSearch(); }; const handleAdd = async () => { dialogTitle.value = '新增报价' isEdit.value = false resetForm() dialogTitle.value = "新增报价"; isEdit.value = false; resetForm(); // 重置审批人节点 approverNodes.value = [{ id: 1, userId: null }] nextApproverId = 2 dialogVisible.value = true 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 || '' nickName: item.nickName || "", userName: item.userName || "", })); approveUserList({ approveType: 6 }).then(res => { userListApprove.value = res.data; }); getProductOptions(); customerList().then((res) => { 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) => { return productTreeList().then(res => { productOptions.value = convertIdToValue(res); return productOptions.value return productOptions.value; }); }; function convertIdToValue(data) { return data.map((item) => { return data.map(item => { const { id, children, ...rest } = item; const newItem = { ...rest, @@ -539,12 +657,12 @@ if (!row) return; // 如果清空选择,则清空相关字段 if (!value) { row.productId = ''; row.product = ''; row.productId = ""; row.product = ""; row.modelOptions = []; row.specificationId = ''; row.specification = ''; row.unit = ''; row.specificationId = ""; row.specification = ""; row.unit = ""; return; } // 更新 productId(v-model 已经自动更新,这里确保一致性) @@ -555,7 +673,7 @@ row.product = label; } // 获取规格型号列表,设置到当前行的 modelOptions modelList({ id: value }).then((res) => { modelList({ id: value }).then(res => { row.modelOptions = res || []; }); }; @@ -563,21 +681,21 @@ if (!row) return; // 如果清空选择,则清空相关字段 if (!value) { row.specificationId = ''; row.specification = ''; row.unit = ''; 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); 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 = ''; row.specification = ""; row.unit = ""; } }; const findNodeById = (nodes, productId) => { @@ -594,59 +712,63 @@ } return null; // 没有找到节点,返回null }; const handleView = (row) => { 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 || '', 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 || '', unit: product.unit || "", unitPrice: product.unitPrice || 0, amount: product.amount || 0 })) : [], totalAmount: row.totalAmount || 0 } viewDialogVisible.value = true } 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 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() 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 || '' 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) || '' : findNodeIdByLabel(productOptions.value, productName) || ""; // 如果有产品ID,加载对应的规格型号列表 let modelOptions = []; let resolvedSpecificationId = product.specificationId || ''; let resolvedSpecificationId = product.specificationId || ""; if (resolvedProductId) { try { @@ -655,13 +777,15 @@ // 如果返回的数据没有 specificationId,但有 specification 名称,根据名称查找 ID if (!resolvedSpecificationId && product.specification) { const foundModel = modelOptions.find(item => item.model === product.specification); const foundModel = modelOptions.find( item => item.model === product.specification ); if (foundModel) { resolvedSpecificationId = foundModel.id; } } } catch (error) { console.error('加载规格型号失败:', error); console.error("加载规格型号失败:", error); } } @@ -669,238 +793,248 @@ productId: resolvedProductId, product: productName, specificationId: resolvedSpecificationId, specification: product.specification || '', specification: product.specification || "", quantity: product.quantity || 0, unit: product.unit || '', 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 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(',') const userIds = row.approveUserIds.split(","); approverNodes.value = userIds.map((userId, idx) => ({ id: idx + 1, userId: parseInt(userId.trim()) })) nextApproverId = userIds.length + 1 userId: parseInt(userId.trim()), })); nextApproverId = userIds.length + 1; } else { approverNodes.value = [{ id: 1, userId: null }] nextApproverId = 2 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 || '' nickName: item.nickName || "", userName: item.userName || "", })); dialogVisible.value = true } dialogVisible.value = true; }; const handleDelete = (row) => { ElMessageBox.confirm('确认删除该报价单吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' const handleDelete = row => { ElMessageBox.confirm("确认删除该报价单吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(() => { const index = quotationList.value.findIndex(item => item.id === row.id) 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() 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 } 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: '', productId: "", product: "", productName: "", specificationId: "", specification: "", quantity: 1, unit: '', unit: "", unitPrice: 0, amount: 0, modelOptions: [] // 为每行添加独立的规格型号列表 }) } modelOptions: [], // 为每行添加独立的规格型号列表 }); }; const removeProduct = (index) => { form.products.splice(index, 1) calculateSubtotal() } const removeProduct = index => { form.products.splice(index, 1); calculateSubtotal(); }; const calculateAmount = (product) => { product.amount = product.quantity * product.unitPrice 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() } 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 } 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) => { formRef.value.validate(valid => { if (valid) { if (form.products.length === 0) { ElMessage.warning('请至少添加一个产品') return ElMessage.warning("请至少添加一个产品"); return; } // 审批人必填校验 const hasEmptyApprover = approverNodes.value.some(node => !node.userId) const hasEmptyApprover = approverNodes.value.some(node => !node.userId); if (hasEmptyApprover) { ElMessage.error('请为所有审批节点选择审批人!') return ElMessage.error("请为所有审批节点选择审批人!"); return; } // 收集所有节点的审批人id form.approveUserIds = approverNodes.value.map(node => node.userId).join(',') 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) const price = Number(product.unitPrice) || 0; return sum + price; }, 0); if (isEdit.value) { // 编辑 const index = quotationList.value.findIndex(item => item.id === editId.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() ElMessage.success("编辑成功"); dialogVisible.value = false; handleSearch(); } }) }); } } else { // 新增 addQuotation(form).then(res=>{ if(res.code===200){ ElMessage.success('新增成功') dialogVisible.value = false handleSearch() ElMessage.success("新增成功"); dialogVisible.value = false; handleSearch(); } }) }); } } }); }; } }) } const handleCurrentChange = (val) => { pagination.currentPage = val.page pagination.pageSize = val.limit const handleCurrentChange = val => { pagination.currentPage = val.page; pagination.pageSize = val.limit; // 分页变化时重新查询列表 handleSearch() } handleSearch(); }; const handleSearch = ()=>{ const params = { // 后端分页参数:current / size current: pagination.currentPage, size: pagination.pageSize, ...searchForm } ...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 || '草稿', 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 || '', 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 || '', unit: product.unit || "", unitPrice: product.unitPrice || 0, amount: product.amount || 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 totalAmount: item.totalAmount || 0, })); pagination.total = res.data.total; } }) customerList().then((res) => { }); 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 || "", })); }); } }; onMounted(()=>{ handleSearch() }) handleSearch(); }); </script> <style scoped lang="scss">