feat(mes): 提交 wm outsource 相关的迁移(代码优化)

pull/350/head
YunaiV 2026-05-30 09:16:33 +08:00
parent b6d1154b8f
commit 79af870afe
24 changed files with 970 additions and 207 deletions

View File

@ -42,3 +42,38 @@ export function getCardPage(params: MesProCardApi.PageParams) {
export function getCard(id: number) {
return requestClient.get<MesProCardApi.Card>(`/mes/pro/card/get?id=${id}`);
}
/** 新增生产流转卡 */
export function createCard(data: MesProCardApi.Card) {
return requestClient.post<number>('/mes/pro/card/create', data);
}
/** 修改生产流转卡 */
export function updateCard(data: MesProCardApi.Card) {
return requestClient.put('/mes/pro/card/update', data);
}
/** 删除生产流转卡 */
export function deleteCard(id: number) {
return requestClient.delete(`/mes/pro/card/delete?id=${id}`);
}
/** 导出生产流转卡 */
export function exportCard(params: any) {
return requestClient.download('/mes/pro/card/export-excel', { params });
}
/** 提交生产流转卡 */
export function submitCard(id: number) {
return requestClient.put(`/mes/pro/card/submit?id=${id}`);
}
/** 完成生产流转卡 */
export function finishCard(id: number) {
return requestClient.put(`/mes/pro/card/finish?id=${id}`);
}
/** 取消生产流转卡 */
export function cancelCard(id: number) {
return requestClient.put(`/mes/pro/card/cancel?id=${id}`);
}

View File

@ -0,0 +1,62 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesProCardProcessApi {
/** MES 流转卡工序记录 */
export interface CardProcess {
id?: number; // 编号
cardId?: number; // 流转卡编号
sort?: number; // 序号
processId?: number; // 工序编号
processCode?: string; // 工序编码
processName?: string; // 工序名称
inputTime?: number; // 进入工序时间
outputTime?: number; // 出工序时间
inputQuantity?: number; // 投入数量
outputQuantity?: number; // 产出数量
unqualifiedQuantity?: number; // 不合格品数量
workstationId?: number; // 工位编号
workstationCode?: string; // 工位编码
workstationName?: string; // 工位名称
userId?: number; // 操作人编号
nickname?: string; // 操作人名称
ipqcId?: number; // 过程检验单编号
remark?: string; // 备注
}
/** MES 流转卡工序记录分页查询参数 */
export interface PageParams extends PageParam {
cardId?: number;
}
}
/** 查询流转卡工序记录分页 */
export function getCardProcessPage(params: MesProCardProcessApi.PageParams) {
return requestClient.get<PageResult<MesProCardProcessApi.CardProcess>>(
'/mes/pro/card-process/page',
{ params },
);
}
/** 查询流转卡工序记录详情 */
export function getCardProcess(id: number) {
return requestClient.get<MesProCardProcessApi.CardProcess>(
`/mes/pro/card-process/get?id=${id}`,
);
}
/** 新增流转卡工序记录 */
export function createCardProcess(data: MesProCardProcessApi.CardProcess) {
return requestClient.post('/mes/pro/card-process/create', data);
}
/** 修改流转卡工序记录 */
export function updateCardProcess(data: MesProCardProcessApi.CardProcess) {
return requestClient.put('/mes/pro/card-process/update', data);
}
/** 删除流转卡工序记录 */
export function deleteCardProcess(id: number) {
return requestClient.delete(`/mes/pro/card-process/delete?id=${id}`);
}

View File

@ -48,6 +48,8 @@ export namespace MesProTaskApi {
name?: string;
workOrderId?: number;
workstationId?: number;
routeId?: number;
processId?: number;
itemId?: number;
statuses?: number[];
status?: number;
@ -66,3 +68,28 @@ export function getTaskPage(params: MesProTaskApi.PageParams) {
export function getTask(id: number) {
return requestClient.get<MesProTaskApi.Task>(`/mes/pro/task/get?id=${id}`);
}
/** 新增生产任务 */
export function createTask(data: MesProTaskApi.Task) {
return requestClient.post('/mes/pro/task/create', data);
}
/** 修改生产任务 */
export function updateTask(data: MesProTaskApi.Task) {
return requestClient.put('/mes/pro/task/update', data);
}
/** 删除生产任务 */
export function deleteTask(id: number) {
return requestClient.delete(`/mes/pro/task/delete?id=${id}`);
}
/** 导出生产任务 */
export function exportTask(params: any) {
return requestClient.download('/mes/pro/task/export-excel', { params });
}
/** 查询甘特图任务列表(非分页) */
export function getGanttTaskList(params: any) {
return requestClient.get<any[]>('/mes/pro/task/gantt-list', { params });
}

View File

@ -0,0 +1,62 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesProWorkOrderBomApi {
/** MES 生产工单 BOM */
export interface WorkOrderBom {
id?: number; // 编号
workOrderId?: number; // 生产工单编号
itemId?: number; // BOM 物料编号
itemName?: string; // 物料名称
itemCode?: string; // 物料编码
itemSpecification?: string; // 规格型号
unitMeasureId?: number; // 单位编号
unitMeasureName?: string; // 单位名称
quantity?: number; // 预计使用量
remark?: string; // 备注
itemOrProduct?: string; // 物料产品标识
}
/** MES 生产工单 BOM 分页查询参数 */
export interface PageParams extends PageParam {
workOrderId?: number;
}
}
/** 查询工单 BOM 分页 */
export function getWorkOrderBomPage(params: MesProWorkOrderBomApi.PageParams) {
return requestClient.get<PageResult<MesProWorkOrderBomApi.WorkOrderBom>>(
'/mes/pro/work-order-bom/page',
{ params },
);
}
/** 查询工单 BOM 详情 */
export function getWorkOrderBom(id: number) {
return requestClient.get<MesProWorkOrderBomApi.WorkOrderBom>(
`/mes/pro/work-order-bom/get?id=${id}`,
);
}
/** 新增工单 BOM */
export function createWorkOrderBom(data: MesProWorkOrderBomApi.WorkOrderBom) {
return requestClient.post('/mes/pro/work-order-bom/create', data);
}
/** 修改工单 BOM */
export function updateWorkOrderBom(data: MesProWorkOrderBomApi.WorkOrderBom) {
return requestClient.put('/mes/pro/work-order-bom/update', data);
}
/** 删除工单 BOM */
export function deleteWorkOrderBom(id: number) {
return requestClient.delete(`/mes/pro/work-order-bom/delete?id=${id}`);
}
/** 查询工单物料需求列表 */
export function getWorkOrderBomItemListByWorkOrderId(workOrderId: number) {
return requestClient.get<MesProWorkOrderBomApi.WorkOrderBom[]>(
`/mes/pro/work-order-bom/item-list-by-work-order-id?workOrderId=${workOrderId}`,
);
}

View File

@ -5,37 +5,48 @@ import { requestClient } from '#/api/request';
export namespace MesProWorkOrderApi {
/** MES 生产工单 */
export interface WorkOrder {
id?: number;
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;
orderSourceType?: number; // 来源类型
orderSourceCode?: string; // 来源单据编号
productId?: number; // 产品编号
productName?: string; // 产品名称
productCode?: string; // 产品编码
productSpecification?: string; // 规格型号
unitMeasureName?: string; // 单位名称
quantity?: number; // 生产数量
quantityProduced?: number; // 已生产数量
quantityChanged?: number; // 调整数量
quantityScheduled?: number; // 已排产数量
clientId?: number; // 客户编号
clientCode?: string; // 客户编码
clientName?: string; // 客户名称
vendorId?: number; // 供应商编号
vendorName?: string; // 供应商名称
planStartTime?: number | string;
planEndTime?: number | string;
actualStartTime?: number | string;
actualEndTime?: number | string;
remark?: string;
createTime?: number | string;
vendorCode?: string; // 供应商编码
batchCode?: string; // 批次号
requestDate?: number; // 需求日期
parentId?: number; // 父工单编号
parentCode?: string; // 父工单编码
finishDate?: number; // 完成时间
cancelDate?: number; // 取消时间
status?: number; // 工单状态
remark?: string; // 备注
createTime?: number; // 创建时间
}
/** MES 生产工单分页查询参数 */
export interface PageParams extends PageParam {
code?: string;
name?: string;
orderSourceCode?: string;
productId?: number;
clientId?: number;
status?: number;
type?: number;
requestDate?: number[];
}
}
@ -53,3 +64,38 @@ export function getWorkOrder(id: number) {
`/mes/pro/work-order/get?id=${id}`,
);
}
/** 新增生产工单 */
export function createWorkOrder(data: MesProWorkOrderApi.WorkOrder) {
return requestClient.post<number>('/mes/pro/work-order/create', data);
}
/** 修改生产工单 */
export function updateWorkOrder(data: MesProWorkOrderApi.WorkOrder) {
return requestClient.put('/mes/pro/work-order/update', data);
}
/** 删除生产工单 */
export function deleteWorkOrder(id: number) {
return requestClient.delete(`/mes/pro/work-order/delete?id=${id}`);
}
/** 导出生产工单 */
export function exportWorkOrder(params: any) {
return requestClient.download('/mes/pro/work-order/export-excel', { params });
}
/** 完成工单 */
export function finishWorkOrder(id: number) {
return requestClient.put(`/mes/pro/work-order/finish?id=${id}`);
}
/** 取消工单 */
export function cancelWorkOrder(id: number) {
return requestClient.put(`/mes/pro/work-order/cancel?id=${id}`);
}
/** 确认工单 */
export function confirmWorkOrder(id: number) {
return requestClient.put(`/mes/pro/work-order/confirm?id=${id}`);
}

View File

@ -19,6 +19,7 @@ export namespace MesWmOutsourceReceiptLineApi {
expireDate?: number; // 有效期
lotNumber?: string; // 生产批号
iqcCheckFlag?: boolean; // 是否需要质检
qualityStatus?: number; // 质量状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}

View File

@ -39,6 +39,15 @@ const routes: RouteRecordRaw[] = [
},
component: () => import('#/views/mes/wm/barcode/config/index.vue'),
},
{
path: 'pro/task/edit',
name: 'MesProTaskGanttEdit',
meta: {
title: '甘特图编辑',
activePath: '/mes/pro/task',
},
component: () => import('#/views/mes/pro/task/edit/index.vue'),
},
],
},
];

View File

@ -225,6 +225,12 @@ export const MesProWorkOrderTypeEnum = {
PURCHASE: 3, // 采购
} as const;
/** MES 工单来源类型枚举 */
export const MesProWorkOrderSourceTypeEnum = {
ORDER: 1, // 客户订单
STORE: 2, // 库存备货
} as const;
/** MES 生产任务状态枚举 */
export const MesProTaskStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,

View File

@ -38,25 +38,19 @@ const canSubmit = computed(() => // 是否可提交
formType.value === 'update' &&
formData.value?.status === MesWmOutsourceIssueStatusEnum.PREPARE,
);
// TODO @AI
const getTitle = computed(() => {
switch (formType.value) {
case 'detail': {
return $t('ui.actionTitle.view', ['外协发料单']);
}
case 'finish': {
return '执行领出';
}
case 'stock': {
return '执行拣货';
}
case 'update': {
return $t('ui.actionTitle.edit', ['外协发料单']);
}
default: {
return $t('ui.actionTitle.create', ['外协发料单']);
}
if (formType.value === 'detail') {
return $t('ui.actionTitle.view', ['外协发料单']);
}
if (formType.value === 'stock') {
return '执行拣货';
}
if (formType.value === 'finish') {
return '执行领出';
}
return formType.value === 'update'
? $t('ui.actionTitle.edit', ['外协发料单'])
: $t('ui.actionTitle.create', ['外协发料单']);
});
const [Form, formApi] = useVbenForm({
@ -138,7 +132,6 @@ async function handleFinish() {
}
const [Modal, modalApi] = useVbenModal({
// TODO @AI// form.vue xxx-form.vue
async onConfirm() {
if (!isEditable.value) {
await modalApi.close();

View File

@ -27,10 +27,9 @@ const props = defineProps<{
issueId: number;
}>();
// TODO @AI// computed(() =>
const isEditable = computed(() =>
const isEditable = computed(() => //
['create', 'update'].includes(props.formType),
); //
);
const isStock = computed(() => props.formType === 'stock'); //
const detailMap = reactive<
Record<number, MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]>

View File

@ -22,10 +22,9 @@ const emit = defineEmits<{
refresh: [];
}>();
// TODO @AI // computed(
const isEditable = computed(
() => ['create', 'stock', 'update'].includes(props.formType),
); //
const isEditable = computed(() => //
['create', 'stock', 'update'].includes(props.formType),
);
/** 添加收货明细 */
function handleCreate() {

View File

@ -37,25 +37,19 @@ const canSubmit = computed(() => // 是否可提交
formType.value === 'update' &&
formData.value?.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
);
// TODO @AI
const getTitle = computed(() => {
switch (formType.value) {
case 'detail': {
return $t('ui.actionTitle.view', ['外协入库单']);
}
case 'finish': {
return '完成入库';
}
case 'stock': {
return '执行上架';
}
case 'update': {
return $t('ui.actionTitle.edit', ['外协入库单']);
}
default: {
return $t('ui.actionTitle.create', ['外协入库单']);
}
if (formType.value === 'detail') {
return $t('ui.actionTitle.view', ['外协入库单']);
}
if (formType.value === 'stock') {
return '执行上架';
}
if (formType.value === 'finish') {
return '完成入库';
}
return formType.value === 'update'
? $t('ui.actionTitle.edit', ['外协入库单'])
: $t('ui.actionTitle.create', ['外协入库单']);
});
const [Form, formApi] = useVbenForm({

View File

@ -21,7 +21,6 @@ const emit = defineEmits(['success']);
const formData = ref<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>();
const receiptId = ref<number>(); //
// TODO @AI getTitle const style vue
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['外协入库单行'])

View File

@ -28,9 +28,9 @@ const props = defineProps<{
receiptId: number;
}>();
const isEditable = computed(() =>
const isEditable = computed(() => //
['create', 'update'].includes(props.formType),
); //
);
const isStock = computed(() => props.formType === 'stock'); //
const detailMap = reactive<
Record<number, MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail[]>

View File

@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmStockTakingPlanParamApi } from '#/api/mes/wm/stocktaking/plan/param';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteStockTakingPlanParam,
getStockTakingPlanParamPage,
} from '#/api/mes/wm/stocktaking/plan/param';
import { $t } from '#/locales';
import { useParamGridColumns } from '../data';
import ParamForm from './param-form.vue';
const props = defineProps<{
disabled: boolean; //
planId: number; //
}>();
const [ParamFormModal, paramFormModalApi] = useVbenModal({
connectedComponent: ParamForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 添加条件 */
function handleCreate() {
paramFormModalApi.setData({ planId: props.planId }).open();
}
/** 编辑条件 */
function handleEdit(row: MesWmStockTakingPlanParamApi.StockTakingPlanParam) {
paramFormModalApi.setData({ id: row.id, planId: props.planId }).open();
}
/** 删除条件 */
async function handleDelete(
row: MesWmStockTakingPlanParamApi.StockTakingPlanParam,
) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.valueName]),
duration: 0,
});
try {
await deleteStockTakingPlanParam(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.valueName]));
handleRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useParamGridColumns(!props.disabled),
height: 320,
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }) => {
if (!props.planId) {
return { list: [], total: 0 };
}
return await getStockTakingPlanParamPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
planId: props.planId,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<MesWmStockTakingPlanParamApi.StockTakingPlanParam>,
});
</script>
<template>
<div>
<ParamFormModal @success="handleRefresh" />
<Grid table-title="">
<template v-if="!disabled" #toolbar-tools>
<TableAction
:actions="[
{
label: '添加条件',
type: 'primary',
icon: ACTION_ICON.ADD,
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.valueName]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</div>
</template>

View File

@ -19,6 +19,7 @@ export namespace MesWmOutsourceReceiptLineApi {
expireDate?: number; // 有效期
lotNumber?: string; // 生产批号
iqcCheckFlag?: boolean; // 是否需要质检
qualityStatus?: number; // 质量状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}

View File

@ -1,11 +1,431 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProCardApi } from '#/api/mes/pro/card';
import type { MesProCardProcessApi } from '#/api/mes/pro/card/process';
import { markRaw } from 'vue';
import { h, markRaw } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { ElButton } from 'element-plus';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { MdItemSelect } from '#/views/mes/md/item/components';
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 {
MesAutoCodeRuleCode,
MesProWorkOrderStatusEnum,
} from '#/views/mes/utils/constants';
import { UserSelect } from '#/views/system/user/components';
/** 表单类型 */
export type FormType = 'create' | 'detail' | 'finish' | 'update';
/** 表头是否只读(完成、详情态) */
function isHeaderReadonly(formType: FormType): boolean {
return formType === 'detail' || formType === 'finish';
}
/** 新增/修改的表单 */
export function useFormSchema(
formType: FormType,
formApi?: VbenFormApi,
): VbenFormSchema[] {
const headerReadonly = isHeaderReadonly(formType);
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'status',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'code',
label: '流转卡编码',
component: 'Input',
componentProps: {
disabled: headerReadonly,
placeholder: '请输入流转卡编码',
},
rules: 'required',
suffix:
formType === 'create' || formType === 'update'
? () =>
h(
ElButton,
{
onClick: async () => {
const code = await generateAutoCode(
MesAutoCodeRuleCode.PRO_CARD_CODE,
);
await formApi?.setFieldValue('code', code);
},
},
{ default: () => '生成' },
)
: undefined,
},
{
fieldName: 'workOrderId',
label: '生产工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
disabled: headerReadonly,
placeholder: '请选择生产工单',
status: MesProWorkOrderStatusEnum.CONFIRMED,
},
rules: 'selectRequired',
},
{
fieldName: 'itemId',
label: '产品',
component: markRaw(MdItemSelect),
componentProps: {
disabled: headerReadonly,
placeholder: '请选择产品',
},
rules: 'selectRequired',
},
{
fieldName: 'transferedQuantity',
label: '流转数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
disabled: headerReadonly,
min: 0,
placeholder: '请输入流转数量',
precision: 2,
},
rules: 'required',
},
{
fieldName: 'batchCode',
label: '批次号',
component: 'Input',
componentProps: {
disabled: headerReadonly,
placeholder: '请输入批次号',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
disabled: headerReadonly,
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '流转卡编码',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入流转卡编码',
},
},
{
fieldName: 'workOrderId',
label: '生产工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
placeholder: '请选择生产工单',
},
},
{
fieldName: 'itemId',
label: '产品',
component: markRaw(MdItemSelect),
componentProps: {
placeholder: '请选择产品',
},
},
{
fieldName: 'batchCode',
label: '批次号',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入批次号',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesProCardApi.Card>['columns'] {
return [
{
field: 'code',
title: '流转卡编码',
width: 160,
slots: { default: 'code' },
},
{
field: 'workOrderCode',
title: '生产工单编号',
width: 160,
},
{
field: 'workOrderName',
title: '工单名称',
minWidth: 150,
},
{
field: 'batchCode',
title: '批次号',
width: 120,
},
{
field: 'itemCode',
title: '产品物料编码',
width: 140,
},
{
field: 'itemName',
title: '产品物料名称',
minWidth: 120,
},
{
field: 'specification',
title: '规格型号',
width: 120,
},
{
field: 'unitMeasureName',
title: '单位',
width: 80,
},
{
field: 'transferedQuantity',
title: '流转数量',
width: 100,
},
{
field: 'status',
title: '单据状态',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_PRO_WORK_ORDER_STATUS },
},
},
{
title: '操作',
width: 240,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 工序记录子表的字段 */
export function useProcessGridColumns(
editable: boolean,
): VxeTableGridOptions<MesProCardProcessApi.CardProcess>['columns'] {
return [
{
field: 'sort',
title: '序号',
width: 60,
},
{
field: 'processName',
title: '工序名称',
minWidth: 120,
},
{
field: 'processCode',
title: '工序编码',
width: 120,
},
{
field: 'inputTime',
title: '进入工序时间',
width: 180,
formatter: 'formatDateTime',
},
{
field: 'outputTime',
title: '出工序时间',
width: 180,
formatter: 'formatDateTime',
},
{
field: 'inputQuantity',
title: '投入数量',
width: 100,
},
{
field: 'outputQuantity',
title: '产出数量',
width: 100,
},
{
field: 'unqualifiedQuantity',
title: '不良品数量',
width: 100,
},
{
field: 'workstationCode',
title: '工位编码',
width: 120,
},
{
field: 'workstationName',
title: '工位名称',
minWidth: 120,
},
{
field: 'nickname',
title: '操作人',
width: 100,
},
...(editable
? [
{
title: '操作',
width: 160,
fixed: 'right',
slots: { default: 'actions' },
} as const,
]
: []),
];
}
/** 工序记录新增/修改的表单 */
export function useProcessFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'sort',
label: '序号',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 0,
placeholder: '请输入序号',
precision: 0,
},
},
{
fieldName: 'processId',
label: '工序',
component: markRaw(ProProcessSelect),
componentProps: {
placeholder: '请选择工序',
},
},
{
fieldName: 'inputTime',
label: '进入工序时间',
component: 'DatePicker',
componentProps: {
class: '!w-full',
placeholder: '请选择进入工序时间',
type: 'datetime',
valueFormat: 'x',
},
},
{
fieldName: 'outputTime',
label: '出工序时间',
component: 'DatePicker',
componentProps: {
class: '!w-full',
placeholder: '请选择出工序时间',
type: 'datetime',
valueFormat: 'x',
},
},
{
fieldName: 'inputQuantity',
label: '投入数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 0,
placeholder: '请输入投入数量',
precision: 2,
},
},
{
fieldName: 'outputQuantity',
label: '产出数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 0,
placeholder: '请输入产出数量',
precision: 2,
},
},
{
fieldName: 'unqualifiedQuantity',
label: '不合格数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 0,
placeholder: '请输入不合格数量',
precision: 2,
},
},
{
fieldName: 'workstationId',
label: '工位',
component: markRaw(MdWorkstationSelect),
componentProps: {
placeholder: '请选择工位',
},
},
{
fieldName: 'userId',
label: '操作人',
component: markRaw(UserSelect),
componentProps: {
placeholder: '请选择操作人',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 流转卡选择弹窗的搜索表单 */
export function useCardSelectGridFormSchema(): VbenFormSchema[] {

View File

@ -1 +1,2 @@
export { default as ProWorkOrderSelectDialog } from './pro-work-order-select-dialog.vue';
export { default as ProWorkOrderSelect } from './pro-work-order-select.vue';

View File

@ -1,20 +1,16 @@
<script lang="ts" setup>
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
import { computed, onMounted, ref, watch } from 'vue';
import { computed, ref, useAttrs, watch } from 'vue';
import { ElOption, ElSelect, ElTag, ElTooltip } from 'element-plus';
import { CircleX, Search } from '@vben/icons';
import { getWorkOrder, getWorkOrderPage } from '#/api/mes/pro/workorder';
import { ElInput, ElTooltip } from 'element-plus';
import { getWorkOrder } from '#/api/mes/pro/workorder';
import ProWorkOrderSelectDialog from './pro-work-order-select-dialog.vue';
/**
* MES 生产工单选择器轻量版
*
* 当前用于安灯记录等只需要单选工单 ID 的业务页面
* - 默认按 `status` 过滤拉取首页 100 条工单作为下拉
* - 编辑回显走 `getWorkOrder(id)`
* - 后续 `mes/pro/workorder` 完整迁移后可替换为带弹窗的复杂选择器
*/
defineOptions({ name: 'ProWorkOrderSelect', inheritAttrs: false });
const props = withDefaults(
@ -22,7 +18,6 @@ const props = withDefaults(
clearable?: boolean;
disabled?: boolean;
modelValue?: number;
pageSize?: number;
placeholder?: string;
status?: number;
type?: number;
@ -31,7 +26,6 @@ const props = withDefaults(
clearable: true,
disabled: false,
modelValue: undefined,
pageSize: 100,
placeholder: '请选择工单',
status: undefined,
type: undefined,
@ -43,108 +37,102 @@ const emit = defineEmits<{
'update:modelValue': [value: number | undefined];
}>();
const allList = ref<MesProWorkOrderApi.WorkOrder[]>([]);
const filteredList = ref<MesProWorkOrderApi.WorkOrder[]>([]);
const selectedItem = ref<MesProWorkOrderApi.WorkOrder>();
const attrs = useAttrs(); //
const dialogRef = ref<InstanceType<typeof ProWorkOrderSelectDialog>>(); //
const hovering = ref(false); //
const selectedItem = ref<MesProWorkOrderApi.WorkOrder>(); //
const selectValue = computed({
get: () => props.modelValue,
set: (value: number | undefined) => {
emit('update:modelValue', value);
},
});
const displayLabel = computed(() => selectedItem.value?.code ?? ''); //
const showClear = computed(() => //
props.clearable &&
!props.disabled &&
hovering.value &&
props.modelValue != null,
);
function handleFilter(query: string) {
if (!query) {
filteredList.value = allList.value;
return;
}
const keyword = query.toLowerCase();
filteredList.value = allList.value.filter(
(item) =>
item.code?.toLowerCase().includes(keyword) ||
item.name?.toLowerCase().includes(keyword),
);
}
/** 同步选中工单详情,未在列表内时单独拉取 */
async function syncSelectedItem(value: number | undefined) {
if (value === undefined) {
/** 根据编号单条查询工单信息(用于编辑回显) */
async function resolveItemById(id: number | undefined) {
if (id == null) {
selectedItem.value = undefined;
return;
}
const found = allList.value.find((item) => item.id === value);
if (found) {
selectedItem.value = found;
if (selectedItem.value?.id === id) {
return;
}
try {
selectedItem.value = await getWorkOrder(value);
} catch (error) {
console.error('[ProWorkOrderSelect] resolveItemById failed:', error);
selectedItem.value = await getWorkOrder(id);
}
watch(() => props.modelValue, resolveItemById, { immediate: true });
/** 清空已选工单 */
function clearSelected() {
selectedItem.value = undefined;
emit('update:modelValue', undefined);
emit('change', undefined);
}
/** 打开工单选择弹窗 */
function handleClick(event: MouseEvent) {
if (props.disabled) {
return;
}
const target = event.target as HTMLElement;
if (showClear.value && target.closest('.el-input__suffix')) {
event.stopPropagation();
clearSelected();
return;
}
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
dialogRef.value?.open(selectedIds, { multiple: false });
}
/** 除 v-model 外,额外抛出完整工单对象给业务表单使用 */
function handleChange(value: number | undefined) {
syncSelectedItem(value);
emit('change', selectedItem.value);
/** 弹窗选中回调 */
function handleSelected(rows: MesProWorkOrderApi.WorkOrder[]) {
const item = rows[0];
if (!item) {
return;
}
selectedItem.value = item;
emit('update:modelValue', item.id);
emit('change', item);
}
watch(
() => props.modelValue,
(value) => {
syncSelectedItem(value);
},
);
onMounted(async () => {
const data = await getWorkOrderPage({
pageNo: 1,
pageSize: props.pageSize,
status: props.status,
type: props.type,
});
allList.value = data.list ?? [];
filteredList.value = allList.value;
syncSelectedItem(props.modelValue);
});
</script>
<template>
<ElTooltip :disabled="!selectedItem" placement="top" :show-after="500">
<template #content>
<div v-if="selectedItem" class="leading-6">
<div>编码{{ selectedItem.code || '-' }}</div>
<div>名称{{ selectedItem.name || '-' }}</div>
<div>产品{{ selectedItem.productName || '-' }}</div>
<div>数量{{ selectedItem.quantity ?? '-' }}</div>
</div>
</template>
<ElSelect
v-bind="$attrs"
v-model="selectValue"
:clearable="clearable"
:disabled="disabled"
:filter-method="handleFilter"
:placeholder="placeholder"
class="w-full"
filterable
@change="handleChange"
>
<ElOption
v-for="item in filteredList"
:key="item.id"
:label="item.code"
:value="item.id!"
>
<div class="flex items-center gap-2">
<span>{{ item.code }}</span>
<ElTag v-if="item.name" size="small" type="info">
{{ item.name }}
</ElTag>
<div
v-bind="attrs"
class="w-full"
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
@click="handleClick"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<ElTooltip :disabled="!selectedItem" placement="top" :show-after="500">
<template #content>
<div v-if="selectedItem" class="leading-6">
<div>编码{{ selectedItem.code || '-' }}</div>
<div>名称{{ selectedItem.name || '-' }}</div>
<div>产品{{ selectedItem.productName || '-' }}</div>
<div>数量{{ selectedItem.quantity ?? '-' }}</div>
</div>
</ElOption>
</ElSelect>
</ElTooltip>
</template>
<ElInput
:disabled="disabled"
:model-value="displayLabel"
:placeholder="placeholder"
readonly
>
<template #suffix>
<CircleX v-if="showClear" class="size-4" />
<Search v-else class="size-4" />
</template>
</ElInput>
</ElTooltip>
</div>
<ProWorkOrderSelectDialog
ref="dialogRef"
:status="status"
:type="type"
@selected="handleSelected"
/>
</template>

View File

@ -39,23 +39,18 @@ const canSubmit = computed(() => // 是否可提交
formData.value?.status === MesWmOutsourceIssueStatusEnum.PREPARE,
);
const getTitle = computed(() => {
switch (formType.value) {
case 'detail': {
return $t('ui.actionTitle.view', ['外协发料单']);
}
case 'finish': {
return '执行领出';
}
case 'stock': {
return '执行拣货';
}
case 'update': {
return $t('ui.actionTitle.edit', ['外协发料单']);
}
default: {
return $t('ui.actionTitle.create', ['外协发料单']);
}
if (formType.value === 'detail') {
return $t('ui.actionTitle.view', ['外协发料单']);
}
if (formType.value === 'stock') {
return '执行拣货';
}
if (formType.value === 'finish') {
return '执行领出';
}
return formType.value === 'update'
? $t('ui.actionTitle.edit', ['外协发料单'])
: $t('ui.actionTitle.create', ['外协发料单']);
});
const [Form, formApi] = useVbenForm({

View File

@ -27,9 +27,9 @@ const props = defineProps<{
issueId: number;
}>();
const isEditable = computed(() =>
const isEditable = computed(() => //
['create', 'update'].includes(props.formType),
); //
);
const isStock = computed(() => props.formType === 'stock'); //
const detailMap = reactive<
Record<number, MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]>

View File

@ -22,9 +22,9 @@ const emit = defineEmits<{
refresh: [];
}>();
const isEditable = computed(
() => ['create', 'stock', 'update'].includes(props.formType),
); //
const isEditable = computed(() => //
['create', 'stock', 'update'].includes(props.formType),
);
/** 添加收货明细 */
function handleCreate() {

View File

@ -38,23 +38,18 @@ const canSubmit = computed(() => // 是否可提交
formData.value?.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
);
const getTitle = computed(() => {
switch (formType.value) {
case 'detail': {
return $t('ui.actionTitle.view', ['外协入库单']);
}
case 'finish': {
return '完成入库';
}
case 'stock': {
return '执行上架';
}
case 'update': {
return $t('ui.actionTitle.edit', ['外协入库单']);
}
default: {
return $t('ui.actionTitle.create', ['外协入库单']);
}
if (formType.value === 'detail') {
return $t('ui.actionTitle.view', ['外协入库单']);
}
if (formType.value === 'stock') {
return '执行上架';
}
if (formType.value === 'finish') {
return '完成入库';
}
return formType.value === 'update'
? $t('ui.actionTitle.edit', ['外协入库单'])
: $t('ui.actionTitle.create', ['外协入库单']);
});
const [Form, formApi] = useVbenForm({

View File

@ -28,9 +28,9 @@ const props = defineProps<{
receiptId: number;
}>();
const isEditable = computed(() =>
const isEditable = computed(() => //
['create', 'update'].includes(props.formType),
); //
);
const isStock = computed(() => props.formType === 'stock'); //
const detailMap = reactive<
Record<number, MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail[]>