src/api/basicData/customerFile.js
@@ -1,7 +1,5 @@ // å®¢æ·æ¡£æ¡é¡µé¢æ¥å£ import request from '@/utils/request' // å页æ¥è¯¢ export function listCustomer(query) { return request({ url: '/basic/customer/list', @@ -9,14 +7,76 @@ params: query }) } // æ¥è¯¢å®¢æ·æ¡£æ¡è¯¦ç» // å®¢æ·æ¡£æ¡ç§æµ·æ¥è¯¢ export function listCustomerPrivatePool(query) { return request({ url: '/customerPrivatePool/listPage', method: 'get', params: query }) } export function addCustomerPrivatePool(data) { return request({ url: '/customerPrivatePool/add', method: 'post', data: data }) } export function addCustomerPrivate(data) { return request({ url: '/customerPrivate/add', method: 'post', data: data }) } export function delCustomerPrivate(ids) { return request({ url: '/customerPrivate/delete', method: 'delete', data: ids }) } export function delCustomerPrivatePool(id) { return request({ url: '/customerPrivatePool/delete/' + id, method: 'delete', }) } export function shareCustomer(data) { return request({ url: '/customerPrivatePool/together', method: 'post', data: data }) } export function getCustomer(id) { return request({ url: '/basic/customer/' + id, method: 'get' }) } // æ°å¢å®¢æ·æ¡£æ¡ export function getCustomerPrivatePoolById(id) { return request({ url: '/customerPrivatePool/getbyId/' + id, method: 'get' }) } export function getCustomerPrivatePoolInfo(id) { return request({ url: '/customerPrivatePool/info/' + id, method: 'get' }) } export function addCustomer(data) { return request({ url: '/basic/customer/addCustomer', @@ -24,7 +84,7 @@ data: data }) } // ä¿®æ¹å®¢æ·æ¡£æ¡ export function updateCustomer(data) { return request({ url: '/basic/customer/updateCustomer', @@ -32,7 +92,15 @@ data: data }) } // 导åºå®¢æ·æ¡£æ¡ export function updateCustomerPrivatePool(data) { return request({ url: '/customerPrivatePool/update', method: 'put', data: data }) } export function exportCustomer(query) { return request({ url: '/basic/customer/export', @@ -41,7 +109,7 @@ responseType: 'blob' }) } // å é¤å®¢æ·æ¡£æ¡ export function delCustomer(ids) { return request({ url: '/basic/customer/delCustomer', @@ -50,8 +118,6 @@ }) } // æ°å¢å®¢æ·è·è¿ export function addCustomerFollow(data) { return request({ url: '/basic/customer-follow/add', @@ -60,23 +126,21 @@ }) } // ä¿®æ¹å®¢æ·è·è¿ export function updateCustomerFollow(data) { return request({ url: '/basic/customer-follow/edit', method: 'put', data: data, }) return request({ url: '/basic/customer-follow/edit', method: 'put', data: data, }) } // å é¤å®¢æ·è·è¿ export function delCustomerFollow(id) { return request({ url: '/basic/customer-follow/'+id, url: '/basic/customer-follow/' + id, method: 'delete', }) } // å访æé-æ°å¢/æ´æ° export function addReturnVisit(data) { return request({ url: '/basic/customer-follow/return-visit', @@ -84,10 +148,10 @@ data: data }) } // è·åå访æé详æ export function getReturnVisit(id) { return request({ url: '/basic/customer-follow/return-visit/' + id, method: 'get' }) } } src/api/basicData/productModel.js
@@ -6,4 +6,12 @@ method: 'get', params: query }) } export function productModelListByUrl(url, query) { return request({ url, method: 'get', params: query }) } src/api/productionManagement/workOrder.js
@@ -37,7 +37,7 @@ // å·¥å-å½åå·¥åºç©æå°è´¦ export function listWorkOrderMaterialLedger(query) { return request({ url: "/productWorkOrder/material/list", url: "/productOrderMaterial/reportMaterials", method: "get", params: query, }); @@ -69,3 +69,12 @@ params: query, }); } // å·¥å-é¢ç¨ï¼æäº¤å®é é¢ç¨æ°éï¼ export function pickWorkOrderMaterial(data) { return request({ url: "/productWorkOrder/material/pick", method: "post", data, }); } src/store/modules/permission.js
@@ -1,5 +1,5 @@ import auth from '@/plugins/auth' import router, { constantRoutes, dynamicRoutes } from '@/router' import router, { constantRoutes, dynamicRoutes, financialRoutes } from '@/router' import { getRouters } from '@/api/menu' import Layout from '@/layout/index' import ParentView from '@/components/ParentView' @@ -45,9 +45,14 @@ const asyncRoutes = filterDynamicRoutes(dynamicRoutes) asyncRoutes.forEach(route => { router.addRoute(route) }) this.setRoutes(rewriteRoutes) this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)) this.setDefaultRoutes(sidebarRoutes) this.setTopbarRoutes(defaultRoutes) // å°è´¢å¡ç®¡çè·¯ç±åå¹¶å°ä¾§è¾¹æ this.setSidebarRouters(constantRoutes.concat(sidebarRoutes).concat(financialRoutes)) this.setDefaultRoutes(sidebarRoutes.concat(financialRoutes)) this.setTopbarRoutes(defaultRoutes.concat(financialRoutes)) // æ·»å è´¢å¡ç®¡ç模åè·¯ç± financialRoutes.forEach(route => { router.addRoute(route) }) resolve(rewriteRoutes) }) }) src/views/basicData/customerFile/index.vue
@@ -612,11 +612,13 @@ import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; import { addCustomer, delCustomer, addCustomerPrivate, delCustomerPrivate, getCustomer, listCustomer, updateCustomer, getCustomerPrivatePoolById, getCustomerPrivatePoolInfo, listCustomerPrivatePool, updateCustomerPrivatePool, addCustomerFollow, updateCustomerFollow, delCustomerFollow, @@ -654,7 +656,7 @@ const negotiationFormRef = ref(); const negotiationForm = reactive({ customerName: "", customerId: "", customerPrivatePoolId: "", followUpMethod: "", followUpLevel: "", followUpTime: "", @@ -726,7 +728,7 @@ }, { label: "å°ååèç³»æ¹å¼", prop: "addressPhone", prop: "companyAddress", width: 250, }, { @@ -766,6 +768,24 @@ { label: "ç»´æ¤äºº", prop: "maintainer", }, { label: "å®¢æ·æ¥æº", prop: "type", dataType: "tag", width: 100, formatData: value => { if (value === 1 || value === "1") { return "å ¬æµ·"; } return "ç§æµ·"; }, formatType: value => { if (value === 1 || value === "1") { return "warning"; } return "success"; }, }, { label: "ç»´æ¤æ¶é´", @@ -881,7 +901,7 @@ // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", url: import.meta.env.VITE_APP_BASE_API + "/customerPrivate/importData", // æä»¶ä¸ä¼ åçåè° beforeUpload: file => { console.log("æä»¶å³å°ä¸ä¼ ", file); @@ -952,10 +972,10 @@ }; const getList = () => { tableLoading.value = true; listCustomer({ ...searchForm.value, ...page }).then(res => { listCustomerPrivatePool({ ...searchForm.value, ...page }).then(res => { tableLoading.value = false; tableData.value = res.records; page.total = res.total; tableData.value = res.data.records; page.total = res.data.total; }); }; // è¡¨æ ¼éæ©æ°æ® @@ -974,7 +994,7 @@ } /** ä¸è½½æ¨¡æ¿ */ function importTemplate() { proxy.download("/basic/customer/downloadTemplate", {}, "客æ·å¯¼å ¥æ¨¡æ¿.xlsx"); proxy.download("/customerPrivate/downloadTemplate", {}, "客æ·å¯¼å ¥æ¨¡æ¿.xlsx"); } // æå¼å¼¹æ¡ const openForm = (type, row) => { @@ -992,7 +1012,7 @@ userList.value = res.data; }); if (type === "edit") { getCustomer(row.id).then(res => { getCustomerPrivatePoolById(row.id).then(res => { form.value = { ...res.data }; formYYs.value.contactList = res.data.contactPerson .split(",") @@ -1029,7 +1049,7 @@ form.value.contactPhone = formYYs.value.contactList .map(item => item.contactPhone) .join(","); addCustomer(form.value).then(res => { addCustomerPrivate(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); @@ -1043,7 +1063,7 @@ form.value.contactPhone = formYYs.value.contactList .map(item => item.contactPhone) .join(","); updateCustomer(form.value).then(res => { updateCustomerPrivatePool(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); @@ -1062,7 +1082,7 @@ type: "warning", }) .then(() => { proxy.download("/basic/customer/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); proxy.download("/customerPrivate/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); @@ -1072,12 +1092,11 @@ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® const unauthorizedData = selectedRows.value.filter( item => item.maintainer !== userStore.nickName item => item.type === 1 || item.type === "1" ); if (unauthorizedData.length > 0) { proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); proxy.$modal.msgWarning("å ¬æµ·åé ç客æ·ä¸è½å é¤"); return; } ids = selectedRows.value.map(item => item.id); @@ -1092,8 +1111,8 @@ }) .then(() => { tableLoading.value = true; delCustomer(ids) .then(res => { delCustomerPrivate(ids) .then(() => { proxy.$modal.msgSuccess("å 餿å"); getList(); }) @@ -1146,7 +1165,8 @@ if (reminderForm.id) { submitvalue.value = { id: reminderForm.id, customerId: currentCustomerId.value, customerPrivatePoolId: reminderForm.id, customerPrivatePoolId: currentCustomerId.value, isEnabled: reminderForm.reminderSwitch ? 1 : 0, content: reminderForm.reminderContent, reminderTime: reminderForm.reminderTime, @@ -1154,15 +1174,13 @@ }; } else { submitvalue.value = { customerId: currentCustomerId.value, customerPrivatePoolId: currentCustomerId.value, isEnabled: reminderForm.reminderSwitch ? 1 : 0, content: reminderForm.reminderContent, reminderTime: reminderForm.reminderTime, remindUserId: userStore.id, }; } console.log("æäº¤å访æéæ°æ®:", submitvalue.value); // è°ç¨æ¥å£ addReturnVisit(submitvalue.value) @@ -1185,20 +1203,12 @@ // æå¼æ´½è°è¿åº¦å¼¹çª const openNegotiationDialog = row => { negotiationForm.customerName = row.customerName; negotiationForm.customerId = row.id; negotiationForm.customerPrivatePoolId = row.id; negotiationForm.followUpMethod = ""; negotiationForm.followUpLevel = ""; negotiationForm.followUpTime = ""; negotiationForm.followerUserName = userStore.nickName; // é»è®¤å½åç»å½äºº negotiationForm.content = ""; // { // "customerId": 152, // "followUpMethod": "çµè¯æ²é", // "followUpLevel": "没ææå", // "followUpTime": "2026-03-04T15:30:00", // "followerUserName": "管çåè´¦å·", // "content": "111" // } negotiationDialogVisible.value = true; }; @@ -1220,26 +1230,9 @@ if (isEdit) { // ä¿®æ¹æä½ console.log("ä¿®æ¹æ´½è°è¿åº¦æ°æ®:", negotiationForm); // è¿éå¯ä»¥è°ç¨æ´æ°æ¥å£ // å®é 项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ // 示ä¾ï¼updateCustomerFollow(negotiationForm).then(res => { // // æ´æ°æ¬å°æ°æ® // const index = negotiationForm.editIndex; // negotiationRecords.value[index] = { // followUpTime: negotiationForm.followUpTime, // followUpMethod: negotiationForm.followUpMethod, // followUpLevel: negotiationForm.followUpLevel, // followerUserName: negotiationForm.followerUserName, // content: negotiationForm.content, // id: negotiationForm.id, // }; // proxy.$modal.msgSuccess("ä¿®æ¹æå"); // closeNegotiationDialog(); // }); updateCustomerFollow(negotiationForm).then(res => { // æ´æ°æ¬å°æ°æ® getCustomer(negotiationForm.customerId).then(res => { getCustomer(negotiationForm.customerPrivatePoolId).then(res => { // æ´æ°æ¬å°æ°æ® negotiationRecords.value = res.data.followUpList || []; }); @@ -1271,8 +1264,7 @@ // æå¼è¯¦æ å¼¹çª const openDetailDialog = row => { // è°ç¨getCustomeræ¥å£è·å客æ·è¯¦æ getCustomer(row.id).then(res => { getCustomerPrivatePoolInfo(row.id).then(res => { // å¡«å 客æ·åºæ¬ä¿¡æ¯ Object.assign(detailForm, res.data); @@ -1293,7 +1285,7 @@ // å°å½åè®°å½æ°æ®å¡«å å°è¡¨å Object.assign(negotiationForm, { customerName: row.customerName, customerId: row.customerId, customerPrivatePoolId: row.customerPrivatePoolId, followUpMethod: row.followUpMethod, followUpLevel: row.followUpLevel, followUpTime: row.followUpTime, @@ -1321,7 +1313,7 @@ // }); delCustomerFollow(row.id).then(() => { // å 餿ååæ´æ°æ¬å°æ°æ® getCustomer(row.customerId).then(res => { getCustomer(row.customerPrivatePoolId).then(res => { // æ´æ°æ¬å°æ°æ® negotiationRecords.value = res.data.followUpList || []; }); src/views/basicData/customerFileOpenSea/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,1794 @@ <template> <div class="app-container"> <div class="search_form"> <div> <span class="search_title">客æ·åç§°ï¼</span> <el-input v-model="searchForm.customerName" style="width: 240px;margin-right: 10px" placeholder="请è¾å ¥" @change="handleQuery" clearable :prefix-icon="Search" /> <span class="search_title">客æ·åç±»ï¼</span> <el-select v-model="searchForm.customerType" placeholder="è¯·éæ©" style="width: 240px" clearable @change="handleQuery"> <el-option label="é¶å®å®¢æ·" value="é¶å®å®¢æ·" /> <el-option label="è¿éå客æ·" value="è¿éå客æ·" /> </el-select> <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> </div> <div> <el-button type="primary" @click="openForm('add')">æ°å¢å®¢æ·</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="info" plain icon="Upload" @click="handleImport">å¯¼å ¥</el-button> <el-button type="danger" plain @click="handleDelete">å é¤</el-button> </div> </div> <div class="table_list"> <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination"></PIMTable> </div> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢å®¢æ·ä¿¡æ¯' : 'ç¼è¾å®¢æ·ä¿¡æ¯'" width="70%" @close="closeDia"> <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="客æ·åç§°ï¼" prop="customerName"> <el-input v-model="form.customerName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="纳ç¨äººè¯å«å·ï¼" prop="taxpayerIdentificationNumber"> <el-input v-model="form.taxpayerIdentificationNumber" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å ¬å¸å°åï¼" prop="companyAddress"> <el-input v-model="form.companyAddress" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å ¬å¸çµè¯ï¼" prop="companyPhone"> <el-input v-model="form.companyPhone" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="é¶è¡åºæ¬æ·ï¼" prop="basicBankAccount"> <el-input v-model="form.basicBankAccount" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="é¶è¡è´¦å·ï¼" prop="bankAccount"> <el-input v-model="form.bankAccount" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="弿·è¡å·ï¼" prop="bankCode"> <el-input v-model="form.bankCode" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="客æ·åç±»ï¼" prop="customerType"> <el-select v-model="form.customerType" placeholder="è¯·éæ©" clearable> <el-option label="é¶å®å®¢æ·" value="é¶å®å®¢æ·" /> <el-option label="è¿éå客æ·" value="è¿éå客æ·" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index"> <el-col :span="12"> <el-form-item label="è系人ï¼" prop="contactPerson"> <el-input v-model="contact.contactPerson" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="èç³»çµè¯ï¼" prop="contactPhone"> <div style="display: flex; align-items: center;width: 100%;"> <el-input v-model="contact.contactPhone" placeholder="请è¾å ¥" clearable /> <el-button @click="removeContact(index)" type="danger" circle style="margin-left: 5px;"> <el-icon> <Close /> </el-icon> </el-button> </div> </el-form-item> </el-col> </el-row> <el-button @click="addNewContact" style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="ç»´æ¤äººï¼" prop="maintainer"> <el-select v-model="form.maintainer" placeholder="è¯·éæ©" clearable disabled> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ç»´æ¤æ¶é´ï¼" prop="maintenanceTime"> <el-date-picker style="width: 100%" v-model="form.maintenanceTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©" clearable /> </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 @click="closeDia">åæ¶</el-button> </div> </template> </el-dialog> <el-dialog v-model="assignDialogVisible" title="åé 客æ·" width="500px" @close="closeAssignDialog"> <el-form :model="assignForm" :rules="assignRules" ref="assignFormRef" label-width="100px"> <el-form-item label="客æ·åç§°"> <el-input v-model="assignForm.customerName" disabled /> </el-form-item> <el-form-item label="åé 人å" prop="boundId"> <el-select v-model="assignForm.boundId" placeholder="è¯·éæ©åé 人å" style="width: 100%" filterable> <el-option v-for="item in userList" :key="item.userId || item.nickName" :label="item.nickName" :value="item.userId" /> </el-select> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitAssignForm">确认</el-button> <el-button @click="closeAssignDialog">åæ¶</el-button> </div> </template> </el-dialog> <el-dialog v-model="shareDialogVisible" title="å ±äº«å®¢æ·" width="500px" @close="closeShareDialog"> <el-form :model="shareForm" :rules="shareRules" ref="shareFormRef" label-width="100px"> <el-form-item label="客æ·åç§°"> <el-input v-model="shareForm.customerName" disabled /> </el-form-item> <el-form-item label="å ±äº«äººå" prop="boundIds"> <el-select v-model="shareForm.boundIds" placeholder="è¯·éæ©å ±äº«äººå" style="width: 100%" filterable multiple collapse-tags collapse-tags-tooltip> <el-option v-for="item in userList" :key="item.userId || item.nickName" :label="item.nickName" :value="item.userId" /> </el-select> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitShareForm">确认</el-button> <el-button @click="closeShareDialog">åæ¶</el-button> </div> </template> </el-dialog> <!-- ç¨æ·å¯¼å ¥å¯¹è¯æ¡ --> <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body> <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :before-upload="upload.beforeUpload" :on-progress="upload.onProgress" :on-success="upload.onSuccess" :on-error="upload.onError" :on-change="upload.onChange" :auto-upload="false" drag> <el-icon class="el-icon--upload"><upload-filled /></el-icon> <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> <template #tip> <div class="el-upload__tip text-center"> <span>ä» å è®¸å¯¼å ¥xlsãxlsxæ ¼å¼æä»¶ã</span> <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> </div> </template> </el-upload> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> <el-button @click="upload.open = false">å æ¶</el-button> </div> </template> </el-dialog> <!-- å访æéå¯¹è¯æ¡ --> <el-dialog title="å访æé" v-model="reminderDialogVisible" width="500px" @close="closeReminderDialog"> <el-form :model="reminderForm" label-width="100px" :rules="reminderRules" ref="reminderFormRef"> <el-form-item label="客æ·åç§°ï¼"> <el-input v-model="reminderForm.customerName" disabled /> </el-form-item> <el-form-item label="æéå¼å ³ï¼"> <el-switch v-model="reminderForm.reminderSwitch" /> </el-form-item> <el-form-item label="æéå 容ï¼" prop="reminderContent"> <el-input v-model="reminderForm.reminderContent" type="textarea" :maxlength="100" show-word-limit placeholder="请è¾å ¥æéå 容" /> </el-form-item> <el-form-item label="æéæ¶é´ï¼" prop="reminderTime"> <el-date-picker v-model="reminderForm.reminderTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" placeholder="è¯·éæ©æéæ¶é´" style="width: 100%" /> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitReminderForm">确认</el-button> <el-button @click="closeReminderDialog">åæ¶</el-button> </div> </template> </el-dialog> <!-- æ·»å /ä¿®æ¹æ´½è°è¿åº¦å¯¹è¯æ¡ --> <el-dialog :title="negotiationForm.editIndex !== undefined ? 'ä¿®æ¹è¿åº¦' : 'æ·»å è¿åº¦'" v-model="negotiationDialogVisible" width="600px" @close="closeNegotiationDialog"> <el-form :model="negotiationForm" label-width="100px" :rules="negotiationRules" ref="negotiationFormRef"> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="è·è¿æ¹å¼ï¼" prop="followUpMethod"> <el-select v-model="negotiationForm.followUpMethod" placeholder="è¯·éæ©" style="width: 100%"> <el-option label="çµè¯" value="çµè¯" /> <el-option label="é®ä»¶" value="é®ä»¶" /> <el-option label="ä¸é¨" value="ä¸é¨" /> <el-option label="微信" value="微信" /> <el-option label="å ¶ä»" value="å ¶ä»" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="è·è¿ç¨åº¦ï¼" prop="followUpLevel"> <el-select v-model="negotiationForm.followUpLevel" placeholder="è¯·éæ©" style="width: 100%"> <el-option label="æ½å¨å®¢æ·" value="æ½å¨å®¢æ·" /> <el-option label="忬¡æè®¿" value="忬¡æè®¿" /> <el-option label="夿¬¡æè®¿" value="夿¬¡æè®¿" /> <el-option label="æå客æ·" value="æå客æ·" /> <el-option label="å·²ç¾çº¦å®¢æ·" value="å·²ç¾çº¦å®¢æ·" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="è·è¿æ¶é´ï¼" prop="followUpTime"> <el-date-picker v-model="negotiationForm.followUpTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" placeholder="è¯·éæ©" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="è·è¿äººï¼"> <el-input v-model="negotiationForm.followerUserName" disabled /> </el-form-item> </el-col> </el-row> <el-form-item label="å 容ï¼" prop="content"> <el-input v-model="negotiationForm.content" type="textarea" :rows="4" placeholder="请è¾å ¥" /> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitNegotiationForm">确认</el-button> <el-button @click="closeNegotiationDialog">åæ¶</el-button> </div> </template> </el-dialog> <!-- 客æ·è¯¦æ å¯¹è¯æ¡ --> <el-dialog title="客æ·è¯¦æ " v-model="detailDialogVisible" width="1000px" @close="closeDetailDialog"> <!-- 客æ·åºæ¬ä¿¡æ¯ --> <div class="detail-section"> <h3 class="section-title">客æ·åºæ¬ä¿¡æ¯</h3> <div class="info-display"> <el-row :gutter="20"> <el-col :span="12"> <div class="info-item"> <span class="info-label">客æ·åç§°ï¼</span> <span class="info-value">{{ detailForm.customerName }}</span> </div> </el-col> <el-col :span="12"> <div class="info-item"> <span class="info-label">客æ·åç±»ï¼</span> <span class="info-value">{{ detailForm.customerType }}</span> </div> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <div class="info-item"> <span class="info-label">纳ç¨äººè¯å«å·ï¼</span> <span class="info-value">{{ detailForm.taxpayerIdentificationNumber }}</span> </div> </el-col> <el-col :span="12"> <div class="info-item"> <span class="info-label">å ¬å¸çµè¯ï¼</span> <span class="info-value">{{ detailForm.companyPhone }}</span> </div> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <div class="info-item"> <span class="info-label">å ¬å¸å°åï¼</span> <span class="info-value">{{ detailForm.companyAddress }}</span> </div> </el-col> <el-col :span="12"> <div class="info-item"> <span class="info-label">é¶è¡åºæ¬æ·ï¼</span> <span class="info-value">{{ detailForm.basicBankAccount }}</span> </div> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <div class="info-item"> <span class="info-label">é¶è¡è´¦å·ï¼</span> <span class="info-value">{{ detailForm.bankAccount }}</span> </div> </el-col> <el-col :span="12"> <div class="info-item"> <span class="info-label">弿·è¡å·ï¼</span> <span class="info-value">{{ detailForm.bankCode }}</span> </div> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <div class="info-item"> <span class="info-label">è系人ï¼</span> <span class="info-value">{{ detailForm.contactPerson }}</span> </div> </el-col> <el-col :span="12"> <div class="info-item"> <span class="info-label">èç³»çµè¯ï¼</span> <span class="info-value">{{ detailForm.contactPhone }}</span> </div> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <div class="info-item"> <span class="info-label">ç»´æ¤äººï¼</span> <span class="info-value">{{ detailForm.maintainer }}</span> </div> </el-col> <el-col :span="12"> <div class="info-item"> <span class="info-label">ç»´æ¤æ¶é´ï¼</span> <span class="info-value">{{ detailForm.maintenanceTime }}</span> </div> </el-col> </el-row> </div> </div> <!-- æ´½è°è¿åº¦è®°å½ --> <div class="detail-section"> <div class="section-header"> <h3 class="section-title">æ´½è°è¿åº¦è®°å½</h3> <el-button type="primary" size="small" @click="openNegotiationDialog(detailForm)"> æ·»å è¿åº¦ </el-button> </div> <el-table :data="negotiationRecords" border style="width: 100%"> <el-table-column prop="followUpTime" label="è·è¿æ¶é´" width="160" /> <el-table-column prop="followUpMethod" label="è·è¿æ¹å¼" width="100" /> <el-table-column prop="followUpLevel" label="è·è¿ç¨åº¦" /> <el-table-column prop="followerUserName" label="è·è¿äºº" width="100" /> <el-table-column prop="content" label="å 容" show-overflow-tooltip /> <el-table-column label="éä»¶" width="100" align="center"> <template #default="{ row }"> <el-button type="info" link @click="openAttachmentDialog(row)"> <el-icon> <Paperclip /> </el-icon> éä»¶ <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : 'ä¸ä¼ ' }} --> </el-button> </template> </el-table-column> <el-table-column label="æä½" width="150" align="center"> <template #default="{ row, $index }"> <el-button type="primary" link @click="editNegotiationRecord(row, $index)"> ä¿®æ¹ </el-button> <el-button type="danger" link @click="deleteNegotiationRecord(row, $index)"> å é¤ </el-button> </template> </el-table-column> </el-table> <div v-if="negotiationRecords.length === 0" class="no-records"> ææ æ´½è°è¿åº¦è®°å½ </div> </div> <template #footer> <div class="dialog-footer"> <el-button @click="closeDetailDialog">å ³é</el-button> </div> </template> </el-dialog> <!-- éä»¶ä¸ä¼ å¼¹çª --> <el-dialog title="é件管ç" v-model="attachmentDialogVisible" width="600px" @close="closeAttachmentDialog"> <div class="attachment-section"> <div class="upload-area"> <el-upload ref="attachmentUploadRef" :action="getAttachmentUploadUrl()" :headers="attachmentUploadHeaders" :file-list="currentAttachmentList" :on-success="handleAttachmentSuccess" :on-error="handleAttachmentError" :on-remove="handleAttachmentRemove" :before-upload="beforeAttachmentUpload" multiple :limit="10" name="files"> <el-button type="primary"> <el-icon> <Upload /> </el-icon> ä¸ä¼ éä»¶ </el-button> <template #tip> <div class="el-upload__tip"> æ¯æä¸ä¼ å¾çãææ¡£çæä»¶ï¼å个æä»¶ä¸è¶ è¿50MB </div> </template> </el-upload> </div> <div v-if="currentAttachmentList.length > 0" class="attachment-list"> <h4>å·²ä¸ä¼ éä»¶ï¼</h4> <el-table :data="currentAttachmentList" border size="small"> <el-table-column prop="name" label="æä»¶å" show-overflow-tooltip /> <el-table-column prop="size" label="大å°" width="100"> <template #default="{ row }"> {{ formatFileSize(row.size) }} </template> </el-table-column> <el-table-column label="æä½" width="120" align="center"> <template #default="{ row, $index }"> <el-button type="primary" link @click="downloadAttachment(row)"> ä¸è½½ </el-button> <el-button type="danger" link @click="deleteAttachment(row, $index)"> å é¤ </el-button> </template> </el-table-column> </el-table> </div> <div v-else class="no-attachment"> ææ éä»¶ </div> </div> <template #footer> <div class="dialog-footer"> <el-button @click="closeAttachmentDialog">å ³é</el-button> </div> </template> </el-dialog> </div> </template> <script setup> import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; import { addCustomer, addCustomerPrivatePool, delCustomerPrivatePool, delCustomer, getCustomer, shareCustomer, listCustomer, updateCustomer, addCustomerFollow, updateCustomerFollow, delCustomerFollow, addReturnVisit, getReturnVisit, } from "@/api/basicData/customerFile.js"; import { ElMessageBox } from "element-plus"; import { userListNoPage } from "@/api/system/user.js"; import useUserStore from "@/store/modules/user"; import { getToken } from "@/utils/auth.js"; const { proxy } = getCurrentInstance(); const userStore = useUserStore(); const assignDialogVisible = ref(false); const assignFormRef = ref(); const assignForm = reactive({ id: undefined, customerName: "", boundId: undefined, }); const assignRules = { boundId: [{ required: true, message: "è¯·éæ©åé 人å", trigger: "change" }], }; const shareDialogVisible = ref(false); const shareFormRef = ref(); const shareForm = reactive({ id: undefined, customerName: "", boundIds: [], }); const shareRules = { boundIds: [{ required: true, message: "è¯·éæ©å ±äº«äººå", trigger: "change" }], }; // å访æéç¸å ³ const reminderDialogVisible = ref(false); const reminderFormRef = ref(); const currentCustomerId = ref(); const reminderForm = reactive({ customerName: "", reminderSwitch: false, reminderContent: "", reminderTime: "", }); const reminderRules = { reminderContent: [ { required: true, message: "请è¾å ¥æéå 容", trigger: "blur" }, ], reminderTime: [ { required: true, message: "è¯·éæ©æéæ¶é´", trigger: "change" }, ], }; // æ´½è°è¿åº¦ç¸å ³ const negotiationDialogVisible = ref(false); const negotiationFormRef = ref(); const negotiationForm = reactive({ customerName: "", customerId: "", followUpMethod: "", followUpLevel: "", followUpTime: "", followerUserName: "", content: "", }); const negotiationRules = { followUpMethod: [ { required: true, message: "è¯·éæ©è·è¿æ¹å¼", trigger: "change" }, ], followUpLevel: [ { required: true, message: "è¯·éæ©è·è¿ç¨åº¦", trigger: "change" }, ], followUpTime: [ { required: true, message: "è¯·éæ©è·è¿æ¶é´", trigger: "change" }, ], content: [{ required: true, message: "请è¾å ¥å 容", trigger: "blur" }], }; // 详æ ç¸å ³ const detailDialogVisible = ref(false); const detailForm = reactive({ customerName: "", customerType: "", taxpayerIdentificationNumber: "", companyPhone: "", companyAddress: "", basicBankAccount: "", bankAccount: "", bankCode: "", contactPerson: "", contactPhone: "", maintainer: "", maintenanceTime: "", }); const negotiationRecords = ref([]); // éä»¶ç¸å ³ const attachmentDialogVisible = ref(false); const attachmentUploadRef = ref(); const currentAttachmentList = ref([]); const currentFollowRecord = ref({}); const attachmentUploadHeaders = { Authorization: "Bearer " + getToken() }; // 卿æå»ºä¸ä¼ URL const getAttachmentUploadUrl = () => { const baseUrl = import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/upload"; return currentFollowRecord.value.id ? `${baseUrl}/${currentFollowRecord.value.id}` : baseUrl; }; const tableColumn = ref([ { label: "客æ·åç±»", prop: "customerType", width: 120, }, { label: "客æ·åç§°", prop: "customerName", width: 220, }, { label: "纳ç¨äººè¯å«ç ", prop: "taxpayerIdentificationNumber", width: 220, }, { label: "å°ååèç³»æ¹å¼", prop: "addressPhone", width: 250, }, { label: "è系人", prop: "contactPerson", }, { label: "èç³»çµè¯", prop: "contactPhone", width: 150, }, // { // label: "è·è¿è¿åº¦", // prop: "followUpLevel", // width: 120, // }, // { // label: "è·è¿æ¶é´", // prop: "followUpTime", // width: 120, // }, { label: "é¶è¡åºæ¬æ·", prop: "basicBankAccount", width: 220, }, { label: "é¶è¡è´¦å·", prop: "bankAccount", width: 220, }, { label: "弿·è¡å·", prop: "bankCode", width: 220, }, { label: "ç»´æ¤äºº", prop: "maintainer", }, { label: "ç»´æ¤æ¶é´", prop: "maintenanceTime", width: 100, }, { label: "é¢ç¨äºº", prop: "usageUserName", width: 120, fixed: "right", }, { label: "é¢ç¨ç¶æ", prop: "usageStatus", dataType: "tag", width: 100, fixed: "right", formatData: value => { if (value === 1 || value === "1") { return "å·²é¢ç¨"; } return "æªé¢ç¨"; }, formatType: value => { if (value === 1 || value === "1") { return "success"; } return "info"; }, }, { label: "å ±äº«äºº", prop: "togetherUserNames", width: 120, fixed: "right", }, { dataType: "action", label: "æä½", align: "center", fixed: "right", width: 200, operation: [ { name: "åé ", type: "text", showHide: row => row.usageStatus != 1, clickFun: row => { openAssignDialog(row); }, }, { name: "åæ¶", type: "text", showHide: row => row.usageStatus == 1, clickFun: row => { recycleCustomer(row); }, }, { name: "å ±äº«", type: "text", showHide: row => row.usageStatus == 1, clickFun: row => { openShareDialog(row); }, }, { name: "ç¼è¾", type: "text", clickFun: row => { openForm("edit", row); }, }, // { // name: "详æ ", // type: "text", // clickFun: row => { // openDetailDialog(row); // }, // }, ], }, ]); const tableData = ref([]); const selectedRows = ref([]); const userList = ref([]); const tableLoading = ref(false); const page = reactive({ current: 1, size: 100, total: 0, }); const total = ref(0); // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® const operationType = ref(""); const dialogFormVisible = ref(false); const formYYs = ref({ // å ¶ä»å段... contactList: [ { contactPerson: "", contactPhone: "", }, ], }); const data = reactive({ searchForm: { customerName: "", customerType: "", }, form: { customerName: "", taxpayerIdentificationNumber: "", companyAddress: "", companyPhone: "", contactPerson: "", contactPhone: "", maintainer: "", maintenanceTime: "", basicBankAccount: "", bankAccount: "", bankCode: "", customerType: "", }, rules: { customerName: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], taxpayerIdentificationNumber: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], companyAddress: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], companyPhone: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], // contactPerson: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], // contactPhone: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], maintainer: [{ required: false, message: "è¯·éæ©", trigger: "change" }], maintenanceTime: [ { required: false, message: "è¯·éæ©", trigger: "change" }, ], basicBankAccount: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], bankAccount: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], bankCode: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], customerType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], }, }); const upload = reactive({ // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼å®¢æ·å¯¼å ¥ï¼ open: false, // å¼¹åºå±æ é¢ï¼å®¢æ·å¯¼å ¥ï¼ title: "", // æ¯å¦ç¦ç¨ä¸ä¼ isUploading: false, // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", // æä»¶ä¸ä¼ åçåè° beforeUpload: file => { console.log("æä»¶å³å°ä¸ä¼ ", file); // å¯ä»¥å¨æ¤å¤åæä»¶ç±»åæå¤§å°æ ¡éª const isValid = file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || file.name.endsWith(".xlsx") || file.name.endsWith(".xls"); if (!isValid) { proxy.$modal.msgError("åªè½ä¸ä¼ Excel æä»¶"); } return isValid; }, // æä»¶ç¶ææ¹åæ¶çåè° onChange: (file, fileList) => { console.log("æä»¶ç¶ææ¹å", file, fileList); }, // æä»¶ä¸ä¼ æåæ¶çåè° onSuccess: (response, file, fileList) => { console.log("ä¸ä¼ æå", response, file, fileList); upload.isUploading = false; if (response.code === 200) { proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); upload.open = false; proxy.$refs["uploadRef"].clearFiles(); getList(); } else if (response.code === 500) { proxy.$modal.msgError(response.msg); } else { proxy.$modal.msgWarning(response.msg); } }, // æä»¶ä¸ä¼ 失败æ¶çåè° onError: (error, file, fileList) => { console.error("ä¸ä¼ 失败", error, file, fileList); upload.isUploading = false; proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); }, // æä»¶ä¸ä¼ è¿åº¦åè° onProgress: (event, file, fileList) => { console.log("ä¸ä¼ ä¸...", event.percent); }, }); const { searchForm, form, rules } = toRefs(data); const addNewContact = () => { formYYs.value.contactList.push({ contactPerson: "", contactPhone: "", }); }; const removeContact = index => { if (formYYs.value.contactList.length > 1) { formYYs.value.contactList.splice(index, 1); } }; // æ¥è¯¢å表 /** æç´¢æé®æä½ */ const handleQuery = () => { page.current = 1; getList(); }; const pagination = obj => { page.current = obj.page; page.size = obj.limit; getList(); }; const getList = () => { tableLoading.value = true; const { total, ...queryPage } = page; listCustomer({ ...searchForm.value, ...queryPage }).then(res => { tableLoading.value = false; tableData.value = res.data.records; page.total = res.data.total; }); }; // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = selection => { selectedRows.value = selection; }; /** æäº¤ä¸ä¼ æä»¶ */ function submitFileForm() { upload.isUploading = true; proxy.$refs["uploadRef"].submit(); } /** å¯¼å ¥æé®æä½ */ function handleImport() { upload.title = "客æ·å¯¼å ¥"; upload.open = true; } /** ä¸è½½æ¨¡æ¿ */ function importTemplate() { proxy.download("/basic/customer/downloadTemplate", {}, "客æ·å¯¼å ¥æ¨¡æ¿.xlsx"); } // æå¼å¼¹æ¡ const openForm = (type, row) => { operationType.value = type; form.value = {}; form.value.maintainer = userStore.nickName; formYYs.value.contactList = [ { contactPerson: "", contactPhone: "", }, ]; form.value.maintenanceTime = getCurrentDate(); userListNoPage().then(res => { userList.value = res.data; }); if (type === "edit") { getCustomer(row.id).then(res => { form.value = { ...res.data }; formYYs.value.contactList = res.data.contactPerson .split(",") .map((item, index) => { return { contactPerson: item, contactPhone: res.data.contactPhone.split(",")[index], }; }); }); } dialogFormVisible.value = true; }; // æäº¤è¡¨å const submitForm = () => { proxy.$refs["formRef"].validate(valid => { if (valid) { if (operationType.value === "edit") { submitEdit(); } else { submitAdd(); } } }); }; // æäº¤æ°å¢ const submitAdd = () => { if (formYYs.value.contactList.length < 1) { return proxy.$modal.msgWarning("请è³å°æ·»å ä¸ä¸ªè系人"); } form.value.contactPerson = formYYs.value.contactList .map(item => item.contactPerson) .join(","); form.value.contactPhone = formYYs.value.contactList .map(item => item.contactPhone) .join(","); addCustomer(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); }; // æäº¤ä¿®æ¹ const submitEdit = () => { form.value.contactPerson = formYYs.value.contactList .map(item => item.contactPerson) .join(","); form.value.contactPhone = formYYs.value.contactList .map(item => item.contactPhone) .join(","); updateCustomer(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); }; // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; }; const ensureUserList = () => { if (userList.value.length) { return Promise.resolve(); } return userListNoPage().then(res => { userList.value = res.data || []; }); }; const openAssignDialog = row => { assignForm.id = row.id; assignForm.customerName = row.customerName; assignForm.boundId = undefined; ensureUserList().then(() => { assignDialogVisible.value = true; }); }; const closeAssignDialog = () => { proxy.resetForm("assignFormRef"); assignForm.id = undefined; assignForm.customerName = ""; assignForm.boundId = undefined; assignDialogVisible.value = false; }; const openShareDialog = row => { shareForm.id = row.id; shareForm.customerName = row.customerName; shareForm.boundIds = []; ensureUserList().then(() => { shareDialogVisible.value = true; }); }; const closeShareDialog = () => { proxy.resetForm("shareFormRef"); shareForm.id = undefined; shareForm.customerName = ""; shareForm.boundIds = []; shareDialogVisible.value = false; }; const submitAssignForm = () => { proxy.$refs.assignFormRef.validate(valid => { if (!valid) { return; } addCustomerPrivatePool({ customerId: assignForm.id, boundId: assignForm.boundId, }).then(() => { proxy.$modal.msgSuccess("åé æå"); closeAssignDialog(); getList(); }); }); }; const submitShareForm = () => { proxy.$refs.shareFormRef.validate(valid => { if (!valid) { return; } shareCustomer({ customerId: shareForm.id, boundIds: shareForm.boundIds, }).then(() => { proxy.$modal.msgSuccess("å ±äº«æå"); closeShareDialog(); getList(); }); }); }; const recycleCustomer = row => { ElMessageBox.confirm("ç¡®è®¤åæ¶å®¢æ·â" + row.customerName + "âåï¼", "åæ¶æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { return delCustomerPrivatePool(row.id); }) .then(() => { proxy.$modal.msgSuccess("åæ¶æå"); getList(); }) .catch(error => { if (error === "cancel" || error === "close") { proxy.$modal.msg("已忶"); } }); }; // å¯¼åº const handleOut = () => { ElMessageBox.confirm("éä¸çå 容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { proxy.download("/basic/customer/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // å é¤ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® const unauthorizedData = selectedRows.value.filter( item => item.maintainer !== userStore.nickName ); if (unauthorizedData.length > 0) { proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); return; } ids = selectedRows.value.map(item => item.id); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { tableLoading.value = true; delCustomer(ids) .then(res => { proxy.$modal.msgSuccess("å 餿å"); getList(); }) .finally(() => { tableLoading.value = false; }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // æå¼å访æéå¼¹çª const openReminderDialog = row => { currentCustomerId.value = row.id; reminderForm.customerName = row.customerName; reminderForm.reminderSwitch = false; reminderForm.reminderContent = ""; reminderForm.reminderTime = ""; // å°è¯è·åå·²æçå访æé getReturnVisit(row.id) .then(res => { if (res.code === 200 && res.data) { reminderForm.reminderSwitch = res.data.isEnabled === 1; reminderForm.reminderContent = res.data.content; reminderForm.reminderTime = res.data.reminderTime; reminderForm.id = res.data.id; } }) .catch(error => { console.error("è·åå访æé失败:", error); }); reminderDialogVisible.value = true; }; // å ³éå访æéå¼¹çª const closeReminderDialog = () => { proxy.resetForm("reminderFormRef"); reminderDialogVisible.value = false; }; const submitvalue = ref({}); // æäº¤å访æé const submitReminderForm = () => { console.log("æäº¤å访æéæ°æ®:", userStore.id, userStore); proxy.$refs.reminderFormRef.validate(valid => { if (valid) { if (reminderForm.id) { submitvalue.value = { id: reminderForm.id, customerId: currentCustomerId.value, isEnabled: reminderForm.reminderSwitch ? 1 : 0, content: reminderForm.reminderContent, reminderTime: reminderForm.reminderTime, remindUserId: userStore.id, }; } else { submitvalue.value = { customerId: currentCustomerId.value, isEnabled: reminderForm.reminderSwitch ? 1 : 0, content: reminderForm.reminderContent, reminderTime: reminderForm.reminderTime, remindUserId: userStore.id, }; } console.log("æäº¤å访æéæ°æ®:", submitvalue.value); // è°ç¨æ¥å£ addReturnVisit(submitvalue.value) .then(res => { if (res.code === 200) { proxy.$modal.msgSuccess("å访æé设置æå"); closeReminderDialog(); } else { proxy.$modal.msgError(res.msg || "设置失败"); } }) .catch(error => { console.error("设置å访æé失败:", error); proxy.$modal.msgError("设置失败"); }); } }); }; // æå¼æ´½è°è¿åº¦å¼¹çª const openNegotiationDialog = row => { negotiationForm.customerName = row.customerName; negotiationForm.customerId = row.id; negotiationForm.followUpMethod = ""; negotiationForm.followUpLevel = ""; negotiationForm.followUpTime = ""; negotiationForm.followerUserName = userStore.nickName; // é»è®¤å½åç»å½äºº negotiationForm.content = ""; // { // "customerId": 152, // "followUpMethod": "çµè¯æ²é", // "followUpLevel": "没ææå", // "followUpTime": "2026-03-04T15:30:00", // "followerUserName": "管çåè´¦å·", // "content": "111" // } negotiationDialogVisible.value = true; }; // å ³éæ´½è°è¿åº¦å¼¹çª const closeNegotiationDialog = () => { proxy.resetForm("negotiationFormRef"); // æ¸ é¤ç¼è¾ç¶æ delete negotiationForm.editIndex; delete negotiationForm.id; negotiationDialogVisible.value = false; }; // æäº¤æ´½è°è¿åº¦ const submitNegotiationForm = () => { proxy.$refs.negotiationFormRef.validate(valid => { if (valid) { // å¤ææ¯æ°å¢è¿æ¯ä¿®æ¹ const isEdit = negotiationForm.editIndex !== undefined; if (isEdit) { // ä¿®æ¹æä½ console.log("ä¿®æ¹æ´½è°è¿åº¦æ°æ®:", negotiationForm); // è¿éå¯ä»¥è°ç¨æ´æ°æ¥å£ // å®é 项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ // 示ä¾ï¼updateCustomerFollow(negotiationForm).then(res => { // // æ´æ°æ¬å°æ°æ® // const index = negotiationForm.editIndex; // negotiationRecords.value[index] = { // followUpTime: negotiationForm.followUpTime, // followUpMethod: negotiationForm.followUpMethod, // followUpLevel: negotiationForm.followUpLevel, // followerUserName: negotiationForm.followerUserName, // content: negotiationForm.content, // id: negotiationForm.id, // }; // proxy.$modal.msgSuccess("ä¿®æ¹æå"); // closeNegotiationDialog(); // }); updateCustomerFollow(negotiationForm).then(res => { // æ´æ°æ¬å°æ°æ® getCustomer(negotiationForm.customerId).then(res => { // æ´æ°æ¬å°æ°æ® negotiationRecords.value = res.data.followUpList || []; }); }); proxy.$modal.msgSuccess("ä¿®æ¹æå"); closeNegotiationDialog(); } else { // æ°å¢æä½ console.log("æäº¤æ´½è°è¿åº¦æ°æ®:", negotiationForm); addCustomerFollow(negotiationForm).then(res => { // æ·»å æååæ´æ°è¯¦æ 页é¢çè¿åº¦è®°å½ const newRecord = { followUpTime: negotiationForm.followUpTime, followUpMethod: negotiationForm.followUpMethod, followUpLevel: negotiationForm.followUpLevel, followerUserName: negotiationForm.followerUserName, content: negotiationForm.content, }; negotiationRecords.value.unshift(newRecord); proxy.$modal.msgSuccess("æäº¤æå"); closeNegotiationDialog(); getList(); }); } } }); }; // æå¼è¯¦æ å¼¹çª const openDetailDialog = row => { // è°ç¨getCustomeræ¥å£è·å客æ·è¯¦æ getCustomer(row.id).then(res => { // å¡«å 客æ·åºæ¬ä¿¡æ¯ Object.assign(detailForm, res.data); // è·åæ´½è°è¿åº¦è®°å½ negotiationRecords.value = res.data.followUpList || []; detailDialogVisible.value = true; }); }; // å ³é详æ å¼¹çª const closeDetailDialog = () => { detailDialogVisible.value = false; }; // ä¿®æ¹æ´½è°è®°å½ const editNegotiationRecord = (row, index) => { // å°å½åè®°å½æ°æ®å¡«å å°è¡¨å Object.assign(negotiationForm, { customerName: row.customerName, customerId: row.customerId, followUpMethod: row.followUpMethod, followUpLevel: row.followUpLevel, followUpTime: row.followUpTime, followerUserName: row.followerUserName, content: row.content, id: row.id, // è®°å½IDç¨äºæ´æ° editIndex: index, // è®°å½ç´¢å¼ç¨äºæ¬å°æ´æ° }); negotiationDialogVisible.value = true; }; // å 餿´½è°è®°å½ const deleteNegotiationRecord = (row, index) => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¿æ¡æ´½è°è®°å½åï¼", "å é¤æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { // è¿éå¯ä»¥è°ç¨å 餿¥å£ // å®é 项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ // 示ä¾ï¼deleteCustomerFollow(row.id).then(() => { // negotiationRecords.value.splice(index, 1); // proxy.$modal.msgSuccess("å 餿å"); // }); delCustomerFollow(row.id).then(() => { // å 餿ååæ´æ°æ¬å°æ°æ® getCustomer(row.customerId).then(res => { // æ´æ°æ¬å°æ°æ® negotiationRecords.value = res.data.followUpList || []; }); proxy.$modal.msgSuccess("å 餿å"); }); // æ¬å°å é¤ï¼æ¨¡æï¼ negotiationRecords.value.splice(index, 1); proxy.$modal.msgSuccess("å 餿å"); }) .catch(() => { proxy.$modal.msg("已忶å é¤"); }); }; // æå¼éä»¶å¼¹çª const openAttachmentDialog = row => { currentFollowRecord.value = row; // 转æ¢ä¸ºç¬¦åElement Plus fileListæ ¼å¼çæ°ç» currentAttachmentList.value = (row.fileList || []).map((file, index) => ({ name: file.fileName, url: file.fileUrl, size: file.fileSize, id: file.id, uid: file.id || index, status: "success", })); attachmentDialogVisible.value = true; }; // å ³ééä»¶å¼¹çª const closeAttachmentDialog = () => { attachmentDialogVisible.value = false; currentFollowRecord.value = {}; currentAttachmentList.value = []; }; // éä»¶ä¸ä¼ æå const handleAttachmentSuccess = (response, file, fileList) => { if (response.code === 200) { proxy.$modal.msgSuccess("ä¸ä¼ æå"); // æ´æ°å½åè®°å½çéä»¶å表 currentAttachmentList.value = fileList.map(item => ({ name: item.name, size: item.size, url: item.response?.data?.url || item.url, id: item.response?.data?.id, uid: item.uid, status: "success", })); // æ´æ°åè®°å½ä¸çfilesåæ®µ if (currentFollowRecord.value) { currentFollowRecord.value.files = [...currentAttachmentList.value]; } } else { proxy.$modal.msgError(response.msg || "ä¸ä¼ 失败"); } }; // éä»¶ä¸ä¼ 失败 const handleAttachmentError = (error, file, fileList) => { console.error("ä¸ä¼ 失败:", error); proxy.$modal.msgError("ä¸ä¼ 失败"); }; // éä»¶ç§»é¤ const handleAttachmentRemove = (file, fileList) => { currentAttachmentList.value = fileList; // æ´æ°åè®°å½ä¸çfilesåæ®µ if (currentFollowRecord.value) { currentFollowRecord.value.files = [...fileList]; } }; // éä»¶ä¸ä¼ åæ ¡éª const beforeAttachmentUpload = file => { const maxSize = 50 * 1024 * 1024; // 50MB if (file.size > maxSize) { proxy.$modal.msgError("æä»¶å¤§å°ä¸è½è¶ è¿50MB"); return false; } return true; }; // æ ¼å¼åæä»¶å¤§å° const formatFileSize = size => { if (size < 1024) { return size + " B"; } else if (size < 1024 * 1024) { return (size / 1024).toFixed(2) + " KB"; } else { return (size / (1024 * 1024)).toFixed(2) + " MB"; } }; // ä¸è½½éä»¶ const downloadAttachment = row => { if (row.url) { // proxy.download(row.url, {}, row.name); proxy.$download.name(row.url); } else { proxy.$modal.msgError("ä¸è½½é¾æ¥ä¸åå¨"); } }; // å é¤éä»¶ const deleteAttachment = (row, index) => { ElMessageBox.confirm("ç¡®å®è¦å é¤è¿ä¸ªéä»¶åï¼", "å é¤æç¤º", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { // è°ç¨å端æ¥å£å é¤éä»¶ const deleteUrl = import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/file/" + row.id; fetch(deleteUrl, { method: "DELETE", headers: { Authorization: "Bearer " + getToken(), "Content-Type": "application/json", }, }) .then(response => response.json()) .then(res => { if (res.code === 200) { // å 餿ååæ´æ°æ¬å°æä»¶å表 currentAttachmentList.value.splice(index, 1); // æ´æ°åè®°å½ä¸çfilesåæ®µ if (currentFollowRecord.value) { currentFollowRecord.value.files = [ ...currentAttachmentList.value, ]; } proxy.$modal.msgSuccess("å 餿å"); } else { proxy.$modal.msgError(res.msg || "å é¤å¤±è´¥"); } }) .catch(error => { console.error("å é¤é件失败:", error); proxy.$modal.msgError("å é¤å¤±è´¥"); }); }) .catch(() => { proxy.$modal.msg("已忶å é¤"); }); }; // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD function getCurrentDate() { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ const day = String(today.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } onMounted(() => { getList(); }); </script> <style scoped lang="scss"> .detail-section { margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 4px; } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .section-title { font-size: 16px; font-weight: bold; margin: 0 0 15px 0; color: #333; } .info-display { background-color: #fff; padding: 15px; border-radius: 4px; } .info-item { margin-bottom: 12px; display: flex; align-items: flex-start; } .info-label { width: 120px; font-weight: 500; color: #606266; margin-right: 10px; } .info-value { flex: 1; color: #303133; word-break: break-word; } .no-records { text-align: center; padding: 30px; color: #999; font-size: 14px; } .attachment-section { .upload-area { margin-bottom: 20px; padding: 20px; background-color: #f9f9f9; border-radius: 4px; border: 1px dashed #d9d9d9; .el-upload__tip { margin-top: 10px; color: #909399; } } .attachment-list { h4 { margin: 0 0 10px 0; font-size: 14px; color: #606266; } } .no-attachment { text-align: center; padding: 40px; color: #909399; font-size: 14px; } } </style> src/views/basicData/product/ProductSelectDialog.vue
@@ -1,12 +1,12 @@ <template> <el-dialog v-model="visible" title="éæ©äº§å" width="900px" destroy-on-close :close-on-click-modal="false"> <el-form :inline="true" :model="query" class="mb-2"> <el-form-item label="产å大类"> <el-input v-model="query.productName" placeholder="è¾å ¥äº§å大类" clearable @keyup.enter="onSearch" /> <el-form-item label="产ååç§°"> <el-input v-model="query.productName" placeholder="è¾å ¥äº§ååç§°" clearable @keyup.enter="onSearch" /> </el-form-item> <el-form-item label="åå·åç§°"> <el-input v-model="query.model" placeholder="è¾å ¥åå·åç§°" clearable @keyup.enter="onSearch" /> <el-form-item label="产ååå·"> <el-input v-model="query.model" placeholder="è¾å ¥äº§ååå·" clearable @keyup.enter="onSearch" /> </el-form-item> <el-form-item> @@ -20,8 +20,8 @@ @selection-change="handleSelectionChange" @select="handleSelect"> <el-table-column type="selection" width="55" /> <el-table-column type="index" label="åºå·" width="60" /> <el-table-column prop="productName" label="产å大类" min-width="160" /> <el-table-column prop="model" label="åå·åç§°" min-width="200" /> <el-table-column prop="productName" label="产ååç§°" min-width="160" /> <el-table-column prop="model" label="产ååå·" min-width="200" /> <el-table-column prop="unit" label="åä½" min-width="160" /> </el-table> @@ -43,7 +43,7 @@ <script setup lang="ts"> import { computed, onMounted, reactive, ref, watch, nextTick } from "vue"; import { ElMessage } from "element-plus"; import { productModelList } from '@/api/basicData/productModel' import { productModelList, productModelListByUrl } from '@/api/basicData/productModel' export type ProductRow = { id: number; @@ -56,6 +56,7 @@ modelValue: boolean; single?: boolean; // æ¯å¦åªè½éæ©ä¸ä¸ªï¼é»è®¤falseï¼å¯éæ©å¤ä¸ªï¼ topProductParentId?: number; // ä¸çº§äº§åid requestUrl?: string; // èªå®ä¹æ¥è¯¢æ¥å£ }>(); const emit = defineEmits(['update:modelValue', 'confirm']); @@ -155,15 +156,19 @@ loading.value = true; try { multipleSelection.value = []; // 翻页/æç´¢åæ¸ ç©ºéæ©æ´ç¬¦å颿 const res: any = await productModelList({ const params = { productName: query.productName.trim(), model: query.model.trim(), current: page.pageNum, size: page.pageSize, topProductParentId: props.topProductParentId, }); tableData.value = res.records; total.value = res.total; }; const res: any = props.requestUrl ? await productModelListByUrl(props.requestUrl, params) : await productModelList(params); const records = res?.records || res?.data?.records || res?.data || []; tableData.value = Array.isArray(records) ? records : []; total.value = Number(res?.total ?? res?.data?.total ?? tableData.value.length); } finally { loading.value = false; } src/views/basicData/product/index.vue
@@ -12,6 +12,7 @@ prefix-icon="Search" /> <el-button v-if="false" type="primary" @click="openProDia('addOne')" style="margin-left: 10px" @@ -49,6 +50,7 @@ <el-button type="primary" link :disabled="isTopLevelNode(data, node)" @click="openProDia('edit', data)" > ç¼è¾ @@ -61,6 +63,7 @@ style="margin-left: 4px" type="danger" link :disabled="isTopLevelNode(data, node)" @click="remove(node, data)" > å é¤ @@ -375,8 +378,18 @@ const searchFilter = () => { proxy.$refs.tree.filter(search.value); }; const isTopLevelNode = (data, node) => { if (node?.level !== undefined) { return node.level === 1; } return [null, undefined, "", 0, "0"].includes(data?.parentId); }; // æå¼äº§åå¼¹æ¡ const openProDia = (type, data) => { if (data && type === "edit" && isTopLevelNode(data)) { proxy.$modal.msgWarning("ä¸çº§èç¹ä¸è½ç¼è¾æå é¤"); return; } operationType.value = type; productDia.value = true; form.value.productName = ""; @@ -425,6 +438,10 @@ // å é¤äº§å const remove = (node, data) => { if (isTopLevelNode(data, node)) { proxy.$modal.msgWarning("ä¸çº§èç¹ä¸è½ç¼è¾æå é¤"); return; } let ids = []; ids.push(data.id); ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { src/views/procurementManagement/procurementLedger/index.vue
@@ -546,6 +546,7 @@ <el-tree-select v-model="productForm.productId" placeholder="è¯·éæ©" clearable filterable check-strictly @change="getModels" :data="productOptions" @@ -560,6 +561,7 @@ prop="productModelId"> <el-select v-model="productForm.productModelId" placeholder="è¯·éæ©" filterable clearable @change="getProductModel"> <el-option v-for="item in modelOptions" @@ -1685,12 +1687,12 @@ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map(item => item.id); ids = selectedRows.value.filter(item => item.salesLedgerId === null).map(item => item.id); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", src/views/procurementManagement/purchaseReturnOrder/New.vue
@@ -70,6 +70,7 @@ v-model="formState.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 240px" filterable @focus="fetchSupplierOptions" @change="handleChangeSupplierId" > src/views/productionManagement/processRoute/index.vue
@@ -171,6 +171,7 @@ path: '/productionManagement/processRouteItem', query: { id: row.id, bomId: row.bomId, processRouteCode: row.processRouteCode || '', productName: row.productName || '', model: row.model || '', src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -47,7 +47,13 @@ </div> </div> </el-card> <div class="section-title" style="margin-bottom: 10px;">产åç»æ</div> <ProductStructureDetail class="product-structure-panel" style="margin-bottom: 20px;" embedded :bom-id="route.query.bomId" /> <!-- è¡¨æ ¼è§å¾ --> <div v-if="viewMode === 'table'" class="section-header"> <div class="section-title">å·¥èºè·¯çº¿é¡¹ç®å表</div> @@ -231,7 +237,7 @@ </template> <script setup> import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue"; import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick, defineAsyncComponent } from "vue"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem } from "@/api/productionManagement/processRouteItem.js"; import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem } from "@/api/productionManagement/productProcessRoute.js"; @@ -242,6 +248,7 @@ const route = useRoute() const { proxy } = getCurrentInstance() || {}; const ProductStructureDetail = defineAsyncComponent(() => import("@/views/productionManagement/productStructure/Detail/index.vue")); const routeId = computed(() => route.query.id); const orderId = computed(() => route.query.orderId); @@ -841,6 +848,10 @@ align-items: center; } .product-structure-panel { margin: 12px 0 20px; } /* å·¥èºè·¯çº¿ä¿¡æ¯å¡çæ ·å¼ */ .route-info-card { margin-bottom: 20px; src/views/productionManagement/productStructure/Detail/index.vue
@@ -1,6 +1,6 @@ <template> <div class="app-container"> <PageHeader content="产åç»æè¯¦æ "> <div :class="embedded ? 'embedded-container' : 'app-container'"> <PageHeader v-if="!embedded" content="产åç»æè¯¦æ "> <template #right-button> <el-button v-if="!dataValue.isEdit && !isOrderPage" type="primary" @@ -119,7 +119,7 @@ </el-form-item> </template> </el-table-column> <el-table-column label="æä½" <el-table-column v-if="!embedded" label="æä½" fixed="right" width="200"> <template #default="{ row, $index }"> @@ -174,6 +174,18 @@ const ProductSelectDialog = defineAsyncComponent( () => import("@/views/basicData/product/ProductSelectDialog.vue") ); const props = defineProps({ embedded: { type: Boolean, default: false, }, // æ¾å¼æå®BOM主é®ï¼ç¨äºåµå ¥å°âå·¥èºè·¯çº¿é¡¹ç®âç页颿¶ï¼è·¯ç± query.id 䏿¯ bomId çæ åµï¼ bomId: { type: [String, Number], default: undefined, }, }); const embedded = computed(() => props.embedded); const emit = defineEmits(["update:router"]); const form = ref(); @@ -181,7 +193,8 @@ const router = useRouter(); const routeId = computed({ get() { return route.query.id; // ä¼å 使ç¨å¤é¨ä¼ å ¥ç bomIdï¼å ¶æ¬¡ä½¿ç¨è·¯ç±ç bomIdï¼æååéå°è·¯ç±ç idï¼å ¼å®¹å页é¢ï¼ return props.bomId ?? route.query.bomId ?? route.query.id; }, set(val) { @@ -227,29 +240,27 @@ }; const fetchData = async () => { if (isOrderPage.value) { // è®¢åæ åµï¼ä½¿ç¨è®¢åç产åç»ææ¥å£ const { data } = await listProcessBom({ orderId: routeOrderId.value }); dataValue.dataList = (data as any) || []; } else { // éè®¢åæ åµï¼ä½¿ç¨åæ¥çæ¥å£ const { data } = await queryList(routeId.value); dataValue.dataList = (data as any) || []; // 为ææé¡¹åå ¶å项设置name屿§ const setNameRecursively = (items: any[]) => { items.forEach((item: any) => { item.tempId = item.id; item.processName = dataValue.processOptions.find(option => option.id === item.processId) ?.name || ""; if (item.children && item.children.length > 0) { setNameRecursively(item.children); } }); }; setNameRecursively(dataValue.dataList); console.log(dataValue.dataList, "dataValue.dataList"); const setNameRecursively = (items: any[]) => { items.forEach((item: any) => { item.tempId = item.tempId || item.id || new Date().getTime() + Math.random(); item.processName = dataValue.processOptions.find(option => option.id === item.processId)?.name || item.processName || ""; if (item.children && item.children.length > 0) { setNameRecursively(item.children); } }); }; // ç»ä¸ä½¿ç¨ BOM æ¥è¯¢äº§åç»æï¼/productStructure/listBybomId/{bomId} // 说æï¼è®¢å页ä¹ä¼ä»è·¯ç±/ç¶ç»ä»¶å¸¦å ¥ bomIdï¼route.query.bomId æ props.bomIdï¼ const bomId = routeId.value; if (!bomId) { dataValue.dataList = []; return; } const { data } = await queryList(bomId); dataValue.dataList = (data as any) || []; setNameRecursively(dataValue.dataList); }; const fetchProcessOptions = async () => { @@ -518,4 +529,11 @@ await fetchProcessOptions(); await fetchData(); }); </script> </script> <style scoped> .embedded-container { padding: 0; margin: 0; } </style> src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -36,8 +36,9 @@ <el-dialog v-model="supplementRecordDialogVisible" title="è¡¥æè®°å½" width="800px"> <el-table v-loading="supplementRecordLoading" :data="supplementRecordTableData" border row-key="id"> <el-table-column label="è¡¥ææ°é" prop="supplementQty" min-width="120" /> <el-table-column label="è¡¥ææ¶é´" prop="supplementTime" min-width="180" /> <el-table-column label="夿³¨" prop="remark" min-width="200" /> <el-table-column label="è¡¥æäºº" prop="supplementUserName" min-width="120" /> <el-table-column label="è¡¥ææ¥æ" prop="supplementTime" min-width="160" /> <el-table-column label="è¡¥æåå " prop="supplementReason" min-width="200" /> </el-table> <template #footer> <span class="dialog-footer"> @@ -54,29 +55,6 @@ <el-table-column label="éææ±æ»æ°é" prop="returnQtyTotal" min-width="140" /> </el-table> <el-card class="approver-card" shadow="never"> <template #header> <div class="card-header-wrapper"> <span class="card-title">审æ¹äººéæ©</span> <el-button type="primary" size="small" @click="addApproverNode">æ°å¢èç¹</el-button> </div> </template> <div class="approver-nodes-container"> <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item"> <div class="approver-node-label"> <span class="node-step">{{ index + 1 }}</span> <span class="node-text">审æ¹äºº</span> </div> <el-select v-model="node.userId" placeholder="éæ©äººå" class="approver-select" clearable> <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> <el-button v-if="approverNodes.length > 1" type="danger" size="small" @click="removeApproverNode(index)"> å é¤ </el-button> </div> </div> </el-card> <template #footer> <span class="dialog-footer"> <el-button type="primary" :loading="materialReturnConfirming" @click="handleReturnConfirm">确认æäº¤</el-button> @@ -91,7 +69,6 @@ import { computed, ref, watch } from "vue"; import { ElMessage } from "element-plus"; import { listMaterialPickingDetail, listMaterialSupplementRecord, confirmMaterialReturn } from "@/api/productionManagement/productionOrder.js"; import { userListNoPageByTenantId } from "@/api/system/user.js"; const props = defineProps({ modelValue: { type: Boolean, default: false }, @@ -112,10 +89,10 @@ const supplementRecordTableData = ref([]); const returnSummaryDialogVisible = ref(false); const returnSummaryList = ref([]); const userList = ref([]); const approverNodes = ref([{ id: Date.now(), userId: undefined }]); const calcReturnQty = item => Number(item.pickQty || 0) + Number(item.supplementQty || 0) - Number(item.actualQty || 0); const canOpenReturnSummary = computed(() => materialDetailTableData.value.some(item => Number(item.returnQty || 0) > 0) materialDetailTableData.value.some(item => calcReturnQty(item) > 0) ); const loadDetailList = async () => { @@ -159,6 +136,8 @@ const buildReturnSummary = () => { const map = new Map(); materialDetailTableData.value.forEach(item => { const returnQty = calcReturnQty(item); if (returnQty <= 0) return; const key = `${item.materialModelId || ""}_${item.materialName || ""}_${item.materialModel || ""}_${item.unit || ""}`; const old = map.get(key) || { summaryKey: key, @@ -167,52 +146,28 @@ unit: item.unit || "", returnQtyTotal: 0, }; old.returnQtyTotal += Number(item.returnQty || 0); old.returnQtyTotal += returnQty; map.set(key, old); }); return Array.from(map.values()); }; const loadUserList = async () => { if (userList.value.length > 0) return; const res = await userListNoPageByTenantId(); userList.value = res.data || []; }; const openReturnSummaryDialog = async () => { if (!canOpenReturnSummary.value) { ElMessage.warning("éææ°é大äº0æ¶æè½éæç¡®è®¤"); ElMessage.warning("éææ°é=é¢ç¨æ°é+è¡¥ææ°é-å®é æ°éï¼ä¸é大äº0"); return; } returnSummaryList.value = buildReturnSummary(); approverNodes.value = [{ id: Date.now(), userId: undefined }]; await loadUserList(); returnSummaryDialogVisible.value = true; }; const addApproverNode = () => { approverNodes.value.push({ id: Date.now() + Math.random(), userId: undefined }); }; const removeApproverNode = index => { approverNodes.value.splice(index, 1); }; const handleReturnConfirm = async () => { if (!props.orderRow?.id) return; const approverList = approverNodes.value .filter(item => item.userId) .map((item, index) => ({ userId: item.userId, sort: index + 1 })); if (approverList.length === 0) { ElMessage.warning("请è³å°éæ©ä¸ä½å®¡æ¹äºº"); return; } materialReturnConfirming.value = true; try { await confirmMaterialReturn({ orderId: props.orderRow.id, returnSummaryList: returnSummaryList.value, approverList, }); returnSummaryDialogVisible.value = false; dialogVisible.value = false; @@ -223,42 +178,4 @@ }; </script> <style scoped lang="scss"> .approver-card { margin-top: 12px; } .card-header-wrapper { display: flex; align-items: center; justify-content: space-between; } .approver-nodes-container { display: flex; flex-direction: column; gap: 8px; } .approver-node-item { display: flex; gap: 8px; align-items: center; } .approver-node-label { display: flex; gap: 4px; min-width: 88px; align-items: center; } .node-step { width: 20px; height: 20px; line-height: 20px; text-align: center; border-radius: 50%; background: #409eff; color: #fff; font-size: 12px; } .approver-select { flex: 1; } </style> <style scoped lang="scss"></style> src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -7,21 +7,24 @@ <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="tempId"> <el-table-column label="å·¥åºåç§°" min-width="180"> <template #default="{ row }"> <span v-if="row.bom === true">{{ row.processName || "-" }}</span> <el-select v-model="row.processId" v-else v-model="row.processName" placeholder="è¯·éæ©å·¥åº" clearable filterable style="width: 100%;" @change="val => handleProcessChange(row, val)" @change="val => handleProcessNameChange(row, val)" > <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.name" /> </el-select> </template> </el-table-column> <el-table-column label="åæåç§°" min-width="160"> <template #default="{ row }"> <el-button type="primary" link @click="openMaterialProductSelect(row)"> <span v-if="row.bom === true">{{ row.materialName || "-" }}</span> <el-button v-else type="primary" link @click="openMaterialProductSelect(row)"> {{ row.materialName || "éæ©åæ" }} </el-button> </template> @@ -33,7 +36,9 @@ </el-table-column> <el-table-column label="éæ±æ°é" min-width="120"> <template #default="{ row }"> <span v-if="row.bom === true">{{ row.requiredQty ?? "-" }}</span> <el-input-number v-else v-model="row.requiredQty" :min="0" :precision="3" @@ -62,8 +67,8 @@ </template> </el-table-column> <el-table-column label="æä½" width="90" fixed="right"> <template #default="{ $index }"> <el-button type="danger" link @click="handleDeleteMaterialRow($index)">å é¤</el-button> <template #default="{ $index, row }"> <el-button v-if="row.bom !== true" type="danger" link @click="handleDeleteMaterialRow($index)">å é¤</el-button> </template> </el-table-column> </el-table> @@ -79,15 +84,21 @@ v-model="materialProductDialogVisible" @confirm="handleMaterialProductConfirm" single request-url="/stockInventory/rawMaterials" /> </div> </template> <script setup> import { computed, ref, watch } from "vue"; import { ElMessage } from "element-plus"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import { processList } from "@/api/productionManagement/productionProcess.js"; import { listMaterialPickingLedger, saveMaterialPickingLedger } from "@/api/productionManagement/productionOrder.js"; import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js"; import { listMaterialPickingDetail, listMaterialPickingLedger, saveMaterialPickingLedger, } from "@/api/productionManagement/productionOrder.js"; const props = defineProps({ modelValue: { type: Boolean, default: false }, @@ -112,7 +123,9 @@ tempId: row.id || `temp_${++materialTempId}`, id: row.id, processId: row.processId, productProcessId: row.productProcessId || row.processId, processName: row.processName || "", bom: row.bom === true, materialModelId: row.materialModelId, materialName: row.materialName || "", materialModel: row.materialModel || "", @@ -122,9 +135,23 @@ }); const getProcessOptions = async () => { if (processOptions.value.length > 0) return; const res = await processList({}); processOptions.value = res.data || []; if (!props.orderRow?.id) return; const res = await findProductProcessRouteItemList({ orderId: props.orderRow.id }); const routeList = Array.isArray(res?.data) ? res.data : res?.data?.records || []; const processMap = new Map(); routeList.forEach(item => { const processId = item.processId; const processName = item.processName; if (!processId || !processName) return; const key = `${processId}_${processName}`; if (!processMap.has(key)) { processMap.set(key, { id: processId, name: processName, }); } }); processOptions.value = Array.from(processMap.values()); }; const loadMaterialData = async () => { @@ -133,8 +160,19 @@ materialTableData.value = []; await getProcessOptions(); try { const res = await listMaterialPickingLedger({ orderId: props.orderRow.id }); materialTableData.value = (res.data || []).map(item => createMaterialRow(item)); const detailRes = await listMaterialPickingDetail({ orderId: props.orderRow.id }); const detailList = Array.isArray(detailRes?.data) ? detailRes.data : detailRes?.data?.records || []; if (detailList.length > 0) { materialTableData.value = detailList.map(item => createMaterialRow(item)); return; } const ledgerRes = await listMaterialPickingLedger({ orderId: props.orderRow.id }); const ledgerList = Array.isArray(ledgerRes?.data) ? ledgerRes.data : ledgerRes?.data?.records || []; materialTableData.value = ledgerList.map(item => createMaterialRow(item)); } finally { materialTableLoading.value = false; } @@ -162,9 +200,9 @@ materialTableData.value.splice(index, 1); }; const handleProcessChange = (row, processId) => { const process = processOptions.value.find(item => item.id === processId); row.processName = process?.name || ""; const handleProcessNameChange = (row, processName) => { const process = processOptions.value.find(item => item.name === processName); row.productProcessId = process?.id; }; const handleRequiredQtyChange = (row, val) => { @@ -186,37 +224,56 @@ if (index < 0 || !materialTableData.value[index]) return; const product = products[0]; const row = materialTableData.value[index]; row.materialModelId = product.id; row.materialName = product.productName || ""; row.materialModel = product.model || ""; row.unit = product.unit || ""; row.materialModelId = product.materialModelId || product.modelId || product.id; row.materialName = product.materialName || product.productName || product.name || ""; row.materialModel = product.materialModel || product.model || ""; row.unit = product.unit || product.measureUnit || ""; currentMaterialSelectRowIndex.value = -1; materialProductDialogVisible.value = false; }; const validateMaterialRows = () => { if (materialTableData.value.length === 0) return false; return !materialTableData.value.find( if (materialTableData.value.length === 0) { return { valid: false, message: "è¯·å æ°å¢é¢ææ°æ®" }; } const invalidNewRow = materialTableData.value.find( item => item.bom !== true && (!item.processName || !item.materialName) ); if (invalidNewRow) { return { valid: false, message: "æ°å¢è¡çå·¥åºåç§°ååæåç§°ä¸ºå¿ å¡«é¡¹" }; } const invalidRow = materialTableData.value.find( item => !item.processId || !item.materialModelId || !item.processName || !item.materialName || item.requiredQty === null || item.requiredQty === undefined || item.pickQty === null || item.pickQty === undefined ); if (invalidRow) { return { valid: false, message: "请å®åå·¥åºãåæåæ°éååä¿å" }; } return { valid: true, message: "" }; }; const handleMaterialSave = async () => { if (!props.orderRow?.id || !validateMaterialRows()) return; if (!props.orderRow?.id) return; const validateResult = validateMaterialRows(); if (!validateResult.valid) { ElMessage.warning(validateResult.message); return; } materialSaving.value = true; try { await saveMaterialPickingLedger({ orderId: props.orderRow.id, items: materialTableData.value.map(item => ({ id: item.id, processId: item.processId, processId: item.processName, productProcessId: item.productProcessId, processName: item.processName, bom: item.bom === true, materialModelId: item.materialModelId, materialName: item.materialName, materialModel: item.materialModel, src/views/productionManagement/productionOrder/index.vue
@@ -48,7 +48,7 @@ @click="handleQuery">æç´¢</el-button> </el-form-item> </el-form> <div> <div class="action-buttons"> <el-button type="primary" @click="isShowNewModal = true">æ°å¢</el-button> <el-button type="danger" @click="handleDelete">å é¤</el-button> <el-button @click="handleOut">导åº</el-button> @@ -224,13 +224,13 @@ openBindRouteDialog(row); }, }, { name: "产åç»æ", type: "text", clickFun: row => { showProductStructure(row); }, }, // { // name: "产åç»æ", // type: "text", // clickFun: row => { // showProductStructure(row); // }, // }, { name: "颿", type: "text", @@ -421,6 +421,7 @@ path: "/productionManagement/processRouteItem", query: { id: data.id, bomId: data.bomId, processRouteCode: data.processRouteCode || "", productName: data.productName || "", model: data.model || "", @@ -504,6 +505,12 @@ align-items: start; } .action-buttons { display: flex; flex-wrap: nowrap; gap: 8px; } :deep(.yellow) { background-color: #FAF0DE; } src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
@@ -11,18 +11,33 @@ <el-table-column label="åæåç§°" prop="materialName" min-width="140" /> <el-table-column label="åæåå·" prop="materialModel" min-width="140" /> <el-table-column label="计éåä½" prop="unit" min-width="100" /> <el-table-column label="é¢ç¨æ°é" prop="pickQty" min-width="100" /> <el-table-column label="çº¿è¾¹ä»æ°é" prop="pickQty" min-width="100" /> <el-table-column label="è¡¥ææ°é" prop="supplementQty" min-width="100" /> <el-table-column label="éææ°é" prop="returnQty" min-width="100" /> <el-table-column label="å®é æ°é" prop="actualQty" min-width="100" /> <el-table-column label="æä½" align="center" fixed="right" width="220"> <el-table-column label="å®é æ°é" min-width="140"> <template #default="{ row }"> <el-input-number v-model="row.actualQty" :min="0" :precision="3" :step="1" controls-position="right" style="width: 100%;" /> </template> </el-table-column> <el-table-column label="æä½" align="center" fixed="right" width="180"> <template #default="{ row }"> <el-button type="primary" link @click="openSupplementDialog(row)">è¡¥æ</el-button> <el-button type="warning" link @click="openReturnDialog(row)">éæ</el-button> <el-button type="info" link @click="openSupplementRecordDialog(row)">è¡¥æè®°å½</el-button> </template> </el-table-column> </el-table> <template #footer> <span class="dialog-footer"> <el-button type="primary" :loading="pickSubmitting" @click="handleSubmitPick">é¢ç¨</el-button> <el-button @click="dialogVisible = false">åæ¶</el-button> </span> </template> </el-dialog> <FormDialog @@ -60,31 +75,6 @@ </template> </FormDialog> <FormDialog v-model="returnDialogVisible" title="éæ" width="500px" @confirm="handleSubmitReturn" > <el-form ref="returnFormRef" :model="returnForm" :rules="returnRules" label-width="120px"> <el-form-item label="éææ°é" prop="returnQty"> <el-input-number v-model="returnForm.returnQty" :min="0.001" :precision="3" :step="1" style="width: 100%;" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button type="primary" :loading="returnSubmitting" @click="handleSubmitReturn">ç¡®å®</el-button> <el-button @click="returnDialogVisible = false">åæ¶</el-button> </span> </template> </FormDialog> <el-dialog v-model="supplementRecordDialogVisible" title="è¡¥æè®°å½" width="900px"> <el-table v-loading="supplementRecordLoading" :data="supplementRecordTableData" border row-key="id"> <el-table-column label="è¡¥ææ°é" prop="supplementQty" min-width="100" /> @@ -108,8 +98,8 @@ import { listWorkOrderMaterialLedger, addWorkOrderMaterialSupplement, addWorkOrderMaterialReturn, listWorkOrderMaterialSupplementRecord, pickWorkOrderMaterial, } from "@/api/productionManagement/workOrder.js"; const props = defineProps({ @@ -134,6 +124,7 @@ const materialTableData = ref([]); const currentMaterialRow = ref(null); const currentMaterialOrderRow = ref(null); const pickSubmitting = ref(false); const supplementDialogVisible = ref(false); const supplementSubmitting = ref(false); @@ -141,13 +132,6 @@ const supplementForm = reactive({ supplementQty: null, supplementReason: "", }); const returnDialogVisible = ref(false); const returnSubmitting = ref(false); const returnFormRef = ref(null); const returnForm = reactive({ returnQty: null, }); const supplementRecordDialogVisible = ref(false); @@ -158,10 +142,6 @@ supplementQty: [{ required: true, message: "请è¾å ¥è¡¥ææ°é", trigger: "blur" }], supplementReason: [{ required: true, message: "请è¾å ¥è¡¥æåå ", trigger: "blur" }], }; const returnRules = { returnQty: [{ required: true, message: "请è¾å ¥éææ°é", trigger: "blur" }], }; const loadMaterialTable = async row => { if (!row?.id) return; currentMaterialOrderRow.value = row; @@ -234,49 +214,6 @@ }); }; const openReturnDialog = row => { currentMaterialRow.value = row; returnForm.returnQty = null; returnDialogVisible.value = true; nextTick(() => { returnFormRef.value?.clearValidate(); }); }; const handleSubmitReturn = () => { returnFormRef.value?.validate(async valid => { if (!valid || !currentMaterialRow.value?.id) { ElMessage.warning("缺å°ç©ææç»ID"); return; } const returnQty = Number(returnForm.returnQty); const minQty = Number(currentMaterialRow.value.pickQty || 0) + Number(currentMaterialRow.value.supplementQty || 0); if (returnQty < minQty) { ElMessage.warning(`éææ°éä¸è½ä½äºé¢ç¨æ°é+è¡¥ææ°éï¼${minQty}ï¼`); return; } returnSubmitting.value = true; try { await addWorkOrderMaterialReturn({ materialLedgerId: currentMaterialRow.value.id, returnQty, workOrderId: currentMaterialOrderRow.value?.id, }); returnDialogVisible.value = false; await loadMaterialTable(currentMaterialOrderRow.value); ElMessage.success("éææå"); emit("refresh"); } catch (e) { console.error("éæå¤±è´¥", e); ElMessage.error("éæå¤±è´¥"); } finally { returnSubmitting.value = false; } }); }; const openSupplementRecordDialog = async row => { supplementRecordDialogVisible.value = true; supplementRecordLoading.value = true; @@ -293,4 +230,49 @@ supplementRecordLoading.value = false; } }; const validatePickRows = () => { if (materialTableData.value.length === 0) { return { valid: false, message: "ææ å¯é¢ç¨ç©æ" }; } const invalidRow = materialTableData.value.find(item => item.actualQty === null || item.actualQty === undefined || item.actualQty === ""); if (invalidRow) { return { valid: false, message: "请填åå®é æ°éååé¢ç¨" }; } const exceedRow = materialTableData.value.find(item => { const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0); return Number(item.actualQty || 0) > maxQty; }); if (exceedRow) { return { valid: false, message: "å®é æ°éä¸è½å¤§äºé¢ç¨æ°é+è¡¥ææ°é" }; } return { valid: true, message: "" }; }; const handleSubmitPick = async () => { if (!currentMaterialOrderRow.value?.id) return; const validateResult = validatePickRows(); if (!validateResult.valid) { ElMessage.warning(validateResult.message); return; } pickSubmitting.value = true; try { await pickWorkOrderMaterial({ workOrderId: currentMaterialOrderRow.value.id, items: materialTableData.value.map(item => ({ materialLedgerId: item.id, actualQty: Number(item.actualQty || 0), })), }); ElMessage.success("é¢ç¨æå"); await loadMaterialTable(currentMaterialOrderRow.value); emit("refresh"); } catch (e) { console.error("é¢ç¨å¤±è´¥", e); ElMessage.error("é¢ç¨å¤±è´¥"); } finally { pickSubmitting.value = false; } }; </script> src/views/productionManagement/workOrderManagement/index.vue
@@ -289,17 +289,17 @@ }, }, { name: "ç©æ", clickFun: row => { openMaterialDialog(row); }, }, { name: "æ¥å·¥", clickFun: row => { showReportDialog(row); }, disabled: row => row.planQuantity <= 0, }, { name: "ç©æ", clickFun: row => { openMaterialDialog(row); }, }, ], }, src/views/salesManagement/returnOrder/components/formDia.vue
@@ -168,10 +168,10 @@ <script setup> import { reactive, ref, toRefs, getCurrentInstance } from "vue"; import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById } from "@/api/salesManagement/returnOrder.js"; import { getAllCustomerList } from "@/api/customerService/index.js"; import useUserStore from "@/store/modules/user.js"; import { userListNoPageByTenantId } from "@/api/system/user.js"; import { listProject } from "@/api/oaSystem/projectManagement.js"; import {listCustomerPrivatePool} from "@/api/basicData/customerFile.js"; const { proxy } = getCurrentInstance(); const emit = defineEmits(['close']) @@ -354,15 +354,14 @@ }; const initCustomers = async () => { const res = await getAllCustomerList({}); if (res?.records) { customerNameOptions.value = res.records.map(item => ({ label: item.customerName, value: item.customerName, // Keep value as name if needed for other logic, but request says customerId id: item.id, code: item.customerCode })); } listCustomerPrivatePool({current: -1,size:-1}).then((res) => { customerNameOptions.value = res.data.records.map(item => ({ label: item.customerName, value: item.customerName, // Keep value as name if needed for other logic, but request says customerId id: item.id, code: item.customerCode })); }); }; const initUsers = async () => { src/views/salesManagement/salesLedger/index.vue
@@ -42,7 +42,7 @@ <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 :data="props.row.children" border show-summary :summary-method="(param) => summarizeChildrenTable(param, props.row)"> <el-table-column align="center" label="åºå·" type="index"/> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> @@ -89,9 +89,9 @@ </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="taxInclusiveUnitPrice" :formatter="sensitiveAmountFormatter" /> <el-table-column label="å«ç¨æ»ä»·(å )" prop="taxInclusiveTotalPrice" :formatter="sensitiveAmountFormatter" /> <el-table-column label="ä¸å«ç¨æ»ä»·(å )" prop="taxExclusiveTotalPrice" :formatter="sensitiveAmountFormatter" /> <!--æä½--> <el-table-column Width="60px" label="æä½" align="center"> <template #default="scope"> @@ -122,7 +122,7 @@ <el-table-column label="夿³¨" prop="remarks" width="200" show-overflow-tooltip /> <el-table-column fixed="right" label="æä½" width="130" align="center"> <template #default="scope"> <el-button link type="primary" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit || scope.row.hasProductionRecord">ç¼è¾</el-button> <el-button link type="primary" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit || scope.row.hasProductionRecord || !canEditLedger(scope.row)">ç¼è¾</el-button> <el-button link type="primary" @click="downLoadFile(scope.row)">éä»¶</el-button> </template> </el-table-column> @@ -149,7 +149,7 @@ </el-col> <el-col :span="12"> <el-form-item label="ä¸å¡åï¼" prop="salesman"> <el-select v-model="form.salesman" placeholder="è¯·éæ©" clearable :disabled="operationType === 'view'"> <el-select v-model="form.salesman" placeholder="è¯·éæ©" clearable :disabled="operationType === 'view'" filterable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> </el-select> @@ -159,7 +159,7 @@ <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="客æ·åç§°ï¼" prop="customerId"> <el-select v-model="form.customerId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'view'"> <el-select v-model="form.customerId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'view'" filterable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> {{ item.customerName + "ââ" + item.taxpayerIdentificationNumber @@ -343,11 +343,8 @@ <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="产å大类ï¼" prop="productCategory"> <!-- <el-select v-model="productForm.productCategory" placeholder="è¯·éæ©" clearable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> </el-select> --> <el-tree-select v-model="productForm.productCategory" placeholder="è¯·éæ©" clearable check-strictly @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> <el-tree-select v-model="productForm.productCategory" placeholder="è¯·éæ©" clearable filterable check-strictly @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-form-item> </el-col> </el-row> @@ -677,6 +674,7 @@ import useFormData from "@/hooks/useFormData.js"; import dayjs from "dayjs"; import { getCurrentDate } from "@/utils/index.js"; import {listCustomerPrivatePool} from "@/api/basicData/customerFile.js"; const userStore = useUserStore(); const { proxy } = getCurrentInstance(); @@ -926,7 +924,49 @@ }); }; const formattedNumber = (row, column, cellValue) => { if (cellValue === undefined || cellValue === null || cellValue === "") { return "0.00"; } return parseFloat(cellValue).toFixed(2); }; const findLedgerRecordByRow = (row) => { if (!row) return null; if ( row.maintainer !== undefined || row.maintainerName !== undefined || row.entryPerson !== undefined || row.entryPersonName !== undefined ) { return row; } if (row.salesLedgerId !== undefined && row.salesLedgerId !== null) { return tableData.value.find((item) => String(item.id) === String(row.salesLedgerId)) || null; } return null; }; const isCurrentUserMaintainer = (row) => { const ledgerRecord = findLedgerRecordByRow(row); if (!ledgerRecord) return true; const currentUserId = String(userStore.id ?? ""); const currentNickName = String(userStore.nickName ?? "").trim(); const maintainerId = ledgerRecord.maintainerId ?? ledgerRecord.entryPerson; const maintainerName = ledgerRecord.maintainerName ?? ledgerRecord.maintainer ?? ledgerRecord.entryPersonName; if (maintainerId !== undefined && maintainerId !== null && String(maintainerId) !== "") { return String(maintainerId) === currentUserId; } if (maintainerName !== undefined && maintainerName !== null && String(maintainerName).trim() !== "") { return String(maintainerName).trim() === currentNickName; } return true; }; const canEditLedger = (row) => isCurrentUserMaintainer(row); const canDeleteLedger = (row) => isCurrentUserMaintainer(row); const sensitiveAmountFormatter = (row, column, cellValue) => { if (!isCurrentUserMaintainer(row)) { return "*****"; } return formattedNumber(row, column, cellValue); }; // è·åtreeåæ°æ® const getModels = (value) => { @@ -1041,7 +1081,19 @@ ]); }; // å表åè®¡æ¹æ³ const summarizeChildrenTable = (param) => { const summarizeChildrenTable = (param, parentRow) => { if (!isCurrentUserMaintainer(parentRow)) { const { columns } = param; return columns.map((column, index) => { if (index === 0) { return "å计"; } if (["taxInclusiveUnitPrice", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice"].includes(column.property)) { return "*****"; } return ""; }); } return proxy.summarizeTable(param, [ "taxInclusiveUnitPrice", "taxInclusiveTotalPrice", @@ -1050,14 +1102,18 @@ }; // æå¼å¼¹æ¡ const openForm = async (type, row) => { if (type === "edit" && row && !canEditLedger(row)) { proxy.$modal.msgWarning("å½åç³»ç»ç»å½äººä¸æ¯ç»´æ¤äººï¼ä¸è½ç¼è¾æ°æ®"); return; } operationType.value = type; form.value = {}; productData.value = []; selectedQuotation.value = null; let userLists = await userListNoPage(); userList.value = userLists.data; customerList().then((res) => { customerOption.value = res; listCustomerPrivatePool({current: -1,size:-1}).then((res) => { customerOption.value = res.data.records; }); form.value.entryPerson = userStore.id; if (type === "add") { @@ -1093,8 +1149,9 @@ // å ç¡®ä¿å®¢æ·å表已å è½½ï¼ä¾¿äºåç»åå¡« customerId if (!customerOption.value || customerOption.value.length === 0) { try { const res = await customerList(); customerOption.value = res; listCustomerPrivatePool({current: -1,size:-1}).then((res) => { customerOption.value = res.data.records; }); } catch (e) { // ignoreï¼å è®¸ç¨æ·åç»æå¨éæ©å®¢æ· } @@ -1433,6 +1490,11 @@ proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } const unauthorizedRows = selectedRows.value.filter((row) => !canDeleteLedger(row)); if (unauthorizedRows.length > 0) { proxy.$modal.msgWarning("å½åç»å½ç¨æ·ä¸æ¯å½å ¥äººï¼ä¸è½å é¤è¯¥æ°æ®"); return; } const ids = selectedRows.value.map((item) => item.id); // æ£æ¥æ¯å¦æå·²è¿è¡åè´§æåè´§å®æçéå®è®¢åï¼è¥æåä¸å 许å é¤ src/views/salesManagement/salesQuotation/index.vue
@@ -103,18 +103,14 @@ <el-row :gutter="24"> <el-col :span="12"> <el-form-item label="客æ·åç§°" prop="customer"> <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%" @change="handleCustomerChange" clearable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> {{ item.customerName + "ââ" + item.taxpayerIdentificationNumber }} </el-option> <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%" clearable filterable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"></el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸å¡å" prop="salesperson"> <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%" clearable> <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%" clearable filterable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> </el-select> @@ -190,6 +186,7 @@ v-model="node.userId" placeholder="éæ©äººå" class="approver-select" filterable clearable > <el-option @@ -363,6 +360,7 @@ import {userListNoPage} from "@/api/system/user.js"; import {customerList} from "@/api/salesManagement/salesLedger.js"; import {modelList, productTreeList} from "@/api/basicData/product.js"; import {listCustomerPrivatePool} from "@/api/basicData/customerFile.js"; // ååºå¼æ°æ® const loading = ref(false) @@ -492,13 +490,8 @@ userName: item.userName || '' })); getProductOptions(); customerList().then((res) => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ id: item.id, customerName: item.customerName || '', taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' })) listCustomerPrivatePool({current: -1,size:-1}).then((res) => { customerOption.value = res.data.records; }); } const getProductOptions = () => { @@ -783,10 +776,6 @@ form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount } const handleCustomerChange = () => { // å¯ä»¥æ ¹æ®å®¢æ·ä¿¡æ¯èªå¨å¡«å ä¸äºé»è®¤å¼ } const handleSubmit = () => { formRef.value.validate((valid) => { if (valid) { @@ -889,12 +878,7 @@ } }) customerList().then((res) => { // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ id: item.id, customerName: item.customerName || '', taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' })) customerOption.value = res; }); }