| src/api/productionManagement/productionProductInput.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/product/ProductSelectDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/procurementManagement/procurementLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/New.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/productionManagement/productionProductInput.js
@@ -9,3 +9,12 @@ params: query, }); } // 退料 export function returnMaterial(data) { return request({ url: "/productionProductInput/returnMaterial", method: "post", data: data, }); } src/views/basicData/product/ProductSelectDialog.vue
@@ -1,14 +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> <el-form-item label="图纸编号"> <el-input v-model="query.model" placeholder="输入图纸编号" clearable @keyup.enter="onSearch" /> </el-form-item> <el-form-item label="产品大类"> <el-input v-model="query.productName" placeholder="输入产品大类" clearable @keyup.enter="onSearch" /> </el-form-item> <el-form-item> <el-button type="primary" @click="onSearch">搜索</el-button> <el-button @click="onReset">重置</el-button> @@ -20,8 +18,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="unit" label="单位" min-width="160" /> </el-table> src/views/procurementManagement/procurementLedger/index.vue
@@ -539,33 +539,25 @@ ref="productFormRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="产品大类:" prop="productId"> <el-tree-select v-model="productForm.productId" placeholder="请选择" clearable check-strictly @change="getModels" :data="productOptions" :render-after-expand="false" filterable style="width: 100%" /> <el-form-item label="产品选择:" prop="productSelect"> <div style="display: flex; gap: 10px;"> <el-input v-model="productForm.productCategory" placeholder="请选择产品" disabled style="flex: 1;" /> <el-button type="primary" @click="openProductSelectDialog">选择</el-button> </div> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="图纸编号:" prop="productModelId"> <el-select v-model="productForm.productModelId" placeholder="请选择" clearable @change="getProductModel"> <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> </el-select> prop="specificationModel"> <el-input v-model="productForm.specificationModel" placeholder="请输入图纸编号" disabled /> </el-form-item> </el-col> </el-row> @@ -694,11 +686,19 @@ v-model="fileListDialogVisible" title="附件列表" /> <!-- 产品选择对话框 --> <ProductSelectDialog v-model="productSelectVisible" :single="true" @confirm="handleProductSelect" /> </div> </template> <script setup> import { getToken } from "@/utils/auth"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import pagination from "@/components/PIMTable/Pagination.vue"; import { ref, @@ -947,6 +947,7 @@ const productOperationType = ref(""); const productOperationIndex = ref(""); const currentId = ref(""); const productSelectVisible = ref(false); const productFormData = reactive({ productForm: { productId: "", @@ -1667,6 +1668,22 @@ proxy.resetForm("productFormRef"); productFormVisible.value = false; }; // 打开产品选择对话框 const openProductSelectDialog = () => { productSelectVisible.value = true; }; // 处理产品选择 const handleProductSelect = (selectedProducts) => { if (selectedProducts && selectedProducts.length > 0) { const product = selectedProducts[0]; productForm.value.productCategory = product.productName; productForm.value.specificationModel = product.model; productForm.value.unit = product.unit; } }; // 导出 const handleOut = () => { ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { src/views/productionManagement/productionOrder/New.vue
@@ -402,6 +402,28 @@ formState.value.productModelId = product.id; formState.value.unit = product.unit; formState.value.routeId = product.routeId; // 保存产品原有的图纸文件 if (product.salesLedgerFiles && product.salesLedgerFiles.length > 0) { formState.value.salesLedgerFiles = product.salesLedgerFiles.map(file => ({ id: file.id, name: file.name, url: file.url, type: file.type, isExisting: true // 标记为已存在的文件 })); // 同步到fileList用于反显 fileList.value = formState.value.salesLedgerFiles.map(file => ({ id: file.id, name: file.name, url: file.url, isExisting: true })); } else { formState.value.salesLedgerFiles = []; fileList.value = []; } showProductSelectDialog.value = false; // 1. 通过产品自带的routeId获取工序列表 @@ -614,26 +636,66 @@ return true; }; const handleDrawingUploadSuccess = (response, file, fileList) => { const handleDrawingUploadSuccess = (response, file, uploadFileList) => { console.log('上传成功响应', response); console.log('response.data', response.data); if (response.code === 200) { formState.value.tempFileIds = [response.data?.tempId]; formState.value.salesLedgerFiles = [{ // 将新上传的文件添加到salesLedgerFiles中 const newFile = { tempId: response.data?.tempId, originalName: response.data?.originalName || file.name, name: response.data?.originalName || file.name, tempPath: response.data?.tempPath, type: response.data?.type || 14 }]; type: response.data?.type || 14, isNew: true // 标记为新上传的文件 }; // 添加新文件到salesLedgerFiles if (!formState.value.salesLedgerFiles) { formState.value.salesLedgerFiles = []; } formState.value.salesLedgerFiles.push(newFile); // 更新tempFileIds formState.value.tempFileIds = formState.value.salesLedgerFiles .filter(f => f.tempId) .map(f => f.tempId); proxy.$modal.msgSuccess("上传成功"); } else { proxy.$modal.msgError(response.msg || "上传失败"); } }; const handleDrawingRemove = (file) => { formState.value.tempFileIds = []; formState.value.salesLedgerFiles = []; const handleDrawingRemove = (file, uploadFileList) => { // 从salesLedgerFiles中移除对应的文件 if (formState.value.salesLedgerFiles) { const index = formState.value.salesLedgerFiles.findIndex(item => { // 根据id或tempId匹配 if (file.id && item.id === file.id) return true; if (file.response?.data?.tempId && item.tempId === file.response.data.tempId) return true; if (file.name && item.name === file.name) return true; return false; }); if (index > -1) { const removedFile = formState.value.salesLedgerFiles[index]; // 标记为已删除(用于后端处理) if (removedFile.id) { removedFile.isDeleted = true; } else { // 新上传的文件直接移除 formState.value.salesLedgerFiles.splice(index, 1); } } } // 更新tempFileIds formState.value.tempFileIds = formState.value.salesLedgerFiles .filter(f => f.tempId && !f.isDeleted) .map(f => f.tempId); // 同步更新fileList fileList.value = uploadFileList || []; }; const handleSubmit = () => { @@ -644,10 +706,6 @@ proxy.$modal.msgError("请选择产品"); return; } if (!formState.value.productModelId) { proxy.$modal.msgError("请选择规格"); return; } // 处理提交数据 - 将userPower数组转换为逗号分隔的字符串 const processedProcessRouteItems = processRouteItems.value.map(item => ({ @@ -655,12 +713,45 @@ userPower: Array.isArray(item.userPower) ? item.userPower.join(',') : item.userPower })); // 处理文件数据 - 包含已存在的、新上传的和已删除的 const processedFiles = (formState.value.salesLedgerFiles || []).map(file => { if (file.isNew) { // 新上传的文件 return { tempId: file.tempId, name: file.name, tempPath: file.tempPath, type: file.type, isNew: true }; } else if (file.isDeleted) { // 已删除的原有文件 return { id: file.id, name: file.name, url: file.url, type: file.type, isDeleted: true }; } else { // 保留的原有文件 return { id: file.id, name: file.name, url: file.url, type: file.type, isExisting: true }; } }); // 组装提交数据 const submitData = { ...formState.value, processRouteItems: processedProcessRouteItems, productStructureRecords: productStructureRecords.value, files: formState.value.salesLedgerFiles, salesLedgerFiles: processedFiles, files: processedFiles, }; addProductOrder(submitData).then(res => { src/views/productionManagement/productionOrder/index.vue
@@ -39,6 +39,19 @@ :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" /> </template> <template #productWorkOrders="{ row }"> <div class="work-order-circles"> <div v-for="(workOrder, index) in row.productWorkOrders" :key="index" class="work-order-circle" :class="getWorkOrderColorClass(workOrder.color)" :title="workOrder.processName || workOrder.workOrderNo" > {{ workOrder.processName ? workOrder.processName.substring(0, 2) : index + 1 }} </div> </div> </template> </PIMTable> </div> <el-dialog v-model="bindRouteDialogVisible" @@ -72,19 +85,46 @@ <!-- 查看投入弹框 --> <el-dialog v-model="inputDialogVisible" title="投入" width="1000px"> <PIMTable rowKey="id" :column="inputTableColumn" :tableData="inputTableData" :page="inputPage" :tableLoading="inputTableLoading" @pagination="handleInputPagination" title="投入与退料" width="1200px" :close-on-click-modal="false"> <el-table :data="inputTableData" border size="small" @selection-change="handleInputSelectionChange" :header-cell-style="{ background: '#f5f7fa' }" show-summary :summary-method="summarizeInputTable" > <el-table-column type="selection" width="50" align="center" /> <el-table-column label="投入产品名称" prop="productName" min-width="120" /> <el-table-column label="图纸编号" prop="model" min-width="100" /> <el-table-column label="投入数量" prop="quantity" min-width="100" align="center" /> <el-table-column label="已退料数量" prop="returnQuantity" min-width="100" align="center" /> <el-table-column label="单位" prop="unit" min-width="80" align="center" /> <el-table-column label="本次退料数量" min-width="180" align="center" prop="currentReturnQuantity"> <template #default="{ row }"> <el-input-number v-model="row.currentReturnQuantity" :min="0" :max="row.quantity - (row.returnQuantity || 0)" :precision="0" size="small" style="width: 160px" @change="(val) => handleInputReturnQuantityChange(val, row)" /> </template> </el-table-column> </el-table> <div class="picking-footer-info"> <span>已选 {{ inputSelectedRows.length }} 条</span> <span>{{ inputTableData.length }} 条记录</span> </div> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="inputDialogVisible = false">关闭</el-button> <el-button type="warning" @click="handleReturnSubmit">确认退料</el-button> <el-button @click="inputDialogVisible = false">关闭</el-button> </div> </template> </el-dialog> @@ -138,13 +178,47 @@ </template> </el-dialog> <!-- 查看图纸弹窗 --> <el-dialog v-model="drawingDialogVisible" title="查看图纸" width="800px" :close-on-click-modal="false" > <div class="drawing-container"> <div v-if="currentDrawingFiles.length === 0" class="empty-drawing"> <el-empty description="暂无图纸" /> </div> <div v-else class="drawing-list"> <div v-for="(file, index) in currentDrawingFiles" :key="index" class="drawing-item"> <el-image v-if="isImage(file.name)" :src="getFileUrl(file)" :preview-src-list="imagePreviewList" fit="cover" style="width: 150px; height: 150px; border-radius: 4px; cursor: pointer;" /> <div v-else class="file-item" @click="downloadFile(file)"> <el-icon :size="50"><Document /></el-icon> <span class="file-name">{{ file.name }}</span> </div> </div> </div> </div> <template #footer> <div class="dialog-footer"> <el-button @click="drawingDialogVisible = false">关闭</el-button> </div> </template> </el-dialog> </div> </template> <script setup> import { onMounted, ref, computed } from "vue"; import { ElMessageBox } from "element-plus"; import { Setting } from '@element-plus/icons-vue'; import { Setting, Document } from '@element-plus/icons-vue'; import dayjs from "dayjs"; import { useRouter } from "vue-router"; import { @@ -154,7 +228,7 @@ } from "@/api/productionManagement/productionOrder.js"; import { listPage as getProcessRouteList } from "@/api/productionManagement/processRoute.js"; import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js"; import { productionProductInputListPage } from "@/api/productionManagement/productionProductInput.js"; import { productionProductInputListPage, returnMaterial } from "@/api/productionManagement/productionProductInput.js"; import { listPage as listProductStructureRecord, pick as pickMaterial } from "@/api/productionManagement/productStructureRecord.js"; import {fileDel} from "@/api/financialManagement/revenueManagement.js"; @@ -165,6 +239,66 @@ const router = useRouter(); const isShowNewModal = ref(false); // 查看图纸相关 const drawingDialogVisible = ref(false); const currentDrawingFiles = ref([]); // 判断是否为图片 const isImage = (fileName) => { if (!fileName) return false; const ext = fileName.toLowerCase().split('.').pop(); return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext); }; // 获取文件URL const getFileUrl = (file) => { if (file.url) { if (file.url.startsWith('http')) { return file.url; } return import.meta.env.VITE_APP_BASE_API + file.url; } return ''; }; // 图片预览列表 const imagePreviewList = computed(() => { return currentDrawingFiles.value .filter(file => isImage(file.name)) .map(file => getFileUrl(file)); }); // 下载文件 const downloadFile = (file) => { const url = getFileUrl(file); if (url) { const link = document.createElement('a'); link.href = url; link.download = file.name; document.body.appendChild(link); link.click(); document.body.removeChild(link); } }; // 显示查看图纸弹窗 const showDrawingDialog = (row) => { currentDrawingFiles.value = row.salesLedgerFiles || []; drawingDialogVisible.value = true; }; // 获取工单圆圈颜色类名 // color: 1-灰色 2-黄色 3-绿色 4-红色 const getWorkOrderColorClass = (color) => { const colorMap = { 1: 'gray', 2: 'yellow', 3: 'green', 4: 'red' }; return colorMap[color] || 'gray'; }; const tableColumn = ref([ { @@ -201,6 +335,13 @@ }, { dataType: "slot", label: "工序", prop: "productWorkOrders", slot: "productWorkOrders", width: 200, }, { dataType: "slot", label: "完成进度", prop: "completionStatus", slot: "completionStatus", @@ -229,7 +370,7 @@ label: "操作", align: "center", fixed: "right", width: 360, width: 420, operation: [ { name: "开始", @@ -277,10 +418,18 @@ }, }, { name: "查看投入", name: "投入/退料", type: "text", clickFun: row => { showInputDialog(row); }, }, { name: "查看图纸", type: "text", showHide: row => row.salesLedgerFiles && row.salesLedgerFiles.length > 0, clickFun: row => { showDrawingDialog(row); }, }, ], @@ -300,29 +449,12 @@ const inputTableData = ref([]); const inputTableLoading = ref(false); const inputCurrentRow = ref(null); const inputSelectedRows = ref([]); const inputPage = reactive({ current: 1, size: 100, total: 0, }); const inputTableColumn = ref([ { label: '投入产品名称', prop: 'productName', }, { label: '图纸编号', prop: 'model' }, { label: '投入数量', prop: 'quantity', }, { label: '单位', prop: 'unit', }, ]); // 领料相关 const pickingDialogVisible = ref(false); @@ -331,6 +463,8 @@ const pickingForm = reactive({ orderId: null, }); const data = reactive({ searchForm: { @@ -577,11 +711,12 @@ const handleConfirmRoute = () => {}; // 显示查看投入弹框 const showInputDialog = (row) => { const showInputDialog = async (row) => { inputCurrentRow.value = row; inputDialogVisible.value = true; inputPage.current = 1; inputPage.total = 0; inputSelectedRows.value = []; fetchInputData(); }; @@ -599,7 +734,10 @@ productionProductInputListPage(params) .then(res => { inputTableLoading.value = false; inputTableData.value = res.data.records; inputTableData.value = res.data.records.map(item => ({ ...item, currentReturnQuantity: 0, })); inputPage.total = res.data.total; }) .catch(err => { @@ -687,6 +825,58 @@ ]); }; // 投入表格选择变化 const handleInputSelectionChange = (selection) => { inputSelectedRows.value = selection; }; // 投入退料数量变化处理 const handleInputReturnQuantityChange = (val, row) => { const maxReturn = row.quantity - (row.returnQuantity || 0); if (val > maxReturn) { proxy.$modal.msgWarning("本次退料数量不能超过剩余可退数量"); row.currentReturnQuantity = maxReturn; } }; // 确认退料 const handleReturnSubmit = async () => { if (inputSelectedRows.value.length === 0) { proxy.$modal.msgWarning("请选择要退料的物料"); return; } // 校验退料数量 const invalidRows = inputSelectedRows.value.filter(row => !row.currentReturnQuantity || row.currentReturnQuantity <= 0); if (invalidRows.length > 0) { proxy.$modal.msgWarning("请填写本次退料数量"); return; } const returnData = inputSelectedRows.value.map(row => ({ ...row, returnQuantity: row.currentReturnQuantity, })); try { await returnMaterial(returnData); proxy.$modal.msgSuccess("退料成功"); inputDialogVisible.value = false; getList(); } catch (e) { console.error("退料失败:", e); proxy.$modal.msgError("退料失败"); } }; // 投入表格合计方法 const summarizeInputTable = (param) => { return proxy.summarizeTable(param, [ "quantity", "returnQuantity", "currentReturnQuantity", ]); }; onMounted(() => { getList(); }); @@ -721,4 +911,123 @@ color: #606266; } .drawing-container { min-height: 200px; } .empty-drawing { display: flex; justify-content: center; align-items: center; min-height: 200px; } .drawing-list { display: flex; flex-wrap: wrap; gap: 16px; padding: 10px; } .drawing-item { border: 1px solid #dcdfe6; border-radius: 4px; overflow: hidden; transition: all 0.3s; &:hover { box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } } .file-item { width: 150px; height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #f5f7fa; cursor: pointer; padding: 10px; .file-name { font-size: 12px; color: #606266; margin-top: 10px; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 130px; } &:hover { background: #e4e7ed; } } // 工单圆圈样式 .work-order-circles { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } .work-order-circle { width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 500; color: #fff; cursor: pointer; transition: all 0.2s ease; // 灰色 - 待开始 &.gray { background-color: #909399; &:hover { background-color: #a6a9ad; } } // 黄色 - 进行中 &.yellow { background-color: #e6a23c; &:hover { background-color: #ebb563; } } // 绿色 - 已完成 &.green { background-color: #67c23a; &:hover { background-color: #85ce61; } } // 红色 - 异常/暂停 &.red { background-color: #f56c6c; &:hover { background-color: #f78989; } } &:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); } } </style> src/views/salesManagement/salesLedger/index.vue
@@ -323,21 +323,23 @@ <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef"> <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" filterable style="width: 100%" /> <el-form-item label="产品选择:" prop="productSelect"> <div style="display: flex; gap: 10px;"> <el-input v-model="productForm.productCategory" placeholder="请选择产品" disabled style="flex: 1;" /> <el-button type="primary" @click="openProductSelectDialog">选择</el-button> </div> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="图纸编号:" prop="productModelId"> <el-select v-model="productForm.productModelId" placeholder="请选择" clearable @change="getProductModel" filterable> <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> </el-select> <el-form-item label="图纸编号:" prop="specificationModel"> <el-input v-model="productForm.specificationModel" placeholder="请输入图纸编号" disabled /> </el-form-item> </el-col> </el-row> @@ -615,6 +617,13 @@ </div> </template> </el-dialog> <!-- 产品选择对话框 --> <ProductSelectDialog v-model="productSelectVisible" :single="true" @confirm="handleProductSelect" /> </div> </template> @@ -622,6 +631,7 @@ import { getToken } from "@/utils/auth"; import pagination from "@/components/PIMTable/Pagination.vue"; import {onMounted, ref, getCurrentInstance} from "vue"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js"; import { ElMessageBox, ElMessage } from "element-plus"; import { UploadFilled, Download } from "@element-plus/icons-vue"; @@ -701,6 +711,7 @@ const productFormVisible = ref(false); const productOperationType = ref(""); const currentId = ref(""); const productSelectVisible = ref(false); const productFormData = reactive({ productForm: { productCategory: "", @@ -1317,6 +1328,22 @@ proxy.resetForm("productFormRef"); productFormVisible.value = false; }; // 打开产品选择对话框 const openProductSelectDialog = () => { productSelectVisible.value = true; }; // 处理产品选择 const handleProductSelect = (selectedProducts) => { if (selectedProducts && selectedProducts.length > 0) { const product = selectedProducts[0]; productForm.value.productCategory = product.productName; productForm.value.specificationModel = product.model; productForm.value.unit = product.unit; } }; // 导入 const handleImport = () => { importUpload.title = "导入销售台账";