| | |
| | | :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" |
| | |
| | | |
| | | <!-- 查看投入弹框 --> |
| | | <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> |
| | |
| | | </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 { |
| | |
| | | } 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"; |
| | |
| | | |
| | | 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([ |
| | | { |
| | |
| | | }, |
| | | { |
| | | dataType: "slot", |
| | | label: "工序", |
| | | prop: "productWorkOrders", |
| | | slot: "productWorkOrders", |
| | | width: 200, |
| | | }, |
| | | { |
| | | dataType: "slot", |
| | | label: "完成进度", |
| | | prop: "completionStatus", |
| | | slot: "completionStatus", |
| | |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 360, |
| | | width: 420, |
| | | operation: [ |
| | | { |
| | | name: "开始", |
| | |
| | | }, |
| | | }, |
| | | { |
| | | name: "查看投入", |
| | | name: "投入/退料", |
| | | type: "text", |
| | | clickFun: row => { |
| | | showInputDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "查看图纸", |
| | | type: "text", |
| | | showHide: row => row.salesLedgerFiles && row.salesLedgerFiles.length > 0, |
| | | clickFun: row => { |
| | | showDrawingDialog(row); |
| | | }, |
| | | }, |
| | | ], |
| | |
| | | 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); |
| | |
| | | const pickingForm = reactive({ |
| | | orderId: null, |
| | | }); |
| | | |
| | | |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | |
| | | const handleConfirmRoute = () => {}; |
| | | |
| | | // 显示查看投入弹框 |
| | | const showInputDialog = (row) => { |
| | | const showInputDialog = async (row) => { |
| | | inputCurrentRow.value = row; |
| | | inputDialogVisible.value = true; |
| | | inputPage.current = 1; |
| | | inputPage.total = 0; |
| | | inputSelectedRows.value = []; |
| | | fetchInputData(); |
| | | }; |
| | | |
| | |
| | | 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 => { |
| | |
| | | ]); |
| | | }; |
| | | |
| | | // 投入表格选择变化 |
| | | 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(); |
| | | }); |
| | |
| | | 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> |