Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0
| | |
| | | status?: number; // 设备状态 |
| | | lastMaintenTime?: Date; // 最近保养时间 |
| | | lastCheckTime?: Date; // 最近点检时间 |
| | | productionEfficiency?: number; // 生产效率 |
| | | conveyorBelt?: number; // 传送履带数量 |
| | | workstationId?: number; // 工作站编号 |
| | | workstationName?: string; // 工作站名称 |
| | | remark?: string; // 备注 |
| | | createTime?: Date; // 创建时间 |
| | | } |
| | |
| | | } |
| | | |
| | | export interface PageParams extends PageParam { |
| | | projectId?: number; |
| | | auditNo?: string; |
| | | documentId?: number; |
| | | documentName?: string; |
| | |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** 根据工作站和数量计算生产时长 */ |
| | | export function calculateTaskDuration(workstationId: number, quantity: number) { |
| | | return requestClient.get<{ duration: number }>( |
| | | '/mes/pro/task/calculate-duration', |
| | | { params: { workstationId, quantity } }, |
| | | ); |
| | | } |
| | |
| | | } |
| | | |
| | | /** 客户放入公海 */ |
| | | async function handlePutPool(): Promise<boolean | undefined> { |
| | | try { |
| | | await confirm(`确定将客户【${customer.value.name}】放入公海吗?`); |
| | | } catch { |
| | | return false; |
| | | } |
| | | // 放入公海 |
| | | await putCustomerPool(customerId.value); |
| | | // 提示并返回成功 |
| | | message.success('放入公海成功'); |
| | | return true; |
| | | } |
| | | // async function handlePutPool(): Promise<boolean | undefined> { |
| | | // try { |
| | | // await confirm(`确定将客户【${customer.value.name}】放入公海吗?`); |
| | | // } catch { |
| | | // return false; |
| | | // } |
| | | // // 放入公海 |
| | | // await putCustomerPool(customerId.value); |
| | | // // 提示并返回成功 |
| | | // message.success('放入公海成功'); |
| | | // return true; |
| | | // } |
| | | |
| | | /** 更新成交状态操作 */ |
| | | async function handleUpdateDealStatus(): Promise<boolean | undefined> { |
| | |
| | | ifShow: !customer.ownerUserId, |
| | | onClick: handleDistributeForm, |
| | | }, |
| | | { |
| | | label: '放入公海', |
| | | type: 'default', |
| | | ifShow: |
| | | !!customer.ownerUserId && permissionListRef?.validateOwnerUser, |
| | | onClick: handlePutPool, |
| | | }, |
| | | // { |
| | | // label: '放入公海', |
| | | // type: 'default', |
| | | // ifShow: |
| | | // !!customer.ownerUserId && permissionListRef?.validateOwnerUser, |
| | | // onClick: handlePutPool, |
| | | // }, |
| | | ]" |
| | | /> |
| | | </template> |
| | |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | pool: true, |
| | | sceneType: 4, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | |
| | | rules: z.number().default(MesDvMachineryStatusEnum.STOP), |
| | | }, |
| | | { |
| | | fieldName: 'productionEfficiency', |
| | | label: '生产效率', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | placeholder: '请输入生产效率(每小时)', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'conveyorBelt', |
| | | label: '传送履带', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 0, |
| | | placeholder: '请输入传送履带数量', |
| | | }, |
| | | help: '2代表双倍效率', |
| | | }, |
| | | { |
| | | fieldName: 'specification', |
| | | label: '规格型号', |
| | | component: 'Input', |
| | |
| | | props: { type: DICT_TYPE.MES_DV_MACHINERY_STATUS }, |
| | | }, |
| | | }, |
| | | { field: 'productionEfficiency', title: '生产效率(/h)', width: 120 }, |
| | | { field: 'conveyorBelt', title: '传送履带', width: 100 }, |
| | | { |
| | | field: 'lastCheckTime', |
| | | title: '最近点检时间', |
| | |
| | | ); |
| | | |
| | | const materialFormType = computed<FormType>(() => |
| | | isPendingPublish.value || isPendingUpload.value || isPendingAudit.value ? 'update' : 'detail', |
| | | isPendingPublish.value || isPendingUpload.value ? 'update' : 'detail', |
| | | ); |
| | | |
| | | const [Descriptions] = useDescription({ |
| | |
| | | label: $t('common.edit'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: isFormEditable, |
| | | onClick: handleEdit, |
| | | }, |
| | | { |
| | | label: '发布', |
| | | type: 'primary', |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: isPendingPublish, |
| | | onClick: handlePublish, |
| | | }, |
| | | { |
| | | label: '确认上传', |
| | | type: 'primary', |
| | | auth: ['mes:pd-project:update'], |
| | | ifShow: isPendingUpload, |
| | | onClick: handleUpload, |
| | | }, |
| | | { |
| | | label: '审核通过', |
| | | type: 'primary', |
| | | auth: ['mes:pd-project:audit'], |
| | | ifShow: isPendingAudit, |
| | | onClick: handleAudit, |
| | | }, |
| | | { |
| | | label: '归档', |
| | | type: 'default', |
| | | auth: ['mes:pd-project:archive'], |
| | | ifShow: isPendingArchive, |
| | | onClick: handleArchive, |
| | | }, |
| | |
| | | <Tabs.TabPane key="workflow" tab="流程进度" :force-render="true"> |
| | | <WorkflowTimeline :status="project.status ?? 0" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane v-if="!isPendingAudit" key="material" tab="设计资料" :force-render="true"> |
| | | <Tabs.TabPane v-if="!isPendingAudit" key="material" tab="设计资料"> |
| | | <p v-if="isPendingPublish" class="mb-3 text-sm text-amber-600"> |
| | | 请先发布任务后再上传设计资料 |
| | | </p> |
| | |
| | | :project-status="project.status" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane v-if="isPendingAudit" key="audit" tab="资料审核" :force-render="true"> |
| | | <Tabs.TabPane v-if="isPendingAudit" key="audit" tab="资料审核"> |
| | | <DocumentAuditList :project-id="projectId" @success="loadProjectDetail" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | |
| | | return await getPdDocumentAuditPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | projectId: props.projectId, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | |
| | | label: '审核', |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pd-document-audit:update'], |
| | | ifShow: row.auditStatus === 0, |
| | | onClick: handleAudit.bind(null, row), |
| | | }, |
| | | ]" |
| | |
| | | } 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> |