| | |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination"> |
| | | <template #completionStatus="{ row }"> |
| | | <el-progress :percentage="toProgressPercentage(row?.completionStatus)" |
| | | :color="progressColor(toProgressPercentage(row?.completionStatus))" |
| | | :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" /> |
| | | </template> |
| | | </PIMTable> |
| | | @pagination="pagination" /> |
| | | </div> |
| | | <el-dialog v-model="editDialogVisible" |
| | | title="编辑时间" |
| | |
| | | <el-form-item label="班组信息" |
| | | prop="teamList"> |
| | | <el-select v-model="reportForm.teamList" |
| | | ref="teamSelectRef" |
| | | multiple |
| | | filterable |
| | | allow-create |
| | |
| | | value-key="userName" |
| | | placeholder="请选择或输入班组成员" |
| | | @change="handleTeamListChange"> |
| | | <el-option v-for="user in reportForm.userIdsList" |
| | | :key="user.userId" |
| | | <el-option v-for="user in teamSelectOptions" |
| | | :key="user.userId || `custom-${user.nickName}`" |
| | | :label="user.nickName" |
| | | :value="{ userId: user.userId, userName: user.nickName }" /> |
| | | </el-select> |
| | |
| | | <!-- </el-col>--> |
| | | </el-row> |
| | | </el-form> |
| | | <el-table :data="personList" border style="margin-top: 12px;"> |
| | | <el-table-column label="生产人" prop="userName" min-width="100" align="center" /> |
| | | <el-table-column label="产量" min-width="120" align="center"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.quantity" :min="0" :controls="false" placeholder="产量" style="width: 100%" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="生产时长(分钟)" min-width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.workHour" :min="0" :controls="false" placeholder="时长(分钟)" style="width: 100%" /> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | |
| | | productWorkOrderPage, |
| | | updateProductWorkOrder, |
| | | addProductMain, |
| | | getProductWorkOrderById, |
| | | downProductWorkOrder, |
| | | addProductionMachineRecord, |
| | | productionMachineRecordListPage, |
| | |
| | | import { getCurrentInstance, reactive, toRefs } from "vue"; |
| | | import FilesDia from "./components/filesDia.vue"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | const isAdminUser = computed(() => |
| | | ["admin", "普通2", "最高1"].some(r => (userStore.roles || []).includes(r)) |
| | | ); |
| | | |
| | | const currentUserId = ref(""); |
| | | const deviceOptions = ref([]); |
| | | const currentUserName = ref(""); |
| | | const teamSelectRef = ref(null); |
| | | |
| | | const ensureCurrentUser = async () => { |
| | | if (currentUserId.value) return; |
| | |
| | | return ids.includes(uid); |
| | | }; |
| | | |
| | | // 判断当前用户是否可以报工(工单报工人 或 工序报工人) |
| | | // 判断当前用户是否可以报工(工单报工人 或 工序报工人,管理员不受限) |
| | | const canCurrentUserReport = row => { |
| | | return isCurrentUserInUserIds(row) || isCurrentUserInProcessUserIds(row); |
| | | return ( |
| | | isAdminUser.value || |
| | | isCurrentUserInUserIds(row) || |
| | | isCurrentUserInProcessUserIds(row) |
| | | ); |
| | | }; |
| | | |
| | | const canOperateByReportWorker = computed(() => { |
| | |
| | | width: "140", |
| | | }, |
| | | { |
| | | label: "加放数", |
| | | prop: "processRouteAddNum", |
| | | width: "100", |
| | | }, |
| | | { |
| | | label: "完成数量", |
| | | prop: "completeQuantity", |
| | | width: "140", |
| | | }, |
| | | { |
| | | label: "完成进度", |
| | | prop: "completionStatus", |
| | | dataType: "slot", |
| | | slot: "completionStatus", |
| | | width: "140", |
| | | }, |
| | | { |
| | | label: "计划开始时间", |
| | | prop: "planStartTime", |
| | | width: "140", |
| | | }, |
| | | { |
| | | label: "计划结束时间", |
| | | prop: "planEndTime", |
| | | width: "140", |
| | | }, |
| | | { |
| | |
| | | clickFun: row => { |
| | | showReportDialog(row); |
| | | }, |
| | | // 用户当前id在工单报工人或工序报工人中 |
| | | // 用户当前id在工单报工人或工序报工人中,且完成数量未达计划数量时可继续分批报工 |
| | | disabled: row => |
| | | row.completeQuantity >= row.planQuantity || |
| | | !canCurrentUserReport(row) || |
| | | row.hasUnreportedMachine, |
| | | !canCurrentUserReport(row), |
| | | }, |
| | | { |
| | | name: "生产排产", |
| | |
| | | productMainId: null, |
| | | teamList: [], |
| | | deviceId: null, |
| | | }); |
| | | // 班组人员产量时长明细 [{ userId, userName, quantity, workHour }] |
| | | const personList = ref([]); |
| | | const teamSelectOptions = computed(() => { |
| | | const base = |
| | | Array.isArray(reportForm.userIdsList) && reportForm.userIdsList.length > 0 |
| | | ? reportForm.userIdsList.map(u => ({ |
| | | userId: String(u.userId ?? ""), |
| | | nickName: String(u.nickName ?? "").trim(), |
| | | })) |
| | | : []; |
| | | const baseNameSet = new Set(base.map(u => u.nickName)); |
| | | const selected = Array.isArray(reportForm.teamList) |
| | | ? reportForm.teamList |
| | | : []; |
| | | const extraNames = selected |
| | | .map(item => { |
| | | if (typeof item === "string") return String(item).trim(); |
| | | const name = item?.userName ?? item?.nickName ?? ""; |
| | | return String(name).trim(); |
| | | }) |
| | | .filter(Boolean) |
| | | .filter(name => !baseNameSet.has(name)); |
| | | const extras = extraNames.map(name => ({ userId: "", nickName: name })); |
| | | return [...base, ...extras]; |
| | | }); |
| | | function removeLastFour(str) { |
| | | if (!str) return ""; // 空值保护 |
| | |
| | | }); |
| | | }; |
| | | |
| | | const showReportDialog = row => { |
| | | const showReportDialog = async row => { |
| | | currentReportRowData.value = row; |
| | | reportForm.planQuantity = row.planQuantity - row.completeQuantity; |
| | | reportForm.quantity = row.planQuantity - row.completeQuantity; |
| | |
| | | reportForm.replenishQty = 0; |
| | | reportForm.teamList = []; |
| | | reportForm.scrapQty = 0; |
| | | reportForm.addQty = 0; |
| | | reportForm.addQty = row.processRouteAddNum ? Number(row.processRouteAddNum) : 0; |
| | | reportForm.userIds = row.userIds || []; |
| | | |
| | | // 合并工单报工人和工序报工人,去重 |
| | |
| | | userId: item.userId, |
| | | nickName: item.nickName, |
| | | })); |
| | | |
| | | // 获取报工弹窗数据,带出默认班组成员(合并排产人员与机台操作员,去重) |
| | | personList.value = []; |
| | | try { |
| | | const res = await getProductWorkOrderById(row.id); |
| | | const reportPersons = (Array.isArray(res?.data?.reportPersons) |
| | | ? res.data.reportPersons |
| | | : [] |
| | | ) |
| | | .filter(p => p && typeof p === "object") |
| | | .map(p => ({ |
| | | userId: p.userId ?? p.id ?? "", |
| | | userName: String(p.userName || p.nickName || "").trim(), |
| | | })) |
| | | .filter(p => p.userName !== ""); |
| | | personList.value = reportPersons.map(p => ({ |
| | | userId: p.userId, |
| | | userName: p.userName, |
| | | quantity: null, |
| | | workHour: null, |
| | | })); |
| | | reportForm.teamList = reportPersons.map(p => ({ |
| | | userId: p.userId, |
| | | userName: p.userName, |
| | | })); |
| | | } catch (err) { |
| | | console.error("获取报工弹窗数据失败", err); |
| | | } |
| | | |
| | | nextTick(() => { |
| | | reportFormRef.value?.clearValidate(); |
| | |
| | | // return; |
| | | // } |
| | | |
| | | // 校验各班组产量之和等于报工总产量 |
| | | const personQuantityTotal = personList.value.reduce( |
| | | (sum, p) => sum + (Number(p.quantity) || 0), |
| | | 0 |
| | | ); |
| | | if (personQuantityTotal !== quantity) { |
| | | ElMessageBox.alert("各班组产量之和必须等于报工总产量", "提示", { |
| | | confirmButtonText: "确定", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const submitData = { |
| | | ...reportForm, |
| | | quantity: quantity, |
| | | scrapQty: scrapQty, |
| | | personList: personList.value, |
| | | }; |
| | | addProductMain(submitData).then(res => { |
| | | if (res.code === 200) { |
| | |
| | | |
| | | const handleTeamListChange = val => { |
| | | if (!Array.isArray(val)) return; |
| | | if (!val.some(item => typeof item === "string")) return; |
| | | reportForm.teamList = val.map(item => { |
| | | let hasString = false; |
| | | const newList = val.map(item => { |
| | | if (typeof item === "string") { |
| | | hasString = true; |
| | | return { userName: item }; |
| | | } |
| | | return item; |
| | | }); |
| | | |
| | | if (hasString) { |
| | | reportForm.teamList = newList; |
| | | } |
| | | |
| | | // 解决 allow-create 在 multiple 模式下输入框内容不自动清空的问题 |
| | | setTimeout(() => { |
| | | if (teamSelectRef.value) { |
| | | // 1. 清除内部 query 状态 |
| | | teamSelectRef.value.query = ""; |
| | | if (teamSelectRef.value.states) { |
| | | teamSelectRef.value.states.query = ""; |
| | | teamSelectRef.value.states.inputValue = ""; |
| | | } |
| | | // 2. 强制清除 DOM 输入框的值 |
| | | const input = teamSelectRef.value.$el?.querySelector("input"); |
| | | if (input) { |
| | | input.value = ""; |
| | | } |
| | | // 3. 针对某些版本,重置选中标签的内部偏移(防止输入框被挤占) |
| | | if (typeof teamSelectRef.value.resetInputState === "function") { |
| | | teamSelectRef.value.resetInputState(); |
| | | } |
| | | } |
| | | }, 50); |
| | | }; |
| | | // 审核人 |
| | | const handleReviewerIdChange = userId => { |
| | |
| | | |
| | | // 判断当前用户是否能排产 |
| | | const canScheduleByWorkOrderNo = row => { |
| | | // 管理员可排产 |
| | | if (isAdminUser.value) return true; |
| | | if (!row) return false; |
| | | |
| | | const uid = String(currentUserId.value || ""); |