银川
1.生产任务 - 选工作站自动计算生产时长
2.生产流程 - 查看产品设计归档及附件
| | |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** 根据工作站和数量计算生产时长 */ |
| | | export function calculateTaskDuration(workstationId: number, quantity: number) { |
| | | return requestClient.get<{ duration: number }>( |
| | | '/mes/pro/task/calculate-duration', |
| | | { params: { workstationId, quantity } }, |
| | | ); |
| | | } |
| | |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { MdWorkshopSelect, MdWorkstationSelect } from '#/views/mes/md/workstation/components'; |
| | | import { MdWorkshopSelect } from '#/views/mes/md/workstation/components'; |
| | | |
| | | import { DvMachineryTypeSelect } from './type/components'; |
| | | |
| | |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | placeholder: '请输入生产效率(每分钟)', |
| | | placeholder: '请输入生产效率(每小时)', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | placeholder: '请输入传送履带数量', |
| | | }, |
| | | help: '2代表双倍效率', |
| | | }, |
| | | { |
| | | fieldName: 'workstationId', |
| | | label: '工作站', |
| | | component: markRaw(MdWorkstationSelect), |
| | | componentProps: { |
| | | placeholder: '请选择工作站', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'specification', |
| | |
| | | placeholder: '请选择设备状态', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'workstationId', |
| | | label: '工作站', |
| | | component: markRaw(MdWorkstationSelect), |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请选择工作站', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | props: { type: DICT_TYPE.MES_DV_MACHINERY_STATUS }, |
| | | }, |
| | | }, |
| | | { field: 'productionEfficiency', title: '生产效率', width: 120 }, |
| | | { field: 'productionEfficiency', title: '生产效率(/h)', width: 120 }, |
| | | { field: 'conveyorBelt', title: '传送履带', width: 100 }, |
| | | { field: 'workstationName', title: '工作站', minWidth: 140 }, |
| | | { |
| | | field: 'lastCheckTime', |
| | | title: '最近点检时间', |
| | |
| | | } from '#/api/mes/pro/feedback'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import QuickFeedbackForm from '../workorder-center/modules/quick-feedback.vue'; |
| | | import BatchFeedbackForm from './modules/batch-feedback.vue'; |
| | | import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue'; |
| | | |
| | | const userStore = useUserStore(); |
| | | const currentUserId = userStore.userInfo?.id; // 当前登录用户 ID,用于审批人权限判断 |
| | |
| | | connectedComponent: BatchFeedbackForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>(); |
| | | |
| | | /** 刷新表格 */ |
| | | function handleRefresh() { |
| | |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: '归档', |
| | | type: 'link', |
| | | onClick: () => archiveDetailRef.value?.open(row.itemCode), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | <ArchiveDetail ref="archiveDetailRef" /> |
| | | </Page> |
| | | </template> |
| | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import CrmCustomerSelect from '#/components/crm-customer-select.vue'; |
| | | import { calculateTaskDuration } from '#/api/mes/pro/task'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | import { MdWorkstationSelect } from '#/views/mes/md/workstation/components'; |
| | |
| | | component: markRaw(MdWorkstationSelect), |
| | | componentProps: { |
| | | placeholder: '请选择工作站', |
| | | onChange: () => recalcDuration(formApi), |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | |
| | | min: 0.01, |
| | | placeholder: '请输入排产数量', |
| | | precision: 2, |
| | | onChange: () => recalcDuration(formApi), |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | placeholder: '请选择开始时间', |
| | | placeholder: '请先选择工作站', |
| | | showTime: true, |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | // 开始时间变更:重新计算结束时间 |
| | | onChange: () => recalcEndTime(formApi), |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['workstationId'], |
| | | componentProps: (values) => ({ |
| | | disabled: !values.workstationId, |
| | | placeholder: values.workstationId ? '请选择开始时间' : '请先选择工作站', |
| | | }), |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | ]; |
| | | } |
| | | |
| | | /** 选择工作站或修改数量后,自动计算生产时长 */ |
| | | async function recalcDuration(formApi?: VbenFormApi) { |
| | | if (!formApi) return; |
| | | const values = await formApi.getValues(); |
| | | if (!values.workstationId || !values.quantity) return; |
| | | try { |
| | | const res = await calculateTaskDuration(values.workstationId, values.quantity); |
| | | await formApi.setFieldValue('duration', res.duration); |
| | | if (res.duration === 0) { |
| | | message.warning('该工作站未绑定设备,或绑定设备未配置生产效率'); |
| | | } |
| | | // 重新计算结束时间 |
| | | await recalcEndTime(formApi); |
| | | } catch { |
| | | // 计算失败不阻塞用户操作 |
| | | } |
| | | } |
| | | |
| | | /** 计算结束时间:开始时间 + 生产时长(小时) */ |
| | | async function recalcEndTime(formApi?: VbenFormApi) { |
| | | if (!formApi) { |
| | |
| | | } |
| | | const values = await formApi.getValues(); |
| | | if (values.startTime && values.duration) { |
| | | const start = Number(values.startTime); |
| | | await formApi.setFieldValue( |
| | | 'endTime', |
| | | start + values.duration * 3600 * 1000, |
| | | ); |
| | | const start = new Date(values.startTime).getTime(); |
| | | const endTime = start + values.duration * 3600 * 1000; |
| | | const pad = (n: number) => String(n).padStart(2, '0'); |
| | | const d = new Date(endTime); |
| | | const formatted = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; |
| | | await formApi.setFieldValue('endTime', formatted); |
| | | } |
| | | } |
| | | |
| | |
| | | import { getGanttTaskList, previewAutoGenerate, autoGenerateTasks } from '#/api/mes/pro/task'; |
| | | import { getWorkOrderPage } from '#/api/mes/pro/workorder'; |
| | | |
| | | import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue'; |
| | | import { GanttChart } from './components'; |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import ScheduleForm from './modules/schedule-form.vue'; |
| | |
| | | |
| | | const router = useRouter(); |
| | | const ganttTasks = ref<any[]>([]); // 甘特图预览数据 |
| | | const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>(); |
| | | |
| | | const [ScheduleModal, scheduleModalApi] = useVbenModal({ |
| | | connectedComponent: ScheduleForm, |
| | |
| | | ifShow: row.status === MesProWorkOrderStatusEnum.CONFIRMED, |
| | | onClick: handleSchedule.bind(null, row), |
| | | }, |
| | | { |
| | | label: '归档', |
| | | type: 'link', |
| | | onClick: () => archiveDetailRef.value?.open(row.productCode), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | <ArchiveDetail ref="archiveDetailRef" /> |
| | | </Page> |
| | | </template> |
| | |
| | | } from '#/api/mes/pro/workorder'; |
| | | import { previewAutoGenerate, autoGenerateTasks } from '#/api/mes/pro/task'; |
| | | |
| | | import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue'; |
| | | import { useGridColumns, useGridFormSchema, useTaskGridColumns } from './data'; |
| | | import QuickFeedbackForm from './modules/quick-feedback.vue'; |
| | | import SchedulePreviewModal from './modules/schedule-preview.vue'; |
| | | |
| | | defineOptions({ name: 'WorkorderCenter' }); |
| | | const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>(); |
| | | |
| | | const router = useRouter(); |
| | | |
| | |
| | | confirm: handleScheduleExecute.bind(null, row), |
| | | }, |
| | | }, |
| | | { |
| | | label: '归档', |
| | | type: 'link', |
| | | onClick: () => archiveDetailRef.value?.open(row.productCode), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | </Collapse.Panel> |
| | | </Collapse> |
| | | </Card> |
| | | <ArchiveDetail ref="archiveDetailRef" /> |
| | | </Page> |
| | | </template> |
| | |
| | | getWorkOrderPage, |
| | | } from "#/api/mes/pro/workorder"; |
| | | import { $t } from "#/locales"; |
| | | import { BarcodeDetail } from "#/views/wls/barcode/components"; |
| | | import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue'; |
| | | import { BarcodeDetail } from '#/views/wls/barcode/components'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from "./data"; |
| | | import Form from "./modules/form.vue"; |
| | |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 条码详情弹窗 |
| | | const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>(); |
| | | const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 条码详情弹窗 |
| | | |
| | | /** 刷新表格 */ |
| | | function handleRefresh() { |
| | |
| | | auth: ['mes:pro-work-order:query'], |
| | | onClick: handleBarcode.bind(null, row), |
| | | }, |
| | | { |
| | | label: '归档', |
| | | type: 'link', |
| | | onClick: () => archiveDetailRef.value?.open(row.productCode), |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | </Grid> |
| | | <BarcodeDetail ref="barcodeDetailRef" /> |
| | | <ArchiveDetail ref="archiveDetailRef" /> |
| | | </Page> |
| | | </template> |