| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 炒机1-4 展示(总量 / 正在生产量 / 空余量) --> |
| | | <!-- <div class="machines-grid">--> |
| | | <!-- <div v-for="machine in machines" :key="machine.id" class="machine-card">--> |
| | | <!-- <div class="machine-title">{{ machine.name }}</div>--> |
| | | <!-- <div class="machine-metrics">--> |
| | | <!-- <div class="machine-control">--> |
| | | <!-- <span>总量(kg):</span>--> |
| | | <!-- <el-input-number v-model="machineData[machine.name].workLoad" :min="0" :step="1" size="small" />--> |
| | | <!-- </div>--> |
| | | <!-- <div><span> 预计投入量(kg):</span><span>{{ machineData[machine.name].currentWorkLoad }}</span></div>--> |
| | | <!-- <div><span>空余工作量(kg):</span><span>{{ machineData[machine.name].vacant }}</span></div>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="save-button-container">--> |
| | | <!-- <div class="loss-rate-container">--> |
| | | <!-- <span class="loss-rate-label">损耗率(%):</span>--> |
| | | <!-- <el-select v-model="rate" placeholder="请选择损耗率" style="width: 120px" size="small">--> |
| | | <!-- <el-option label="6" :value="6" />--> |
| | | <!-- <el-option label="7" :value="7" />--> |
| | | <!-- <el-option label="8" :value="8" />--> |
| | | <!-- <el-option label="9" :value="9" />--> |
| | | <!-- <el-option label="10" :value="10" />--> |
| | | <!-- </el-select>--> |
| | | <!-- </div>--> |
| | | <!-- <el-button type="primary" @click="saveMachineTotals" size="small">保存设置</el-button>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">客户名称:</span> |
| | |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">项目名称:</span> |
| | | <!-- <span class="search_title ml10">项目名称:</span> |
| | | <el-input |
| | | v-model="searchForm.projectName" |
| | | style="width: 240px" |
| | |
| | | @change="handleQuery" |
| | | clearable |
| | | prefix-icon="Search" |
| | | /> |
| | | /> --> |
| | | <span class="search_title ml10">录入日期:</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="请选择" clearable @change="changeDaterange" /> |
| | | placeholder="请选择" clearable @change="changeDaterange" /> |
| | | <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> |
| | | </div> |
| | | <el-button type="primary" @click="openForm('add')">生产派工</el-button> |
| | | <el-button type="success" @click="openAutoDispatch">自动派工</el-button> |
| | | <el-button @click="handleOut">导出</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | <auto-dispatch-dia ref="autoDispatchDia" @close="handleQuery"></auto-dispatch-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {onMounted, ref} from "vue"; |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, computed, watch} from "vue"; |
| | | import FormDia from "@/views/productionManagement/productionDispatching/components/formDia.vue"; |
| | | import AutoDispatchDia from "@/views/productionManagement/productionDispatching/components/autoDispatchDia.vue"; |
| | | import dayjs from "dayjs"; |
| | | import {schedulingListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading, getLossRate, addLossRate, updateLossRate} from "@/api/productionManagement/productionOrder.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | customerName: "", |
| | | projectName: "", |
| | | entryDate: null, // 录入日期 |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | // projectName: "", |
| | | status: "", |
| | | entryDate: [dayjs().format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")], // 录入日期,默认当天 |
| | | entryDateStart: dayjs().format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().format("YYYY-MM-DD"), |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | |
| | | prop: "customerName", |
| | | width: 250, |
| | | }, |
| | | { |
| | | label: "项目名称", |
| | | prop: "projectName", |
| | | width:300 |
| | | }, |
| | | // { |
| | | // label: "项目名称", |
| | | // prop: "projectName", |
| | | // width:300 |
| | | // }, |
| | | { |
| | | label: "产品大类", |
| | | prop: "productCategory", |
| | |
| | | { |
| | | label: "规格型号", |
| | | prop: "specificationModel", |
| | | width: 220, |
| | | width: 120, |
| | | }, |
| | | // { |
| | | // label: "绑定机器", |
| | | // prop: "speculativeTradingName", |
| | | // width: 220, |
| | | // }, |
| | | { |
| | | label: "单位", |
| | | prop: "unit", |
| | |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const autoDispatchDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | // 炒机数据 |
| | | const machineData = reactive({ |
| | | "炒机1": { workLoad: 0, currentWorkLoad: 0, vacant: 0 }, |
| | | "炒机2": { workLoad: 0, currentWorkLoad: 0, vacant: 0 }, |
| | | "炒机3": { workLoad: 0, currentWorkLoad: 0, vacant: 0 }, |
| | | "炒机4": { workLoad: 0, currentWorkLoad: 0, vacant: 0 } |
| | | }) |
| | | |
| | | // 炒机配置数组 |
| | | const machines = [ |
| | | { id: 1, name: '炒机1' }, |
| | | { id: 2, name: '炒机2' }, |
| | | { id: 3, name: '炒机3' }, |
| | | { id: 4, name: '炒机4' } |
| | | ] |
| | | |
| | | // 保存炒机总量设置 |
| | | const saveMachineTotals = () => { |
| | | // 验证损耗率是否已选择 |
| | | if (rate.value === null || rate.value === undefined || isNaN(rate.value)) { |
| | | proxy.$message.warning('请选择损耗率'); |
| | | return; |
| | | } |
| | | |
| | | // 构造保存数据数组,使用machines数组循环构建 |
| | | const saveData = machines.map(machine => { |
| | | const saveItem = { |
| | | name: machine.name, // 炒机名称 |
| | | workLoad: machineData[machine.name].workLoad, // 总量 |
| | | currentWorkLoad: machineData[machine.name].currentWorkLoad, // 预计投入量 |
| | | vacant: machineData[machine.name].vacant // 空余量 |
| | | }; |
| | | |
| | | // 如果是修改操作,需要传递id字段 |
| | | if (hasQueryData.value) { |
| | | const queryData = getMachineQueryData(machine.id); |
| | | if (queryData && queryData.id) { |
| | | saveItem.id = queryData.id; |
| | | } |
| | | } |
| | | |
| | | return saveItem; |
| | | }); |
| | | |
| | | // 构造损耗率数据 |
| | | const rateData = { |
| | | rate: rate.value |
| | | }; |
| | | |
| | | // 如果有ID,说明是修改操作 |
| | | if (rateId.value) { |
| | | rateData.id = rateId.value; |
| | | } |
| | | |
| | | // 根据是否有查询数据决定调用新增接口还是修改接口 |
| | | const saveApi = hasQueryData.value ? updateSpeculatTrading : addSpeculatTrading; |
| | | const successMessage = hasQueryData.value ? '炒机设置修改成功' : '炒机设置新增成功'; |
| | | |
| | | // 根据是否有ID决定调用新增接口还是修改接口 |
| | | const rateApi = rateId.value ? updateLossRate : addLossRate; |
| | | const rateSuccessMessage = rateId.value ? '损耗率修改成功' : '损耗率新增成功'; |
| | | |
| | | // 并行调用两个接口 |
| | | Promise.all([ |
| | | saveApi(saveData), |
| | | rateApi(rateData) |
| | | ]).then(([saveRes, rateRes]) => { |
| | | proxy.$message.success(successMessage); |
| | | proxy.$message.success(rateSuccessMessage); |
| | | |
| | | // 保存成功后,设置hasQueryData为true,下次保存将调用修改接口 |
| | | if (!hasQueryData.value) { |
| | | hasQueryData.value = true; |
| | | } |
| | | |
| | | // 如果返回了ID,保存起来 |
| | | if (rateRes && rateRes.data && rateRes.data.id) { |
| | | rateId.value = rateRes.data.id; |
| | | } |
| | | |
| | | // 保存成功后重新调用查询页面 |
| | | getList(); |
| | | }).catch(err => { |
| | | proxy.$message.error('保存失败'); |
| | | console.error('保存失败:', err); |
| | | }); |
| | | } |
| | | |
| | | // 获取炒机查询数据 |
| | | const machineQueryData = ref([]); |
| | | |
| | | const getMachineQueryData = (machineId) => { |
| | | return machineQueryData.value.find(item => item.id === machineId); |
| | | }; |
| | | |
| | | const getMachineIndex = (item) => { |
| | | // 兼容多种字段命名,返回 1-4 之一,否则返回 0(未知) |
| | | const candidates = [item.machineId, item.machineNo, item.machine, item.deviceNo, item.deviceId] |
| | | for (const v of candidates) { |
| | | if (v === undefined || v === null) continue |
| | | const n = Number(String(v).replace(/[^\d]/g, "")) // 抽取数字 |
| | | if ([1,2,3,4].includes(n)) return n |
| | | } |
| | | return 0 |
| | | } |
| | | |
| | | const computeTodaySummary = () => { |
| | | const todayStr = dayjs().format("YYYY-MM-DD") |
| | | |
| | | // 重置所有炒机数据 |
| | | machines.forEach(machine => { |
| | | machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 } |
| | | }) |
| | | |
| | | tableData.value.forEach(item => { |
| | | // 仅统计当天 |
| | | const isToday = dayjs(item.entryDate).format("YYYY-MM-DD") === todayStr |
| | | if (!isToday) return |
| | | |
| | | // 使用正确的字段名:workLoad(炒机工作量), currentWorkLoad(炒机正在工作量) |
| | | const workLoad = Number(item.workLoad) || 0 |
| | | const currentWorkLoad = Number(item.currentWorkLoad) || 0 |
| | | const machineName = item.speculativeTradingName || '炒机1' |
| | | |
| | | if (machineData[machineName]) { |
| | | machineData[machineName].workLoad += workLoad |
| | | machineData[machineName].currentWorkLoad += currentWorkLoad |
| | | machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 查询列表 |
| | | /** 搜索按钮操作 */ |
| | |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // 是否有查询数据 |
| | | const hasQueryData = ref(false) |
| | | // 损耗率 |
| | | const rate = ref(6) |
| | | // 损耗率ID |
| | | const rateId = ref(null) |
| | | |
| | | // 获取炒机正在工作量数据 |
| | | const getMachineProductionData = () => { |
| | | schedulingList().then((res) => { |
| | | // 处理炒机正在工作量数据 |
| | | if (res.data && Array.isArray(res.data)) { |
| | | // 设置是否有查询数据 |
| | | hasQueryData.value = res.data.length > 0 |
| | | |
| | | // 保存查询数据到machineQueryData |
| | | machineQueryData.value = res.data; |
| | | |
| | | // 重置所有炒机数据 |
| | | machines.forEach(machine => { |
| | | machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 } |
| | | }); |
| | | |
| | | // 遍历数据,根据查询返回的数据结构处理 |
| | | res.data.forEach(item => { |
| | | // 根据name字段确定炒机 |
| | | const machineName = item.name || '炒机1'; |
| | | |
| | | if (machineData[machineName]) { |
| | | // 如果查询数据中有workLoad,则初始化炒机总量 |
| | | if (item.workLoad !== null && item.workLoad !== undefined) { |
| | | machineData[machineName].workLoad = Number(item.workLoad) || 0; |
| | | } |
| | | |
| | | // 如果查询数据中有currentWorkLoad,则设置正在工作量 |
| | | if (item.currentWorkLoad !== null && item.currentWorkLoad !== undefined) { |
| | | machineData[machineName].currentWorkLoad = Number(item.currentWorkLoad) || 0; |
| | | } |
| | | |
| | | // 计算空余工作量 |
| | | machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad; |
| | | } |
| | | }); |
| | | } |
| | | }).catch(err => { |
| | | console.error('获取炒机正在工作量数据失败:', err); |
| | | }); |
| | | }; |
| | | |
| | | const changeDaterange = (value) => { |
| | | if (value) { |
| | | searchForm.value.entryDateStart = value[0]; |
| | |
| | | pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0) |
| | | })); |
| | | page.total = res.data.total; |
| | | computeTodaySummary() |
| | | |
| | | // 同时获取炒机正在工作量数据 |
| | | getMachineProductionData(); |
| | | // 获取损耗率数据 |
| | | getLossRateData(); |
| | | }).catch(() => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // 获取损耗率数据 |
| | | const getLossRateData = () => { |
| | | getLossRate().then((res) => { |
| | | const data = res.data || res; |
| | | if (data && data.rate !== undefined && data.rate !== null) { |
| | | rate.value = Number(data.rate); // 确保转换为数字 |
| | | rateId.value = data.id || null; |
| | | } else { |
| | | rate.value = 6; |
| | | rateId.value = null; |
| | | } |
| | | }).catch(err => { |
| | | console.error('获取损耗率数据失败:', err); |
| | | rate.value = 6; |
| | | rateId.value = null; |
| | | }); |
| | | }; |
| | | // 表格选择数据 |
| | | const handleSelectionChange = (selection) => { |
| | |
| | | }) |
| | | }; |
| | | |
| | | // 打开自动派工弹框 |
| | | const openAutoDispatch = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$message.error("请选择至少一条数据"); |
| | | return; |
| | | } |
| | | |
| | | // 过滤掉待排产数量为0的数据 |
| | | const validRows = selectedRows.value.filter(row => row.pendingQuantity > 0); |
| | | |
| | | if (validRows.length === 0) { |
| | | proxy.$message.warning("选中的数据无需派工"); |
| | | return; |
| | | } |
| | | |
| | | nextTick(() => { |
| | | autoDispatchDia.value?.openDialog('auto', validRows) |
| | | }) |
| | | }; |
| | | |
| | | // 导出 |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { |
| | |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | getLossRateData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | | <style scoped> |
| | | .summary-bar{ |
| | | display: flex; |
| | | gap: 16px; |
| | | margin: 10px 0 16px 0; |
| | | } |
| | | .summary-item{ |
| | | background: #f5f7fa; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | padding: 10px 16px; |
| | | min-width: 160px; |
| | | } |
| | | .summary-label{ |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-bottom: 6px; |
| | | } |
| | | .summary-value{ |
| | | color: #303133; |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | } |
| | | .summary-control{ |
| | | display: flex; |
| | | align-items: center; |
| | | height: 28px; |
| | | } |
| | | .machines-grid{ |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | gap: 16px; |
| | | margin-bottom: 20px; |
| | | padding: 16px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | border: 1px solid #e9ecef; |
| | | } |
| | | .machine-card{ |
| | | border: 1px solid #dee2e6; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | background: #fff; |
| | | box-shadow: 0 2px 4px rgba(0,0,0,0.05); |
| | | transition: all 0.3s ease; |
| | | } |
| | | .machine-card:hover{ |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 8px rgba(0,0,0,0.1); |
| | | } |
| | | .machine-title{ |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | margin-bottom: 12px; |
| | | color: #2c3e50; |
| | | text-align: center; |
| | | padding-bottom: 8px; |
| | | border-bottom: 2px solid #3498db; |
| | | } |
| | | .machine-metrics{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | color: #495057; |
| | | } |
| | | .machine-control{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 8px; |
| | | padding: 8px 0; |
| | | border-bottom: 1px solid #f1f3f4; |
| | | } |
| | | .machine-control span{ |
| | | font-size: 14px; |
| | | white-space: nowrap; |
| | | color: #6c757d; |
| | | font-weight: 500; |
| | | } |
| | | .machine-metrics > div:not(.machine-control) { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 4px 0; |
| | | font-size: 14px; |
| | | } |
| | | .machine-metrics > div:not(.machine-control) span:first-child { |
| | | color: #6c757d; |
| | | } |
| | | .machine-metrics > div:not(.machine-control) span:last-child { |
| | | font-weight: 600; |
| | | color: #2c3e50; |
| | | } |
| | | .save-button-container{ |
| | | grid-column: 1 / -1; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 16px; |
| | | margin-top: 16px; |
| | | padding-top: 16px; |
| | | border-top: 1px solid #e9ecef; |
| | | } |
| | | .loss-rate-container{ |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .loss-rate-label{ |
| | | font-size: 14px; |
| | | color: #6c757d; |
| | | font-weight: 500; |
| | | white-space: nowrap; |
| | | } |
| | | </style> |