| src/api/salesManagement/salesProcessFlowConfig.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/customerFile/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/components/OtherAmountMaintenanceButton.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/components/ProcessFlowMaintenanceButton.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/salesManagement/salesProcessFlowConfig.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,89 @@ // å·¥èºæµç¨é ç½®ï¼æ¨¡æ¿ï¼ä¸ç»å®å°éå®å°è´¦äº§åçæ¥å£ import request from "@/utils/request"; // å·¥èºè·¯çº¿å页å表 export function salesProcessFlowConfigList(query = {}) { return request({ url: "/processRoute/page", method: "get", params: query, }); } // å·¥èºè·¯çº¿è¯¦æ ï¼å ¼å®¹æ§è°ç¨ï¼ export function salesProcessFlowConfigGetById(configId) { return request({ url: `/processRoute/${configId}`, method: "get", }); } // æ°å¢/ç¼è¾å·¥èºè·¯çº¿ export function salesProcessFlowConfigUpsert(data) { return request({ url: "/processRoute", method: "post", data: data, }); } // å é¤å·¥èºè·¯çº¿ export function salesProcessFlowConfigDelete(configId) { return request({ url: `/processRoute/${configId}`, method: "delete", }); } // 设置é»è®¤å·¥èºè·¯çº¿ export function salesProcessFlowConfigSetDefault(configId) { return request({ url: `/processRoute/default/${configId}`, method: "put", }); } // æ¥è¯¢å·¥èºè·¯çº¿ä¸çå·¥åº export function salesProcessFlowConfigItemList(routeId) { return request({ url: "/processRouteItem/list", method: "get", params: { routeId }, }); } // æ°å¢/ä¿®æ¹å·¥åº export function salesProcessFlowConfigItemUpsert(data) { return request({ url: "/processRouteItem/", method: "post", data, }); } // å·¥åºæåºæ¥å£ export function salesProcessFlowConfigItemSort(data) { return request({ url: "/processRouteItem/sort", method: "post", data, }); } // å é¤å·¥åº export function salesProcessFlowConfigItemDelete(itemId) { return request({ url: `/processRouteItem/batchDelete/${itemId}`, method: "delete", }); } // å°æå¥é ç½®åºç¨å°å ·ä½äº§åï¼åæ¥æ´æ°ï¼ export function salesLedgerProductSetProcessFlowConfig(data) { return request({ url: "/salesLedgerProduct/setProcessFlowConfig", method: "post", data: data, }); } src/views/basicData/customerFile/index.vue
@@ -76,7 +76,7 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å ¬å¸å°åï¼" <el-form-item label="客æ·å°åï¼" prop="companyAddress"> <el-input v-model="form.companyAddress" placeholder="请è¾å ¥" @@ -84,11 +84,35 @@ </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å ¬å¸çµè¯ï¼" <el-form-item label="客æ·å°åºï¼" prop="regions"> <el-input v-model="form.regions" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="客æ·çµè¯ï¼" prop="companyPhone"> <el-input v-model="form.companyPhone" 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> @@ -117,19 +141,6 @@ <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> @@ -720,6 +731,11 @@ width: 220, }, { label: "客æ·å°åº", prop: "regions", width: 120, }, { label: "纳ç¨äººè¯å«ç ", prop: "taxpayerIdentificationNumber", width: 220, src/views/salesManagement/salesLedger/components/OtherAmountMaintenanceButton.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,278 @@ <template> <div style="display: inline-block;"> <el-button type="primary" plain @click="openDialog"> å ¶ä»éé¢ç»´æ¤ </el-button> <el-dialog v-model="visible" title="å ¶ä»éé¢ç»´æ¤" width="80%" :close-on-click-modal="false" @close="closeDialog" > <el-row :gutter="20"> <el-col :span="14"> <el-table :data="records" border v-loading="loading" height="55vh" > <el-table-column label="ç¼ç " prop="code" min-width="120" show-overflow-tooltip /> <el-table-column label="项ç®" prop="processName" min-width="180" show-overflow-tooltip /> <el-table-column label="æ°é" prop="quantity" min-width="110" :formatter="formattedNumber" /> <el-table-column label="åä»·(å )" prop="unitPrice" min-width="130" :formatter="formattedNumber" /> <el-table-column label="éé¢(å )" prop="amount" min-width="160" :formatter="formattedNumber" /> <el-table-column fixed="right" label="æä½" width="160" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="handleEdit(scope.row)">ç¼è¾</el-button> <el-button link type="danger" size="small" @click="handleDelete(scope.row)">å é¤</el-button> </template> </el-table-column> </el-table> <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> </el-col> <el-col :span="10"> <div style="padding: 8px 0;"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 10px;"> <div style="font-weight:600;"> {{ operationType === "add" ? "æ°å¢å ¶ä»éé¢" : "ç¼è¾å ¶ä»éé¢" }} </div> <el-button type="primary" plain size="small" @click="handleAdd" :disabled="operationType === 'add'" > æ°å¢ </el-button> </div> <el-form :model="form" label-width="120px" label-position="top" :rules="rules" ref="formRef" > <el-form-item label="ç¼ç "> <el-input v-model="form.code" placeholder="请è¾å ¥ç¼ç ï¼å¯éï¼" clearable /> </el-form-item> <el-form-item label="项ç®" prop="processName"> <el-input v-model="form.processName" placeholder="请è¾å ¥é¡¹ç®åç§°" clearable /> </el-form-item> <el-form-item label="æ°é" prop="quantity"> <el-input-number v-model="form.quantity" :min="0" :precision="2" style="width:100%" placeholder="请è¾å ¥æ°é" clearable @change="recalcAmount" /> </el-form-item> <el-form-item label="åä»·(å )" prop="unitPrice"> <el-input-number v-model="form.unitPrice" :min="0" :precision="2" style="width:100%" placeholder="请è¾å ¥åä»·" clearable @change="recalcAmount" /> </el-form-item> <el-form-item label="éé¢(å )"> <el-input v-model="form.amount" disabled /> </el-form-item> <div style="display:flex; justify-content:flex-end; gap: 10px; margin-top: 8px;"> <el-button @click="closeDialog">åæ¶</el-button> <el-button type="primary" @click="submitForm">ä¿å</el-button> </div> </el-form> </div> </el-col> </el-row> </el-dialog> </div> </template> <script setup> import { getCurrentInstance, reactive, ref } from "vue"; import { ElMessageBox } from "element-plus"; import pagination from "@/components/PIMTable/Pagination.vue"; import { salesLedgerProductProcessList, salesLedgerProductProcessAdd, salesLedgerProductProcessUpdate, salesLedgerProductProcessDelete, } from "@/api/salesManagement/salesLedger.js"; const { proxy } = getCurrentInstance(); const visible = ref(false); const loading = ref(false); const records = ref([]); const total = ref(0); const page = reactive({ current: 1, size: 10, }); const operationType = ref("add"); const formRef = ref(null); const form = reactive({ id: null, code: "", processName: "", quantity: 0, unitPrice: 0, amount: "0.00", }); const rules = reactive({ processName: [{ required: true, message: "请è¾å ¥é¡¹ç®åç§°", trigger: "change" }], quantity: [{ required: true, message: "请è¾å ¥æ°é", trigger: "blur" }], unitPrice: [{ required: true, message: "请è¾å ¥åä»·", trigger: "blur" }], }); const formattedNumber = (row, column, cellValue) => { return Number(cellValue || 0).toFixed(2); }; const recalcAmount = () => { const quantity = Number(form.quantity ?? 0) || 0; const unitPrice = Number(form.unitPrice ?? 0) || 0; form.amount = (quantity * unitPrice).toFixed(2); }; const resetForm = (type = "add") => { operationType.value = type; form.id = null; form.code = ""; form.processName = ""; form.quantity = 0; form.unitPrice = 0; form.amount = "0.00"; }; const fetchList = async () => { loading.value = true; try { const res = await salesLedgerProductProcessList({ current: page.current, size: page.size, }); const list = res?.records ?? res?.data?.records ?? []; const sum = res?.total ?? res?.data?.total ?? 0; records.value = list.map((item) => { const quantity = Number(item.quantity ?? 0) || 0; const unitPrice = Number(item.unitPrice ?? 0) || 0; const amount = Number(item.amount ?? quantity * unitPrice) || 0; return { id: item.id, code: item.code ?? item.remark ?? "", processName: item.processName ?? "", quantity, unitPrice, amount: amount.toFixed(2), }; }); total.value = sum; } finally { loading.value = false; } }; const openDialog = () => { visible.value = true; resetForm("add"); fetchList(); }; const closeDialog = () => { visible.value = false; resetForm("add"); }; const paginationChange = (obj) => { page.current = obj.page; page.size = obj.limit; fetchList(); }; const handleAdd = () => { resetForm("add"); }; const handleEdit = (row) => { if (!row) return; operationType.value = "edit"; form.id = row.id ?? null; form.code = row.code ?? ""; form.processName = row.processName ?? ""; form.quantity = Number(row.quantity ?? 0) || 0; form.unitPrice = Number(row.unitPrice ?? 0) || 0; recalcAmount(); }; const submitForm = () => { formRef.value?.validate((valid) => { if (!valid) return; const payload = { processName: form.processName, quantity: Number(form.quantity) || 0, unitPrice: Number(form.unitPrice) || 0, amount: Number(form.amount) || 0, remark: form.code, code: form.code, }; const req = operationType.value === "edit" ? salesLedgerProductProcessUpdate({ ...payload, id: form.id }) : salesLedgerProductProcessAdd(payload); req.then(() => { proxy.$modal.msgSuccess("ä¿åæå"); fetchList(); resetForm("add"); }); }); }; const handleDelete = (row) => { if (!row?.id) return; ElMessageBox.confirm("确认å é¤è¯¥è®°å½ï¼", "å é¤", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { return salesLedgerProductProcessDelete(row.id).then(() => { proxy.$modal.msgSuccess("å 餿å"); fetchList(); if (operationType.value === "edit" && form.id === row.id) { resetForm("add"); } }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; </script> src/views/salesManagement/salesLedger/components/ProcessFlowConfigSelectDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,234 @@ <template> <el-dialog v-model="visible" title="鿩工èºè·¯çº¿é ç½®" width="1000px" :close-on-click-modal="false" @close="handleClose" > <el-row :gutter="20"> <el-col :span="10"> <div style="font-weight: 600; margin-bottom: 8px;">é ç½®</div> <el-select v-model="selectedConfigId" filterable clearable placeholder="è¯·éæ©å·¥èºè·¯çº¿é ç½®" style="width: 100%;" @change="handleConfigChange" > <el-option v-for="cfg in configList" :key="cfg.configId" :label="cfg.configName" :value="cfg.configId" /> </el-select> <el-divider style="margin: 16px 0;" /> <div style="font-weight: 600; margin-bottom: 8px;">æ¥éª¤é¢è§</div> <div style="font-size: 12px; color: #909399; margin-bottom: 6px;"> æ ¹æ®æéé ç½®å±ç¤ºæµç¨å¾ </div> </el-col> <el-col :span="14"> <div class="process-diagram"> <div v-if="steps.length === 0" class="process-diagram-empty">ææ æ¥éª¤</div> <div v-for="(step, idx) in steps" :key="String(step.processId) + '_' + idx" class="process-diagram-segment" > <div class="process-diagram-node"> <div class="process-diagram-index">{{ idx + 1 }}</div> <div class="process-diagram-name">{{ step.processName }}</div> </div> <div v-if="idx < steps.length - 1" class="process-diagram-arrow">â</div> </div> </div> <div v-if="selectedConfigId === null" style="margin-top: 10px; font-size: 12px; color: #909399;"> 请å 鿩䏿¡å·²ç»´æ¤å¥½çå·¥èºè·¯çº¿ </div> </el-col> </el-row> <template #footer> <div class="dialog-footer"> <el-button @click="handleClose">åæ¶</el-button> <el-button type="primary" :loading="saving" @click="confirmSelect"> ç¡®å® </el-button> </div> </template> </el-dialog> </template> <script setup> import { computed, getCurrentInstance, ref, watch } from "vue"; import { salesProcessFlowConfigList, salesProcessFlowConfigGetById } from "@/api/salesManagement/salesProcessFlowConfig.js"; const emit = defineEmits(["update:visible", "confirm"]); const props = defineProps({ visible: { type: Boolean, default: false }, defaultConfigId: { type: [Number, String, null], default: null }, }); const { proxy } = getCurrentInstance(); const visible = computed({ get() { return props.visible; }, set(v) { emit("update:visible", v); }, }); const configList = ref([]); const selectedConfigId = ref(null); const steps = ref([]); const saving = ref(false); const normalizeStepsFromApi = (list) => { if (!Array.isArray(list)) return []; return list.map((s, idx) => ({ stepId: s.stepId ?? s.id ?? null, processId: s.processId ?? s.process_id ?? s.id ?? null, processName: s.processName ?? s.process_name ?? s.name ?? "", sortNo: s.sortNo ?? idx + 1, })); }; const fetchConfigList = async () => { const res = await salesProcessFlowConfigList(); const list = res?.data ?? res?.records ?? res ?? []; configList.value = Array.isArray(list) ? list : []; }; const fetchConfigDetail = async (id) => { if (!id) { steps.value = []; return; } const res = await salesProcessFlowConfigGetById(id); const detail = res?.data ?? res ?? {}; steps.value = normalizeStepsFromApi(detail?.steps ?? []); }; const initDefault = async () => { await fetchConfigList(); selectedConfigId.value = props.defaultConfigId ?? null; await fetchConfigDetail(selectedConfigId.value); }; watch( () => props.visible, async (v) => { if (v) { try { await initDefault(); } catch { proxy?.$modal?.msgError?.("è·åå·¥èºè·¯çº¿é 置失败"); } } } ); const handleConfigChange = async () => { await fetchConfigDetail(selectedConfigId.value); }; const handleClose = () => { emit("update:visible", false); saving.value = false; }; const confirmSelect = async () => { if (saving.value) return; if (selectedConfigId.value === null || selectedConfigId.value === undefined || selectedConfigId.value === "") { proxy?.$modal?.msgWarning?.("è¯·éæ©å·¥èºè·¯çº¿é ç½®"); return; } saving.value = true; try { emit("confirm", selectedConfigId.value); handleClose(); } catch (e) { proxy?.$modal?.msgError?.("确认失败ï¼è¯·ç¨åéè¯"); } finally { saving.value = false; } }; </script> <style scoped> .process-diagram { display: flex; align-items: center; gap: 0; flex-wrap: nowrap; overflow-x: auto; padding: 10px 0; } .process-diagram-segment { display: flex; align-items: center; } .process-diagram-node { width: 160px; min-width: 160px; height: 78px; border: 1px solid #ebeef5; border-radius: 10px; background: #fff; display: flex; flex-direction: column; justify-content: center; padding: 10px 12px; margin-right: 10px; box-sizing: border-box; } .process-diagram-index { font-size: 12px; color: #909399; margin-bottom: 4px; } .process-diagram-name { font-size: 14px; font-weight: 600; color: #303133; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .process-diagram-arrow { font-size: 18px; color: #909399; margin-right: 14px; margin-left: -6px; } .process-diagram-empty { width: 100%; text-align: center; padding: 40px 0; color: #909399; border: 1px dashed #ebeef5; border-radius: 8px; } .dialog-footer { display: flex; justify-content: flex-end; gap: 10px; } </style> src/views/salesManagement/salesLedger/components/ProcessFlowMaintenanceButton.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,525 @@ <template> <div style="display: inline-block; margin-left: 8px;"> <el-button type="primary" plain @click="openDialog">å·¥èºæµç¨</el-button> <el-dialog v-model="visible" title="å·¥èºè·¯çº¿ä¸å·¥åºç»´æ¤" width="1280px" class="process-route-dialog" :close-on-click-modal="false" @close="closeDialog" > <el-row :gutter="16" class="dialog-main"> <el-col :span="10"> <div class="left-panel"> <div style="font-weight: 600; margin-bottom: 10px;">å·¥èºè·¯çº¿</div> <div class="route-toolbar route-toolbar-left"> <el-input v-model="routeKeyword" placeholder="æå·¥èºè·¯çº¿åç§°æ¥è¯¢" clearable @keyup.enter="handleRouteQuery" /> <el-button type="primary" @click="handleRouteQuery">æ¥è¯¢</el-button> <el-input v-model="routeNameDraft" placeholder="è¾å ¥åç§°åæ°å¢" clearable /> <el-button type="primary" plain @click="createRoute">æ°å¢</el-button> </div> <div class="left-table-wrap"> <el-table :data="routeList" border row-key="routeId" highlight-current-row height="100%" table-layout="fixed" @current-change="handleRouteSelect" > <el-table-column label="åºå·" width="56" align="center"> <template #default="scope">{{ scope.$index + 1 }}</template> </el-table-column> <el-table-column label="å·¥èºè·¯çº¿åç§°" min-width="150" prop="processRouteName" show-overflow-tooltip /> <el-table-column label="é»è®¤" width="56" align="center"> <template #default="scope"> <el-tag v-if="scope.row.isDefault" type="success" size="small">æ¯</el-tag> <span v-else>-</span> </template> </el-table-column> <el-table-column label="æä½" width="160" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="editRoute(scope.row)">æ¹å</el-button> <el-button link type="primary" size="small" @click="setDefaultRoute(scope.row)">é»è®¤</el-button> <el-button link type="danger" size="small" @click="deleteRoute(scope.row)">å é¤</el-button> </template> </el-table-column> </el-table> </div> <pagination v-show="routeTotal > 0" :total="routeTotal" layout="total, sizes, prev, pager, next, jumper" :page="routePage.current" :limit="routePage.size" @pagination="handleRoutePaginationChange" /> </div> </el-col> <el-col :span="14"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;"> <div style="font-weight: 600;">å·¥åºç»´æ¤</div> <el-tag v-if="selectedRouteId" type="success" size="small">å½å路线ï¼{{ selectedRouteName }}</el-tag> <el-tag v-else type="info" size="small">请å 鿩工èºè·¯çº¿</el-tag> </div> <div class="route-toolbar"> <el-input v-model="processNameDraft" placeholder="è¾å ¥å·¥åºåç§°åæ°å¢" style="max-width: 260px;" clearable :disabled="!selectedRouteId" /> <el-button type="primary" plain :disabled="!selectedRouteId" @click="createProcessItem">æ°å¢å·¥åº</el-button> </div> <div class="process-diagram"> <div v-if="processItems.length === 0" class="process-diagram-empty">ææ å·¥åº</div> <div v-for="(step, idx) in processItems" :key="String(step.itemId) + '_' + idx" class="process-diagram-segment" > <div class="process-diagram-node"> <div class="process-diagram-index">{{ idx + 1 }}</div> <div class="process-diagram-name">{{ step.processName }}</div> </div> <div v-if="idx < processItems.length - 1" class="process-diagram-arrow">â</div> </div> </div> <el-table ref="processTableRef" :data="processItems" border row-key="itemId" height="420px" size="small"> <el-table-column label="åºå·" width="80" align="center"> <template #default="scope">{{ scope.$index + 1 }}</template> </el-table-column> <el-table-column label="å·¥åºåç§°" min-width="220" prop="processName" show-overflow-tooltip /> <el-table-column label="æä½" width="300" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="editProcessItem(scope.row)">ç¼è¾</el-button> <el-button link type="danger" size="small" @click="deleteProcessItem(scope.row)">å é¤</el-button> </template> </el-table-column> </el-table> </el-col> </el-row> </el-dialog> </div> </template> <script setup> import { getCurrentInstance, ref, watch, onBeforeUnmount, nextTick } from "vue"; import { ElMessageBox } from "element-plus"; import Sortable from "sortablejs"; import { salesProcessFlowConfigList, salesProcessFlowConfigUpsert, salesProcessFlowConfigDelete, salesProcessFlowConfigSetDefault, salesProcessFlowConfigItemList, salesProcessFlowConfigItemUpsert, salesProcessFlowConfigItemSort, salesProcessFlowConfigItemDelete, } from "@/api/salesManagement/salesProcessFlowConfig.js"; const { proxy } = getCurrentInstance(); const visible = ref(false); let prevBodyOverflow = ""; let prevBodyOverflowY = ""; const lockBodyScroll = () => { // å åºå¤çï¼æäºåºæ¯ä¸ Element Plus ä¸ä¼å®å ¨ç¦æ¢èæ¯é¡µé¢æ»å¨ prevBodyOverflow = document.body.style.overflow || ""; prevBodyOverflowY = document.body.style.overflowY || ""; document.body.style.overflow = "hidden"; document.body.style.overflowY = "hidden"; }; const unlockBodyScroll = () => { document.body.style.overflow = prevBodyOverflow; document.body.style.overflowY = prevBodyOverflowY; }; watch(visible, (v) => { if (v) lockBodyScroll(); else unlockBodyScroll(); }); onBeforeUnmount(() => { unlockBodyScroll(); }); const routeKeyword = ref(""); const routeNameDraft = ref(""); const processNameDraft = ref(""); const routeList = ref([]); const routeTotal = ref(0); const routePage = ref({ current: 1, size: 10, }); const selectedRouteId = ref(null); const selectedRouteName = ref(""); const processItems = ref([]); const processTableRef = ref(null); let processStepsSortable = null; let isProcessingDrag = false; const destroyProcessSortable = () => { if (processStepsSortable) { processStepsSortable.destroy(); processStepsSortable = null; } }; const initProcessSortable = () => { destroyProcessSortable(); if (!processTableRef.value) return; const tbody = processTableRef.value?.$el?.querySelector(".el-table__body tbody") || processTableRef.value?.$el?.querySelector(".el-table__body-wrapper > table > tbody"); if (!tbody) return; processStepsSortable = new Sortable(tbody, { animation: 150, ghostClass: "sortable-ghost", draggable: ".el-table__row", handle: ".el-table__row", filter: ".el-button, .el-input, .el-select", preventOnFilter: true, onEnd: async (evt) => { if (isProcessingDrag) return; const { oldIndex, newIndex } = evt; if (oldIndex === newIndex) return; if (!selectedRouteId.value) return; if (!processItems.value[oldIndex]) return; isProcessingDrag = true; try { const arr = [...processItems.value]; const moving = arr.splice(oldIndex, 1)[0]; arr.splice(newIndex, 0, moving); processItems.value = arr; ensureSortNo(); if (!moving?.itemId) { proxy?.$modal?.msgError?.("å½åå·¥åºç¼ºå°IDï¼æ æ³æåº"); await fetchProcessItems(selectedRouteId.value); return; } // 使ç¨ä¸ç¨æåºæ¥å£ï¼é¿å upsert é æé夿°å¢ await salesProcessFlowConfigItemSort({ id: moving.itemId, dragSort: newIndex + 1, }); proxy?.$modal?.msgSuccess?.("顺åºè°æ´æå"); await fetchProcessItems(selectedRouteId.value); } finally { isProcessingDrag = false; } }, }); }; const normalizeRouteList = (list) => { if (!Array.isArray(list)) return []; return list.map((r) => ({ routeId: r.routeId ?? r.id ?? null, processRouteName: r.processRouteName ?? r.routeName ?? r.name ?? "", isDefault: Boolean(r.isDefault), })); }; const normalizeItemList = (list) => { if (!Array.isArray(list)) return []; return list.map((i, idx) => ({ itemId: i.itemId ?? i.id ?? null, routeId: i.routeId ?? i.processRouteId ?? selectedRouteId.value, processName: i.processName ?? i.name ?? "", sortNo: i.sortNo ?? idx + 1, })); }; const fetchRouteList = async () => { const res = await salesProcessFlowConfigList({ current: routePage.value.current, size: routePage.value.size, processRouteName: routeKeyword.value || undefined, }); const records = res?.records ?? res?.data?.records ?? []; const total = res?.total ?? res?.data?.total ?? 0; routeTotal.value = Number(total) || 0; routeList.value = normalizeRouteList(records); }; const handleRouteQuery = async () => { routePage.value.current = 1; await fetchRouteList(); }; const handleRoutePaginationChange = async (obj) => { routePage.value.current = obj.page; routePage.value.size = obj.limit; await fetchRouteList(); }; const fetchProcessItems = async (routeId) => { if (!routeId) { processItems.value = []; destroyProcessSortable(); return; } const res = await salesProcessFlowConfigItemList(routeId); const raw = res?.data ?? res ?? []; processItems.value = normalizeItemList(raw); ensureSortNo(); await nextTick(); initProcessSortable(); }; const openDialog = async () => { visible.value = true; selectedRouteId.value = null; selectedRouteName.value = ""; processItems.value = []; routePage.value.current = 1; await fetchRouteList(); }; const closeDialog = () => { visible.value = false; selectedRouteId.value = null; selectedRouteName.value = ""; processItems.value = []; routeNameDraft.value = ""; processNameDraft.value = ""; routeTotal.value = 0; destroyProcessSortable(); }; const handleRouteSelect = async (row) => { if (!row?.routeId) return; selectedRouteId.value = row.routeId; selectedRouteName.value = row.processRouteName; await fetchProcessItems(selectedRouteId.value); }; const createRoute = async () => { if (!routeNameDraft.value) { proxy?.$modal?.msgWarning("请å è¾å ¥å·¥èºè·¯çº¿åç§°"); return; } const payload = { processRouteName: routeNameDraft.value }; await salesProcessFlowConfigUpsert(payload); proxy?.$modal?.msgSuccess("å·¥èºè·¯çº¿æ°å¢æå"); routeNameDraft.value = ""; await handleRouteQuery(); }; const editRoute = async (row) => { const oldName = row?.processRouteName ?? ""; const { value } = await ElMessageBox.prompt("请è¾å ¥æ°çå·¥èºè·¯çº¿åç§°", "ä¿®æ¹å·¥èºè·¯çº¿", { inputValue: oldName, confirmButtonText: "确认", cancelButtonText: "åæ¶", }); await salesProcessFlowConfigUpsert({ routeId: row.routeId, processRouteName: value, }); proxy?.$modal?.msgSuccess("å·¥èºè·¯çº¿ä¿®æ¹æå"); await fetchRouteList(); if (selectedRouteId.value === row.routeId) selectedRouteName.value = value; }; const deleteRoute = async (row) => { await ElMessageBox.confirm("确认å é¤è¯¥å·¥èºè·¯çº¿ï¼", "å é¤", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }); await salesProcessFlowConfigDelete(row.routeId); proxy?.$modal?.msgSuccess("å 餿å"); if (selectedRouteId.value === row.routeId) { selectedRouteId.value = null; selectedRouteName.value = ""; processItems.value = []; } // å é¤åè¥å½åé¡µè¢«æ¸ ç©ºï¼åéä¸é¡µ if (routeList.value.length <= 1 && routePage.value.current > 1) { routePage.value.current = routePage.value.current - 1; } await fetchRouteList(); }; const setDefaultRoute = async (row) => { await salesProcessFlowConfigSetDefault(row.routeId); proxy?.$modal?.msgSuccess("é»è®¤å·¥èºè·¯çº¿è®¾ç½®æå"); await fetchRouteList(); }; const ensureSortNo = () => { processItems.value = processItems.value.map((i, idx) => ({ ...i, sortNo: idx + 1 })); }; const createProcessItem = async () => { if (!selectedRouteId.value) { proxy?.$modal?.msgWarning("请å 鿩工èºè·¯çº¿"); return; } if (!processNameDraft.value) { proxy?.$modal?.msgWarning("请å è¾å ¥å·¥åºåç§°"); return; } await salesProcessFlowConfigItemUpsert({ routeId: selectedRouteId.value, processName: processNameDraft.value, sortNo: processItems.value.length + 1, }); proxy?.$modal?.msgSuccess("å·¥åºæ°å¢æå"); processNameDraft.value = ""; await fetchProcessItems(selectedRouteId.value); }; const editProcessItem = async (row) => { const { value } = await ElMessageBox.prompt("请è¾å ¥æ°çå·¥åºåç§°", "ä¿®æ¹å·¥åº", { inputValue: row.processName, confirmButtonText: "确认", cancelButtonText: "åæ¶", }); await salesProcessFlowConfigItemUpsert({ id: row.itemId, routeId: selectedRouteId.value, processName: value, sortNo: row.sortNo, }); proxy?.$modal?.msgSuccess("å·¥åºä¿®æ¹æå"); await fetchProcessItems(selectedRouteId.value); }; const deleteProcessItem = async (row) => { await ElMessageBox.confirm("确认å é¤è¯¥å·¥åºï¼", "å é¤", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }); await salesProcessFlowConfigItemDelete(row.itemId); proxy?.$modal?.msgSuccess("å·¥åºå 餿å"); await fetchProcessItems(selectedRouteId.value); }; </script> <style scoped> .route-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; } .route-toolbar-left { flex-wrap: nowrap; } .route-toolbar-left :deep(.el-input) { width: 190px; } .process-route-dialog :deep(.el-dialog__body) { height: 760px; overflow: hidden; overflow-y: hidden; } .dialog-main { height: 100%; } .left-panel { height: 100%; display: flex; flex-direction: column; min-height: 0; } .left-table-wrap { flex: 1; min-height: 0; overflow: hidden; } .process-diagram { display: flex; align-items: center; gap: 0; flex-wrap: nowrap; overflow-x: auto; padding: 10px 0; } .process-diagram-segment { display: flex; align-items: center; } .process-diagram-node { width: 160px; min-width: 160px; height: 78px; border: 1px solid #ebeef5; border-radius: 10px; background: #fff; display: flex; flex-direction: column; justify-content: center; padding: 10px 12px; margin-right: 10px; box-sizing: border-box; } .process-diagram-index { font-size: 12px; color: #909399; margin-bottom: 4px; } .process-diagram-name { font-size: 14px; font-weight: 600; color: #303133; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .process-diagram-arrow { font-size: 18px; color: #909399; margin-right: 14px; margin-left: -6px; } .process-diagram-empty { width: 100%; text-align: center; padding: 24px 0; color: #909399; border: 1px dashed #ebeef5; border-radius: 8px; } </style> src/views/salesManagement/salesLedger/index.vue
@@ -25,13 +25,20 @@ </div> <div class="table_list"> <div class="actions"> <div></div> <div> <OtherAmountMaintenanceButton /> <ProcessFlowMaintenanceButton /> </div> <ProcessFlowConfigSelectDialog v-model:visible="processFlowSelectDialogVisible" @confirm="handleProcessFlowSelectConfirm" /> <div> <el-button type="primary" @click="openForm('add')"> æ°å¢å°è´¦ </el-button> <el-button type="primary" plain @click="openOtherAmountDialog"> å ¶ä»éé¢ç»´æ¤ <el-button type="primary" @click="handleBulkDelivery"> åè´§ </el-button> <el-button type="primary" plain @click="handleImport">å¯¼å ¥</el-button> <el-button @click="handleOut">导åº</el-button> @@ -49,7 +56,11 @@ <el-table-column align="center" label="åºå·" type="index"/> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" /> <el-table-column label="å度" prop="thickness" min-width="90"> <template #default="scope"> {{ scope.row.thickness ?? "" }} </template> </el-table-column> <el-table-column label="产åç¶æ" width="100px" align="center"> @@ -96,7 +107,7 @@ <el-table-column label="å«ç¨æ»ä»·(å )" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column label="ä¸å«ç¨æ»ä»·(å )" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> <!--æä½--> <el-table-column Width="60px" label="æä½" align="center"> <!-- <el-table-column Width="60px" label="æä½" align="center"> <template #default="scope"> <el-button link @@ -106,7 +117,7 @@ åè´§ </el-button> </template> </el-table-column> </el-table-column> --> </el-table> </template> </el-table-column> @@ -123,9 +134,10 @@ <el-table-column label="ç¾è®¢æ¥æ" prop="executionDate" width="120" show-overflow-tooltip /> <el-table-column label="äº¤ä»æ¥æ" prop="deliveryDate" width="120" show-overflow-tooltip /> <el-table-column label="夿³¨" prop="remarks" width="200" show-overflow-tooltip /> <el-table-column fixed="right" label="æä½" width="130" align="center"> <el-table-column fixed="right" label="æä½" width="200" align="center"> <template #default="scope"> <el-button link type="primary" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit">ç¼è¾</el-button> <el-button link type="primary" @click="openProcessFlowSelect(scope.row)" :disabled="!scope.row.isEdit">å·¥èºè·¯çº¿</el-button> <el-button link type="primary" @click="downLoadFile(scope.row)">éä»¶</el-button> </template> </el-table-column> @@ -229,7 +241,11 @@ <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" /> <el-table-column label="å度" prop="thickness" min-width="90"> <template #default="scope"> {{ scope.row.thickness ?? "" }} </template> </el-table-column> <el-table-column label="æ°é" prop="quantity" /> <el-table-column label="ç¨ç(%)" prop="taxRate" /> <el-table-column label="å«ç¨åä»·(å )" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> @@ -247,6 +263,20 @@ <el-col :span="24"> <el-form-item label="夿³¨ï¼" prop="remarks"> <el-input v-model="form.remarks" placeholder="请è¾å ¥" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="客æ·å¤æ³¨ï¼" prop="customerRemarks"> <el-input v-model="form.customerRemarks" placeholder="请è¾å ¥" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> @@ -374,8 +404,16 @@ </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="åä½ï¼" prop="unit"> <el-input v-model="productForm.unit" placeholder="请è¾å ¥" clearable /> <el-form-item label="å度ï¼" prop="thickness"> <el-input-number v-model="productForm.thickness" :min="0" :step="0.000000000000001" :precision="15" style="width: 100%;" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> @@ -564,6 +602,13 @@ </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="楼å±ç¼å·ï¼" prop="floorCode"> <el-input v-model="productForm.floorCode" placeholder="请è¾å ¥æ¥¼å±ç¼å·" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <!-- å ¶ä»éé¢ï¼å 满ä¸è¡ï¼çåäº 3 åç½æ ¼çæ´è¡ï¼ --> <el-row :gutter="30"> <el-col :span="12"> @@ -684,6 +729,7 @@ </el-button> </template> </el-dialog> <!-- å¯¼å ¥å¼¹çª --> <FormDialog v-model="importUpload.open" @@ -778,7 +824,7 @@ <tr> <th>产ååç§°</th> <th>è§æ ¼åå·</th> <th>åä½</th> <th>å度</th> <th>åä»·</th> <th>é¶å®æ°é</th> <th>é¶å®éé¢</th> @@ -788,7 +834,7 @@ <tr v-for="product in item.products" :key="product.id"> <td>{{ product.productCategory || '' }}</td> <td>{{ product.specificationModel || '' }}</td> <td>{{ product.unit || '' }}</td> <td>{{ product.thickness ?? '' }}</td> <td>{{ product.taxInclusiveUnitPrice || '0' }}</td> <td>{{ product.quantity || '0' }}</td> <td>{{ product.taxInclusiveTotalPrice || '0' }}</td> @@ -916,114 +962,6 @@ </template> </el-dialog> <!-- å ¶ä»éé¢ç»´æ¤ï¼æ°å¢/ç¼è¾/å é¤ï¼ --> <el-dialog v-model="otherAmountDialogVisible" title="å ¶ä»éé¢ç»´æ¤" width="80%" :close-on-click-modal="false" @close="closeOtherAmountDialog" > <el-row :gutter="20"> <el-col :span="14"> <el-table :data="otherAmountRecords" border v-loading="otherAmountLoading" height="55vh" > <el-table-column label="ç¼ç " prop="code" min-width="120" show-overflow-tooltip /> <el-table-column label="项ç®" prop="processName" min-width="180" show-overflow-tooltip /> <el-table-column label="æ°é" prop="quantity" min-width="110" :formatter="formattedNumber" /> <el-table-column label="åä»·(å )" prop="unitPrice" min-width="130" :formatter="formattedNumber" /> <el-table-column label="éé¢(å )" prop="amount" min-width="160" :formatter="formattedNumber" /> <el-table-column fixed="right" label="æä½" width="160" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="handleOtherEdit(scope.row)">ç¼è¾</el-button> <el-button link type="danger" size="small" @click="handleOtherDelete(scope.row)">å é¤</el-button> </template> </el-table-column> </el-table> <pagination v-show="otherAmountTotal > 0" :total="otherAmountTotal" layout="total, sizes, prev, pager, next, jumper" :page="otherAmountPage.current" :limit="otherAmountPage.size" @pagination="otherAmountPaginationChange" /> </el-col> <el-col :span="10"> <div style="padding: 8px 0;"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 10px;"> <div style="font-weight:600;"> {{ otherAmountOperationType === 'add' ? 'æ°å¢å ¶ä»éé¢' : 'ç¼è¾å ¶ä»éé¢' }} </div> <el-button type="primary" plain size="small" @click="handleOtherAdd" :disabled="otherAmountOperationType === 'add'" > æ°å¢ </el-button> </div> <el-form :model="otherAmountForm" label-width="120px" label-position="top" :rules="otherAmountRules" ref="otherAmountFormRef" > <el-form-item label="ç¼ç "> <el-input v-model="otherAmountForm.code" placeholder="请è¾å ¥ç¼ç ï¼å¯éï¼" clearable /> </el-form-item> <el-form-item label="项ç®" prop="processName"> <el-input v-model="otherAmountForm.processName" placeholder="请è¾å ¥é¡¹ç®åç§°" clearable /> </el-form-item> <el-form-item label="æ°é" prop="quantity"> <el-input-number v-model="otherAmountForm.quantity" :min="0" :precision="2" style="width:100%" placeholder="请è¾å ¥æ°é" clearable @change="recalcOtherAmount" /> </el-form-item> <el-form-item label="åä»·(å )" prop="unitPrice"> <el-input-number v-model="otherAmountForm.unitPrice" :min="0" :precision="2" style="width:100%" placeholder="请è¾å ¥åä»·" clearable @change="recalcOtherAmount" /> </el-form-item> <el-form-item label="éé¢(å )"> <el-input v-model="otherAmountForm.amount" disabled /> </el-form-item> <div style="display:flex; justify-content:flex-end; gap: 10px; margin-top: 8px;"> <el-button @click="closeOtherAmountDialog">åæ¶</el-button> <el-button type="primary" @click="submitOtherAmountForm">ä¿å</el-button> </div> </el-form> </div> </el-col> </el-row> </el-dialog> </div> </template> @@ -1038,6 +976,9 @@ import { userListNoPage } from "@/api/system/user.js"; import FileListDialog from '@/components/Dialog/FileListDialog.vue'; import FormDialog from '@/components/Dialog/FormDialog.vue'; import OtherAmountMaintenanceButton from "./components/OtherAmountMaintenanceButton.vue"; import ProcessFlowMaintenanceButton from "./components/ProcessFlowMaintenanceButton.vue"; import ProcessFlowConfigSelectDialog from "./components/ProcessFlowConfigSelectDialog.vue"; import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; import { ledgerListPage, @@ -1051,14 +992,12 @@ delLedgerFile, getProductInventory, salesLedgerProductProcessList, salesLedgerProductProcessAdd, salesLedgerProductProcessUpdate, salesLedgerProductProcessDelete, } from "@/api/salesManagement/salesLedger.js"; import { modelList, productTreeList } from "@/api/basicData/product.js"; import useFormData from "@/hooks/useFormData.js"; import dayjs from "dayjs"; import { getCurrentDate } from "@/utils/index.js"; import { salesLedgerProductSetProcessFlowConfig } from "@/api/salesManagement/salesProcessFlowConfig.js"; const userStore = useUserStore(); const { proxy } = getCurrentInstance(); @@ -1077,6 +1016,10 @@ }); const total = ref(0); const fileList = ref([]); // å·¥èºè·¯çº¿é ç½®éæ©å¼¹çªï¼ç»å®å°å°è´¦äº§åï¼ const processFlowSelectDialogVisible = ref(false); const processFlowSelectLedgerRow = ref(null); // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® const operationType = ref(""); @@ -1119,7 +1062,7 @@ productForm: { productCategory: "", specificationModel: "", unit: "", thickness:null, quantity: "", taxInclusiveUnitPrice: "", taxRate: "", @@ -1138,6 +1081,8 @@ processRequirement: "", // å å·¥è¦æ± remark: "", // 夿³¨ salesProductProcessList: [], // å ¶ä»éé¢ï¼[{id, processName, quantity}] processFlowConfigId: null, // å·¥èºæµç¨é ç½®ç»å® floorCode: "", // 楼å±ç¼å· }, productRules: { productCategory: [{ required: true, message: "è¯·éæ©", trigger: "change" }], @@ -1145,7 +1090,7 @@ specificationModel: [ { required: true, message: "è¯·éæ©", trigger: "change" }, ], unit: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], thickness: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], quantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], taxInclusiveUnitPrice: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, @@ -1191,7 +1136,7 @@ // åè´§ç¸å ³ const deliveryFormVisible = ref(false); const currentDeliveryRow = ref(null); const currentDeliveryRows = ref([]); const deliveryFormData = reactive({ deliveryForm: { type: "货车", // 货车, å¿«é @@ -1203,169 +1148,6 @@ }, }); const { deliveryForm, deliveryRules } = toRefs(deliveryFormData); // å ¶ä»éé¢ç»´æ¤ï¼å·¥åº/æµç¨éé¢ç»´æ¤ï¼ const otherAmountDialogVisible = ref(false); const otherAmountLoading = ref(false); const otherAmountRecords = ref([]); const otherAmountTotal = ref(0); const otherAmountPage = reactive({ current: 1, size: 10, }); const otherAmountOperationType = ref("add"); // add/edit const otherAmountFormRef = ref(null); const otherAmountForm = reactive({ id: null, code: "", // åç«¯åæ®µåï¼codeï¼å端æ¥å£å表è¿å remarkï¼æ¤å¤è¿è¡æ å° processName: "", quantity: 0, unitPrice: 0, amount: "0.00", }); const otherAmountRules = reactive({ processName: [{ required: true, message: "请è¾å ¥é¡¹ç®åç§°", trigger: "change" }], quantity: [{ required: true, message: "请è¾å ¥æ°é", trigger: "blur" }], unitPrice: [{ required: true, message: "请è¾å ¥åä»·", trigger: "blur" }], }); const recalcOtherAmount = () => { const quantity = Number(otherAmountForm.quantity ?? 0) || 0; const unitPrice = Number(otherAmountForm.unitPrice ?? 0) || 0; otherAmountForm.amount = (quantity * unitPrice).toFixed(2); }; const resetOtherAmountForm = (type = "add") => { otherAmountOperationType.value = type; otherAmountForm.id = null; otherAmountForm.code = ""; otherAmountForm.processName = ""; otherAmountForm.quantity = 0; otherAmountForm.unitPrice = 0; otherAmountForm.amount = "0.00"; }; const openOtherAmountDialog = () => { otherAmountDialogVisible.value = true; resetOtherAmountForm("add"); // æå¼å¼¹æ¡æ¶å·æ°æ°æ®ï¼é¿å é¿æ¶é´åçå¯¼è´æ°æ®è¿æ otherAmountPage.current = otherAmountPage.current || 1; fetchOtherAmountList(); }; const closeOtherAmountDialog = () => { otherAmountDialogVisible.value = false; resetOtherAmountForm("add"); }; const fetchOtherAmountList = async () => { otherAmountLoading.value = true; try { const params = { current: otherAmountPage.current, size: otherAmountPage.size, }; const res = await salesLedgerProductProcessList(params); // å ¼å®¹ä¸åæ¥å£ååºç»æï¼å¯è½æ¯ res.records / res.total æ res.data.records / res.data.total const records = res?.records ?? res?.data?.records ?? []; const total = res?.total ?? res?.data?.total ?? 0; otherAmountRecords.value = records.map((item) => { const quantity = Number(item.quantity ?? 0) || 0; const unitPrice = Number(item.unitPrice ?? 0) || 0; const amount = Number(item.amount ?? quantity * unitPrice) || 0; return { id: item.id, code: item.code ?? item.remark ?? "", processName: item.processName ?? "", quantity, unitPrice, amount: amount.toFixed(2), }; }); otherAmountTotal.value = total; } finally { otherAmountLoading.value = false; } }; const otherAmountPaginationChange = (obj) => { otherAmountPage.current = obj.page; otherAmountPage.size = obj.limit; fetchOtherAmountList(); }; const handleOtherAdd = () => { resetOtherAmountForm("add"); }; const handleOtherEdit = (row) => { if (!row) return; otherAmountOperationType.value = "edit"; otherAmountForm.id = row.id ?? null; otherAmountForm.code = row.code ?? ""; otherAmountForm.processName = row.processName ?? ""; otherAmountForm.quantity = Number(row.quantity ?? 0) || 0; otherAmountForm.unitPrice = Number(row.unitPrice ?? 0) || 0; recalcOtherAmount(); }; const submitOtherAmountForm = () => { otherAmountFormRef.value?.validate((valid) => { if (!valid) return; const payload = { processName: otherAmountForm.processName, quantity: Number(otherAmountForm.quantity) || 0, unitPrice: Number(otherAmountForm.unitPrice) || 0, amount: Number(otherAmountForm.amount) || 0, // å表è¿ååæ®µæ¯ remarkï¼è¿éæâcode=remarkâåæ å° remark: otherAmountForm.code, // å ¼å®¹å端å¯è½ç´æ¥ä½¿ç¨ code åæ®µ code: otherAmountForm.code, }; if (otherAmountOperationType.value === "edit") { payload.id = otherAmountForm.id; salesLedgerProductProcessUpdate(payload).then(() => { proxy.$modal.msgSuccess("ä¿åæå"); fetchOtherAmountList(); resetOtherAmountForm("add"); }); } else { salesLedgerProductProcessAdd(payload).then(() => { proxy.$modal.msgSuccess("ä¿åæå"); fetchOtherAmountList(); resetOtherAmountForm("add"); }); } }); }; const handleOtherDelete = (row) => { if (!row?.id) return; ElMessageBox.confirm("确认å é¤è¯¥è®°å½ï¼", "å é¤", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { return salesLedgerProductProcessDelete(row.id).then(() => { proxy.$modal.msgSuccess("å 餿å"); fetchOtherAmountList(); if (otherAmountOperationType.value === "edit" && otherAmountForm.id === row.id) { resetOtherAmountForm("add"); } }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // 产åå¼¹æ¡ï¼å ¶ä»éé¢å¤é䏿ï¼åºäºâå ¶ä»éé¢ç»´æ¤âæ¥è¯¢æ¥å£ï¼ const otherAmountSelectOptions = ref([]); // [{id, processName}] @@ -1636,6 +1418,59 @@ tableLoading.value = false; }); }; // æå¼âå·¥èºè·¯çº¿é ç½®â鿩弹çªï¼å¿ é¡»æ¾å¼éæ©ï¼ const openProcessFlowSelect = (ledgerRow) => { if (!ledgerRow) return; if (!ledgerRow.isEdit) return; processFlowSelectLedgerRow.value = ledgerRow; processFlowSelectDialogVisible.value = true; }; // å°é ç½®åºç¨å°å°è´¦ä¸çæææªå货产å const handleProcessFlowSelectConfirm = async (configId) => { const ledgerRow = processFlowSelectLedgerRow.value; if (!ledgerRow?.id) return; const finalConfigId = configId ?? null; if (!finalConfigId) return; proxy?.$modal?.loading?.("æ£å¨åºç¨å·¥èºè·¯çº¿é ç½®ï¼è¯·ç¨å..."); try { const res = await productList({ salesLedgerId: ledgerRow.id, type: 1 }); const products = res?.data ?? res?.records ?? res ?? []; if (!Array.isArray(products) || products.length === 0) { proxy?.$modal?.msgWarning?.("该å°è´¦ä¸æ²¡æäº§åæ°æ®"); return; } for (const product of products) { // å·²åè´§/å®¡æ ¸éè¿ä¸å¯ç¼è¾ï¼è·³è¿ if (isProductShipped(product)) continue; await salesLedgerProductSetProcessFlowConfig({ salesLedgerProductId: product.id, processFlowConfigId: finalConfigId, }); } proxy?.$modal?.msgSuccess?.("å·¥èºè·¯çº¿é ç½®åºç¨æå"); // è¥å½åè¡å·²å±å¼ï¼å·æ°å ¶å产åå表 if (expandedRowKeys.value.includes(ledgerRow.id)) { const childRes = await productList({ salesLedgerId: ledgerRow.id, type: 1 }); const children = childRes?.data ?? childRes?.records ?? childRes ?? []; const index = tableData.value.findIndex((item) => item.id === ledgerRow.id); if (index > -1) { tableData.value[index].children = children; } } } catch (e) { proxy?.$modal?.msgError?.("åºç¨å¤±è´¥ï¼è¯·ç¨åéè¯"); } finally { proxy?.$modal?.closeLoading?.(); } }; // è·å产å大类treeæ°æ® const getProductOptions = () => { // è¿å Promiseï¼ä¾¿äºå¨ç¼è¾äº§åæ¶çå¾ å è½½å®æ @@ -1658,10 +1493,8 @@ const index = modelOptions.value.findIndex((item) => item.id === value); if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model; productForm.value.unit = modelOptions.value[index].unit; } else { productForm.value.specificationModel = null; productForm.value.unit = null; } }; const findNodeById = (nodes, productId) => { @@ -1784,11 +1617,14 @@ form.value.entryDate = getCurrentDate(); // ç¾è®¢æ¥æé»è®¤ä¸ºå½å¤© form.value.executionDate = getCurrentDate(); form.value.customerRemarks = ""; } else { currentId.value = row.id; getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => { form.value = { ...res }; form.value.entryPerson = Number(res.entryPerson); // åæ®µåå ¼å®¹ï¼å端å¯è½è¿å customer_remarks form.value.customerRemarks = res?.customerRemarks ?? res?.customer_remarks ?? ""; productData.value = form.value.productData; fileList.value = form.value.salesLedgerFiles; }); @@ -1886,7 +1722,7 @@ // å°è´¦å段 productCategory: p.product || p.productName || "", specificationModel: p.specification || "", unit: p.unit || "", thickness: p.thickness, quantity: quantity, taxRate: taxRate, taxInclusiveUnitPrice: unitPrice.toFixed(2), @@ -1901,6 +1737,7 @@ settlePieceArea: 0, settleTotalArea: 0, processRequirement: "", floorCode: "", remark: "", salesProductProcessList: [], }; @@ -2007,10 +1844,17 @@ productForm.value.processRequirement = row?.processRequirement ?? row?.process_requirement ?? ""; productForm.value.remark = row?.remark ?? row?.remarks ?? ""; productForm.value.floorCode = row?.floorCode ?? row?.floor_code ?? ""; // å·¥èºæµç¨é ç½®ç»å®å段ï¼åç»ç±åç«¯ç¡®è®¤åæ®µåï¼ productForm.value.processFlowConfigId = row?.processFlowConfigId ?? row?.process_flow_config_id ?? null; // å¨é¿åæ¾ï¼å¦å端è¿åï¼æç»ä»ä»¥å ¬å¼è®¡ç®ä¸ºåï¼ productForm.value.perimeter = row?.perimeter ?? row?.heavyBoxPerimeter ?? row?.heavyboxPerimeter ?? 0; // åç«¯ç´æ¥è¿å thickness productForm.value.thickness = row?.thickness; productForm.value.salesProductProcessList = normalizeOtherAmountsFromRow(row); productIndex.value = index; @@ -2055,6 +1899,11 @@ const submitProduct = () => { proxy.$refs["productFormRef"].validate((valid) => { if (valid) { // å度ä¿ç 15 ä½å°æ°ï¼é¿å ç±äºæµ®ç¹è®¡ç®/è¾å ¥å¯¼è´ç²¾åº¦åå·® if (productForm.value.thickness !== null && productForm.value.thickness !== undefined) { productForm.value.thickness = Number(Number(productForm.value.thickness).toFixed(15)); } // é¢ç§¯/æ»è®¡åæ®µå¨æäº¤åå åºè®¡ç®ä¸æ¬¡ recalcAreaTotals(); // å ¶ä»éé¢åªæäº¤ {id, processName, quantity}ï¼åç«¯åæ®µï¼salesProductProcessListï¼ @@ -2471,7 +2320,7 @@ <tr> <th>产ååç§°</th> <th>è§æ ¼åå·</th> <th>åä½</th> <th>å度</th> <th>åä»·</th> <th>é¶å®æ°é</th> <th>é¶å®éé¢</th> @@ -2483,7 +2332,7 @@ <tr> <td>${product.productCategory || ''}</td> <td>${product.specificationModel || ''}</td> <td>${product.unit || ''}</td> <td>${product.thickness ?? ''}</td> <td>${product.taxInclusiveUnitPrice || '0'}</td> <td>${product.quantity || '0'}</td> <td>${product.taxInclusiveTotalPrice || '0'}</td> @@ -2926,6 +2775,72 @@ return statusStr === 'å¾ åè´§' || statusStr === 'å®¡æ ¸æç»'; }; const handleBulkDelivery = async () => { if (selectedRows.value.length === 0) { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } const customerNames = selectedRows.value.map((r) => String(r.customerName || "").trim()); const uniqueCustomers = Array.from(new Set(customerNames)); // 客æ·åç§°ä¸ä¸è´ä¸å 许åè´§ if (uniqueCustomers.length > 1) { proxy.$modal.msgWarning("客æ·åç§°ä¸ä¸è´ï¼ä¸å 许åè´§"); return; } // 夿¡ä¸å®¢æ·ä¸è´ï¼äºæ¬¡ç¡®è®¤ if (selectedRows.value.length > 1) { try { await ElMessageBox.confirm("æ¯å¦ç¡®è®¤åå¹¶åè´§ï¼", "åå¹¶åè´§", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }); } catch (e) { proxy.$modal.msg("已忶"); return; } } proxy.$modal.loading("æ£å¨è·åäº§åæ°æ®ï¼è¯·ç¨å..."); try { const targets = []; for (const ledger of selectedRows.value) { let products = []; try { const res = await productList({ salesLedgerId: ledger.id, type: 1 }); products = res?.data || []; } catch { products = []; } for (const product of products) { if (!canShip(product)) continue; targets.push({ ...product, salesLedgerId: product.salesLedgerId || ledger.id, }); } } if (targets.length === 0) { proxy.$modal.msgWarning("没æå¯åè´§çæ°æ®"); return; } currentDeliveryRows.value = targets; deliveryForm.value = { type: "货车" }; // é置审æ¹äººèç¹ï¼é»è®¤ä¸ä¸ªç©ºèç¹ï¼ approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; deliveryFormVisible.value = true; } finally { proxy.$modal.closeLoading(); } }; /** * ä¸è½½æä»¶ * @@ -2949,7 +2864,7 @@ return; } currentDeliveryRow.value = row; currentDeliveryRows.value = [row]; deliveryForm.value = { type: "货车", }; @@ -2972,13 +2887,28 @@ const approveUserIds = approverNodes.value.map(node => node.userId).join(","); // ä¿åå½åå±å¼çè¡IDï¼ä»¥ä¾¿åè´§åéæ°å è½½åè¡¨æ ¼æ°æ® const currentExpandedKeys = [...expandedRowKeys.value]; const salesLedgerId = currentDeliveryRow.value.salesLedgerId; addShippingInfo({ salesLedgerId: salesLedgerId, salesLedgerProductId: currentDeliveryRow.value.id, type: deliveryForm.value.type, approveUserIds, }) const targets = currentDeliveryRows.value || []; if (targets.length === 0) { proxy.$modal.msgWarning("æªéæ©å¯åè´§çæ°æ®"); return; } // 便¬¡åè´§ï¼é¿å å¹¶åä¸åºåæ£å/ç¶ææ´æ°äºç¸å½±åï¼ const run = async () => { for (const item of targets) { const salesLedgerId = item.salesLedgerId; if (!salesLedgerId) continue; await addShippingInfo({ salesLedgerId, salesLedgerProductId: item.id, type: deliveryForm.value.type, approveUserIds, }); } }; run() .then(() => { proxy.$modal.msgSuccess("åè´§æå"); closeDeliveryDia(); @@ -2986,8 +2916,7 @@ getList().then(() => { // 妿ä¹åæå±å¼çè¡ï¼éæ°å è½½è¿äºè¡çåè¡¨æ ¼æ°æ® if (currentExpandedKeys.length > 0) { // ä½¿ç¨ Promise.all å¹¶è¡å è½½ææå±å¼è¡çåè¡¨æ ¼æ°æ® const loadPromises = currentExpandedKeys.map(ledgerId => { const loadPromises = currentExpandedKeys.map((ledgerId) => { return productList({ salesLedgerId: ledgerId, type: 1 }).then((res) => { const index = tableData.value.findIndex((item) => item.id === ledgerId); if (index > -1) { @@ -2996,12 +2925,14 @@ }); }); Promise.all(loadPromises).then(() => { // æ¢å¤å±å¼ç¶æ expandedRowKeys.value = currentExpandedKeys; }); } }); }) .catch(() => { proxy.$modal.msgError("å货失败ï¼è¯·ç¨åéè¯"); }); } }); }; @@ -3010,7 +2941,7 @@ const closeDeliveryDia = () => { proxy.resetForm("deliveryFormRef"); deliveryFormVisible.value = false; currentDeliveryRow.value = null; currentDeliveryRows.value = []; }; const currentFactoryName = ref(""); const getCurrentFactoryName = async () => {