diff --git a/apps/web-antd/src/api/mes/pro/andon/config/index.ts b/apps/web-antd/src/api/mes/pro/andon/config/index.ts new file mode 100644 index 000000000..317c6cc50 --- /dev/null +++ b/apps/web-antd/src/api/mes/pro/andon/config/index.ts @@ -0,0 +1,48 @@ +import { requestClient } from '#/api/request'; + +export namespace MesProAndonConfigApi { + /** MES 安灯配置 */ + export interface AndonConfig { + id?: number; + reason?: string; // 呼叫原因 + level?: number; // 级别 + handlerRoleId?: number; // 处置角色编号 + handlerUserId?: number; // 处置人编号 + handlerUserNickname?: string; // 处置人姓名(详情回显) + remark?: string; + } +} + +/** 查询安灯配置分页 */ +export function getAndonConfigPage(params: any) { + return requestClient.get('/mes/pro/andon-config/page', { params }); +} + +/** 查询安灯配置列表 */ +export function getAndonConfigList() { + return requestClient.get( + '/mes/pro/andon-config/list', + ); +} + +/** 查询安灯配置详情 */ +export function getAndonConfig(id: number) { + return requestClient.get( + `/mes/pro/andon-config/get?id=${id}`, + ); +} + +/** 新增安灯配置 */ +export function createAndonConfig(data: MesProAndonConfigApi.AndonConfig) { + return requestClient.post('/mes/pro/andon-config/create', data); +} + +/** 修改安灯配置 */ +export function updateAndonConfig(data: MesProAndonConfigApi.AndonConfig) { + return requestClient.put('/mes/pro/andon-config/update', data); +} + +/** 删除安灯配置 */ +export function deleteAndonConfig(id: number) { + return requestClient.delete(`/mes/pro/andon-config/delete?id=${id}`); +} diff --git a/apps/web-antd/src/api/mes/pro/andon/record/index.ts b/apps/web-antd/src/api/mes/pro/andon/record/index.ts new file mode 100644 index 000000000..b8cf4349b --- /dev/null +++ b/apps/web-antd/src/api/mes/pro/andon/record/index.ts @@ -0,0 +1,76 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace MesProAndonRecordApi { + /** MES 安灯记录 */ + export interface AndonRecord { + id?: number; + configId?: number; // 安灯配置编号 + workstationId?: number; // 工作站编号 + workstationCode?: string; // 工作站编码 + workstationName?: string; // 工作站名称 + workOrderId?: number; // 生产工单编号 + workOrderCode?: string; // 工单编码 + processId?: number; // 工序编号 + processName?: string; // 工序名称 + userId?: number; // 发起用户编号 + userNickname?: string; // 发起人昵称 + reason?: string; // 呼叫原因 + level?: number; // 级别 + status?: number; // 处置状态 + handleTime?: number; // 处置时间(毫秒时间戳) + handlerUserId?: number; // 处置人编号 + handlerUserNickname?: string; // 处置人昵称 + remark?: string; // 备注 + createTime?: number; // 发起时间 + } + + /** MES 安灯记录分页查询参数 */ + export interface PageParams extends PageParam { + workstationId?: number; // 工作站编号 + userId?: number; // 发起用户编号 + handlerUserId?: number; // 处置人编号 + status?: number; // 处置状态 + createTime?: string[]; // 发起时间区间 + } +} + +/** 查询安灯记录分页 */ +export function getAndonRecordPage(params: MesProAndonRecordApi.PageParams) { + return requestClient.get>( + '/mes/pro/andon-record/page', + { params }, + ); +} + +/** 查询安灯记录详情 */ +export function getAndonRecord(id: number) { + return requestClient.get( + `/mes/pro/andon-record/get?id=${id}`, + ); +} + +/** 新增安灯记录 */ +export function createAndonRecord(data: MesProAndonRecordApi.AndonRecord) { + return requestClient.post('/mes/pro/andon-record/create', data); +} + +/** 删除安灯记录 */ +export function deleteAndonRecord(id: number) { + return requestClient.delete(`/mes/pro/andon-record/delete?id=${id}`); +} + +/** 更新安灯记录(保存/已处置) */ +export function updateAndonRecord(data: MesProAndonRecordApi.AndonRecord) { + return requestClient.put('/mes/pro/andon-record/update', data); +} + +/** 导出安灯记录 Excel */ +export function exportAndonRecord( + params: Partial, +) { + return requestClient.download('/mes/pro/andon-record/export-excel', { + params, + }); +} diff --git a/apps/web-antd/src/api/mes/pro/workorder/index.ts b/apps/web-antd/src/api/mes/pro/workorder/index.ts new file mode 100644 index 000000000..fcc22b79e --- /dev/null +++ b/apps/web-antd/src/api/mes/pro/workorder/index.ts @@ -0,0 +1,53 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace MesProWorkOrderApi { + /** MES 生产工单 */ + export interface WorkOrder { + id?: number; + code?: string; // 工单编码 + name?: string; // 工单名称 + type?: number; // 工单类型 + status?: number; // 工单状态 + sourceType?: number; + productId?: number; // 产品物料编号 + productCode?: string; + productName?: string; + productSpecification?: string; + quantity?: number; + unitName?: string; + routeId?: number; + routeName?: string; + clientId?: number; + clientName?: string; + planStartTime?: number | string; + planEndTime?: number | string; + actualStartTime?: number | string; + actualEndTime?: number | string; + remark?: string; + createTime?: number | string; + } + + export interface PageParams extends PageParam { + code?: string; + name?: string; + status?: number; + type?: number; + } +} + +/** 查询生产工单分页 */ +export function getWorkOrderPage(params: MesProWorkOrderApi.PageParams) { + return requestClient.get>( + '/mes/pro/work-order/page', + { params }, + ); +} + +/** 查询生产工单详情 */ +export function getWorkOrder(id: number) { + return requestClient.get( + `/mes/pro/work-order/get?id=${id}`, + ); +} diff --git a/apps/web-antd/src/views/mes/pro/andon/config/components/andon-config-select.vue b/apps/web-antd/src/views/mes/pro/andon/config/components/andon-config-select.vue new file mode 100644 index 000000000..7901e4700 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/andon/config/components/andon-config-select.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-antd/src/views/mes/pro/andon/config/components/index.ts b/apps/web-antd/src/views/mes/pro/andon/config/components/index.ts new file mode 100644 index 000000000..e7a766883 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/andon/config/components/index.ts @@ -0,0 +1 @@ +export { default as AndonConfigSelect } from './andon-config-select.vue'; diff --git a/apps/web-antd/src/views/mes/pro/andon/config/modules/config-form.vue b/apps/web-antd/src/views/mes/pro/andon/config/modules/config-form.vue new file mode 100644 index 000000000..00ef94418 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/andon/config/modules/config-form.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web-antd/src/views/mes/pro/andon/config/modules/config-modal.vue b/apps/web-antd/src/views/mes/pro/andon/config/modules/config-modal.vue new file mode 100644 index 000000000..1ef2d8b44 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/andon/config/modules/config-modal.vue @@ -0,0 +1,122 @@ + + + diff --git a/apps/web-antd/src/views/mes/pro/andon/record/data.ts b/apps/web-antd/src/views/mes/pro/andon/record/data.ts new file mode 100644 index 000000000..36cb24d93 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/andon/record/data.ts @@ -0,0 +1,368 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MesProAndonConfigApi } from '#/api/mes/pro/andon/config'; +import type { MesProAndonRecordApi } from '#/api/mes/pro/andon/record'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { MdWorkstationSelect } from '#/views/mes/md/workstation/components'; +import { ProProcessSelect } from '#/views/mes/pro/process/components'; +import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components'; +import { MesProWorkOrderStatusEnum } from '#/views/mes/utils/constants'; + +import { AndonConfigSelect } from '../config/components'; + +/** 列表搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'workstationId', + label: '工作站', + component: MdWorkstationSelect as any, + componentProps: { allowClear: true, placeholder: '请选择工作站' }, + }, + { + fieldName: 'userId', + label: '发起人', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择发起人', + valueField: 'id', + }, + }, + { + fieldName: 'handlerUserId', + label: '处置人', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择处置人', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '处理状态', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_STATUS, 'number'), + placeholder: '请选择状态', + }, + }, + { + fieldName: 'createTime', + label: '发起时间', + component: 'RangePicker', + componentProps: { + allowClear: true, + format: 'YYYY-MM-DD HH:mm:ss', + showTime: true, + valueFormat: 'YYYY-MM-DD HH:mm:ss', + }, + }, + ]; +} + +/** 列表字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { field: 'workstationCode', title: '工作站编码', width: 140 }, + { field: 'workstationName', title: '工作站名称', minWidth: 140 }, + { field: 'workOrderCode', title: '工单编码', width: 140 }, + { field: 'processName', title: '工序名称', width: 140 }, + { field: 'userNickname', title: '发起人', width: 110 }, + { + field: 'createTime', + title: '发起时间', + width: 180, + formatter: 'formatDateTime', + }, + { field: 'reason', title: '呼叫原因', minWidth: 160 }, + { + field: 'level', + title: '级别', + width: 90, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MES_PRO_ANDON_LEVEL }, + }, + }, + { + field: 'handleTime', + title: '处理时间', + width: 180, + formatter: 'formatDateTime', + }, + { field: 'handlerUserNickname', title: '处理人', width: 110 }, + { + field: 'status', + title: '处置状态', + width: 110, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MES_PRO_ANDON_STATUS }, + }, + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 安灯记录表单(按表单类型动态切换字段) */ +export function useFormSchema( + formType: 'create' | 'detail' | 'update', + onConfigChange?: (config: MesProAndonConfigApi.AndonConfig | undefined) => void, +): VbenFormSchema[] { + const isCreate = formType === 'create'; + const isUpdate = formType === 'update'; + const isDetail = formType === 'detail'; + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { triggerFields: [''], show: () => false }, + }, + isCreate + ? { + fieldName: 'workstationId', + label: '工作站', + component: MdWorkstationSelect as any, + componentProps: { placeholder: '请选择工作站' }, + rules: 'selectRequired', + } + : { + fieldName: 'workstationName', + label: '工作站', + component: 'Input', + componentProps: { disabled: true }, + }, + isCreate + ? { + fieldName: 'userId', + label: '发起人', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择发起人', + valueField: 'id', + }, + } + : { + fieldName: 'userNickname', + label: '发起人', + component: 'Input', + componentProps: { disabled: true }, + }, + isCreate + ? { + fieldName: 'workOrderId', + label: '生产工单', + component: ProWorkOrderSelect as any, + componentProps: { + placeholder: '请选择工单(可选)', + status: MesProWorkOrderStatusEnum.CONFIRMED, + }, + } + : { + fieldName: 'workOrderCode', + label: '生产工单', + component: 'Input', + componentProps: { disabled: true }, + }, + isCreate + ? { + fieldName: 'processId', + label: '工序', + component: ProProcessSelect as any, + componentProps: { placeholder: '请选择工序(可选)' }, + } + : { + fieldName: 'processName', + label: '工序', + component: 'Input', + componentProps: { disabled: true }, + }, + isCreate + ? { + fieldName: 'configId', + label: '呼叫原因', + component: AndonConfigSelect as any, + componentProps: { onChange: onConfigChange }, + rules: 'selectRequired', + } + : { + fieldName: 'reason', + label: '呼叫原因', + component: 'Input', + componentProps: { disabled: true }, + }, + { + fieldName: 'level', + label: '级别', + component: 'Select', + componentProps: { + disabled: true, + options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_LEVEL, 'number'), + placeholder: '由呼叫原因自动带出', + }, + }, + // 处置信息:update / detail 才展示 + ...(isCreate + ? [] + : ([ + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + disabled: true, + options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_STATUS, 'number'), + }, + }, + { + fieldName: 'handleTime', + label: '处置时间', + component: 'DatePicker', + componentProps: { + disabled: !isUpdate, + format: 'YYYY-MM-DD HH:mm:ss', + placeholder: isUpdate ? '请选择处置时间' : undefined, + showTime: true, + valueFormat: 'x', + }, + }, + isUpdate + ? { + fieldName: 'handlerUserId', + label: '处置人', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择处置人', + valueField: 'id', + }, + } + : { + fieldName: 'handlerUserNickname', + label: '处置人', + component: 'Input', + componentProps: { disabled: true }, + }, + ] as VbenFormSchema[])), + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + disabled: isDetail, + maxLength: 250, + placeholder: '请输入备注', + rows: 2, + }, + }, + ]; +} + +/** 安灯配置表格列(弹窗内嵌网格) */ +export function useConfigGridColumns(): VxeTableGridOptions['columns'] { + return [ + { field: 'reason', title: '呼叫原因', minWidth: 200 }, + { + field: 'level', + title: '级别', + width: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MES_PRO_ANDON_LEVEL }, + }, + }, + { field: 'handlerUserNickname', title: '处置人', width: 140 }, + { field: 'remark', title: '备注', minWidth: 160 }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 安灯配置表单(弹窗内的新增/编辑表单) */ +export function useConfigFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { triggerFields: [''], show: () => false }, + }, + { + fieldName: 'reason', + label: '呼叫原因', + component: 'Textarea', + componentProps: { + autoSize: { maxRows: 3, minRows: 1 }, + maxLength: 200, + placeholder: '请输入呼叫原因', + }, + rules: z.string().min(1, '呼叫原因不能为空').max(200), + }, + { + fieldName: 'level', + label: '级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.MES_PRO_ANDON_LEVEL, 'number'), + placeholder: '请选择级别', + }, + rules: 'selectRequired', + }, + { + fieldName: 'handlerRoleId', + label: '处置角色', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: () => + import('#/api/system/role').then((m) => m.getSimpleRoleList()), + labelField: 'name', + placeholder: '请选择角色(与处置人至少填一个)', + valueField: 'id', + }, + }, + { + fieldName: 'handlerUserId', + label: '处置人', + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + placeholder: '请选择处置人(与角色至少填一个)', + valueField: 'id', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { maxLength: 100, placeholder: '请输入备注' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/mes/pro/andon/record/index.vue b/apps/web-antd/src/views/mes/pro/andon/record/index.vue new file mode 100644 index 000000000..d6d4921d8 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/andon/record/index.vue @@ -0,0 +1,174 @@ + + + diff --git a/apps/web-antd/src/views/mes/pro/andon/record/modules/form.vue b/apps/web-antd/src/views/mes/pro/andon/record/modules/form.vue new file mode 100644 index 000000000..62b08097f --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/andon/record/modules/form.vue @@ -0,0 +1,205 @@ + + + diff --git a/apps/web-antd/src/views/mes/pro/route/components/color-picker.vue b/apps/web-antd/src/views/mes/pro/route/components/color-picker.vue new file mode 100644 index 000000000..76623fcb0 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/route/components/color-picker.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/apps/web-antd/src/views/mes/pro/route/components/index.ts b/apps/web-antd/src/views/mes/pro/route/components/index.ts new file mode 100644 index 000000000..4a78a4ce4 --- /dev/null +++ b/apps/web-antd/src/views/mes/pro/route/components/index.ts @@ -0,0 +1 @@ +export { default as RouteColorPicker } from './color-picker.vue'; diff --git a/apps/web-antd/src/views/mes/pro/route/data.ts b/apps/web-antd/src/views/mes/pro/route/data.ts index 1e6f7218e..4fa8916fd 100644 --- a/apps/web-antd/src/views/mes/pro/route/data.ts +++ b/apps/web-antd/src/views/mes/pro/route/data.ts @@ -7,7 +7,7 @@ import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom'; import { h } from 'vue'; -import { DICT_TYPE } from '@vben/constants'; +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; import { getDictOptions } from '@vben/hooks'; import { Button } from 'ant-design-vue'; @@ -20,6 +20,8 @@ import { } from '#/views/mes/md/item/components'; import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants'; +import { RouteColorPicker } from './components'; + /** 工艺路线表单 */ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { return [ @@ -120,7 +122,13 @@ export function useGridFormSchema(): VbenFormSchema[] { } /** 列表字段 */ -export function useGridColumns(): VxeTableGridOptions['columns'] { +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: MesProRouteApi.Route, + ) => PromiseLike, + statusEditable = true, +): VxeTableGridOptions['columns'] { return [ { field: 'code', @@ -134,7 +142,16 @@ export function useGridColumns(): VxeTableGridOptions['col field: 'status', title: '状态', width: 110, - slots: { default: 'status' }, + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + checkedValue: CommonStatusEnum.ENABLE, + disabled: !statusEditable, + unCheckedValue: CommonStatusEnum.DISABLE, + }, + }, }, { field: 'remark', title: '备注', minWidth: 160 }, { @@ -200,11 +217,7 @@ export function useRouteProcessFormSchema( { fieldName: 'colorCode', label: '甘特图颜色', - component: 'Input', - componentProps: { - maxLength: 16, - placeholder: '请输入颜色 hex,例如 #00AEF3', - }, + component: RouteColorPicker, }, { fieldName: 'keyFlag', diff --git a/apps/web-antd/src/views/mes/pro/route/index.vue b/apps/web-antd/src/views/mes/pro/route/index.vue index cdfc72f71..4f066ffa6 100644 --- a/apps/web-antd/src/views/mes/pro/route/index.vue +++ b/apps/web-antd/src/views/mes/pro/route/index.vue @@ -2,11 +2,13 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { MesProRouteApi } from '#/api/mes/pro/route'; +import { useAccess } from '@vben/access'; import { DocAlert, Page, useVbenModal } from '@vben/common-ui'; -import { CommonStatusEnum } from '@vben/constants'; +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictLabel } from '@vben/hooks'; import { downloadFileFromBlobPart } from '@vben/utils'; -import { Button, message, Modal, Switch, Tooltip } from 'ant-design-vue'; +import { Button, message, Modal, Tooltip } from 'ant-design-vue'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { @@ -20,6 +22,9 @@ import { $t } from '#/locales'; import { useGridColumns, useGridFormSchema } from './data'; import Form from './modules/form.vue'; +const { hasAccessByCodes } = useAccess(); +const statusEditable = hasAccessByCodes(['mes:pro-route:update']); // 是否可切换状态 + const [FormModal, formModalApi] = useVbenModal({ connectedComponent: Form, destroyOnClose: true, @@ -46,20 +51,22 @@ function handleDetail(row: MesProRouteApi.Route) { } /** 切换状态 */ -async function handleStatusChange(row: MesProRouteApi.Route, value: number) { - const text = value === CommonStatusEnum.ENABLE ? '启用' : '停用'; - const previousStatus = row.status; - Modal.confirm({ - title: `确认要"${text}""${row.name}"工艺路线吗?`, - onOk: async () => { - await updateRouteStatus(row.id!, value); - message.success($t('ui.actionMessage.operationSuccess')); - handleRefresh(); - }, - onCancel: () => { - // 用户取消时回滚开关状态 - row.status = previousStatus; - }, +async function handleStatusChange( + newStatus: number, + row: MesProRouteApi.Route, +): Promise { + return new Promise((resolve, reject) => { + Modal.confirm({ + content: `确认要将"${row.name}"工艺路线切换为【${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}】吗?`, + async onOk() { + await updateRouteStatus(row.id!, newStatus); + message.success($t('ui.actionMessage.operationSuccess')); + resolve(true); + }, + onCancel() { + reject(new Error('取消操作')); + }, + }); }); } @@ -89,7 +96,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ schema: useGridFormSchema(), }, gridOptions: { - columns: useGridColumns(), + columns: useGridColumns(handleStatusChange, statusEditable), height: 'auto', keepSource: true, proxyConfig: { @@ -146,20 +153,6 @@ const [Grid, gridApi] = useVbenVxeGrid({ - -