feat(mes):优化 review - 工序定义(pro_process)、工艺路线(pro_route)

pull/349/head
YunaiV 2026-05-25 18:10:29 +08:00
parent 4949ed1c07
commit adb57ed364
19 changed files with 359 additions and 385 deletions

View File

@ -40,7 +40,6 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
{ {
type: 'default', type: 'default',
onClick: async () => { onClick: async () => {
// TODO @AI需要 try catch 么?感觉直接去掉也可以?
try { try {
const code = await generateAutoCode( const code = await generateAutoCode(
MesAutoCodeRuleCode.PRO_PROCESS_CODE, MesAutoCodeRuleCode.PRO_PROCESS_CODE,

View File

@ -19,10 +19,11 @@ import { useContentGridColumns } from '../data';
import ContentForm from './content-form.vue'; import ContentForm from './content-form.vue';
const props = defineProps<{ const props = defineProps<{
formMode: 'create' | 'detail' | 'update';
processId: number; processId: number;
readonly?: boolean;
}>(); }>();
const isEditable = ref(props.formMode !== 'detail'); //
const list = ref<MesProProcessContentApi.ProcessContent[]>([]); const list = ref<MesProProcessContentApi.ProcessContent[]>([]);
const [ContentFormModal, contentFormModalApi] = useVbenModal({ const [ContentFormModal, contentFormModalApi] = useVbenModal({
@ -60,11 +61,7 @@ async function getList() {
/** 新增工序步骤 */ /** 新增工序步骤 */
function handleCreate() { function handleCreate() {
// TODO @AI max || 0 const maxSort = Math.max(0, ...list.value.map((item) => item.sort || 0));
const maxSort =
list.value.length > 0
? Math.max(...list.value.map((item) => item.sort || 0))
: 0;
contentFormModalApi contentFormModalApi
.setData({ maxSort, processId: props.processId }) .setData({ maxSort, processId: props.processId })
.open(); .open();
@ -95,7 +92,7 @@ watch(
<template> <template>
<ContentFormModal @success="getList" /> <ContentFormModal @success="getList" />
<div v-if="!readonly" class="mb-3 flex items-center justify-start"> <div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction <TableAction
:actions="[ :actions="[
{ {
@ -113,14 +110,14 @@ watch(
{ {
label: $t('common.edit'), label: $t('common.edit'),
type: 'link', type: 'link',
ifShow: () => !readonly, ifShow: () => isEditable,
onClick: handleEdit.bind(null, row), onClick: handleEdit.bind(null, row),
}, },
{ {
label: $t('common.delete'), label: $t('common.delete'),
type: 'link', type: 'link',
danger: true, danger: true,
ifShow: () => !readonly, ifShow: () => isEditable,
popConfirm: { popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', ['工序步骤']), title: $t('ui.actionMessage.deleteConfirm', ['工序步骤']),
confirm: handleDelete.bind(null, row), confirm: handleDelete.bind(null, row),

View File

@ -22,7 +22,7 @@ type FormMode = 'create' | 'detail' | 'update';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formMode = ref<FormMode>('create'); // const formMode = ref<FormMode>('create'); //
const formData = ref<MesProProcessApi.Process>(); // / const formData = ref<MesProProcessApi.Process>();
const isDetail = computed(() => formMode.value === 'detail'); // const isDetail = computed(() => formMode.value === 'detail'); //
const getTitle = computed(() => { const getTitle = computed(() => {
@ -110,7 +110,7 @@ const processId = computed(() => formData.value?.id);
<template v-if="processId"> <template v-if="processId">
<Divider class="!my-3" orientation="left">操作步骤</Divider> <Divider class="!my-3" orientation="left">操作步骤</Divider>
<div class="mx-4"> <div class="mx-4">
<ContentList :process-id="processId" :readonly="isDetail" /> <ContentList :form-mode="formMode" :process-id="processId" />
</div> </div>
</template> </template>
</Modal> </Modal>

View File

@ -17,7 +17,7 @@ import { $t } from '#/locales';
import { useRouteProductBomFormSchema } from '../data'; import { useRouteProductBomFormSchema } from '../data';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<MesProRouteProductBomApi.RouteProductBom>(); // BOM const formData = ref<MesProRouteProductBomApi.RouteProductBom>();
const productId = ref<number>(0); // BOM BOM const productId = ref<number>(0); // BOM BOM
const getTitle = computed(() => const getTitle = computed(() =>
formData.value?.id formData.value?.id

View File

@ -8,11 +8,7 @@ import { useVbenModal } from '@vben/common-ui';
import { message, TabPane, Tabs } from 'ant-design-vue'; import { message, TabPane, Tabs } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { import { createRoute, getRoute, updateRoute } from '#/api/mes/pro/route';
createRoute,
getRoute,
updateRoute,
} from '#/api/mes/pro/route';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
@ -22,11 +18,11 @@ import ProductList from './product-list.vue';
type FormMode = 'create' | 'detail' | 'update'; type FormMode = 'create' | 'detail' | 'update';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formMode = ref<FormMode>('create'); const formMode = ref<FormMode>('create'); //
const subTab = ref('process'); const subTab = ref('process'); // Tab
const formData = ref<MesProRouteApi.Route>(); const formData = ref<MesProRouteApi.Route>();
const isDetail = computed(() => formMode.value === 'detail'); const isDetail = computed(() => formMode.value === 'detail'); //
const getTitle = computed(() => { const getTitle = computed(() => {
if (formMode.value === 'detail') { if (formMode.value === 'detail') {
return $t('ui.actionTitle.detail', ['工艺路线']); return $t('ui.actionTitle.detail', ['工艺路线']);
@ -48,10 +44,9 @@ const [Form, formApi] = useVbenForm({
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
}); });
// TODO @AI /** 表单 schema 需要 formApi 引用(生成编码按钮),所以通过 setState 设置 schema */
formApi.setState({ schema: useFormSchema(formApi) }); formApi.setState({ schema: useFormSchema(formApi) });
// TODO @AI
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
if (isDetail.value) { if (isDetail.value) {
@ -63,9 +58,11 @@ const [Modal, modalApi] = useVbenModal({
return; return;
} }
modalApi.lock(); modalApi.lock();
//
const data = (await formApi.getValues()) as MesProRouteApi.Route; const data = (await formApi.getValues()) as MesProRouteApi.Route;
try { try {
if (formMode.value === 'create') { if (formMode.value === 'create') {
// 便
const id = await createRoute(data); const id = await createRoute(data);
formData.value = { ...data, id }; formData.value = { ...data, id };
await formApi.setFieldValue('id', id); await formApi.setFieldValue('id', id);
@ -87,6 +84,7 @@ const [Modal, modalApi] = useVbenModal({
} }
await formApi.resetForm(); await formApi.resetForm();
subTab.value = 'process'; subTab.value = 'process';
//
const data = modalApi.getData<{ id?: number; type?: FormMode }>(); const data = modalApi.getData<{ id?: number; type?: FormMode }>();
formMode.value = data?.type ?? 'create'; formMode.value = data?.type ?? 'create';
formApi.setDisabled(formMode.value === 'detail'); formApi.setDisabled(formMode.value === 'detail');
@ -97,6 +95,7 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock(); modalApi.lock();
try { try {
formData.value = await getRoute(data.id); formData.value = await getRoute(data.id);
// values
await formApi.setValues(formData.value); await formApi.setValues(formData.value);
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
@ -108,6 +107,7 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal :title="getTitle" class="w-4/5"> <Modal :title="getTitle" class="w-4/5">
<Form class="mx-4" /> <Form class="mx-4" />
<!-- 编辑/详情模式下展示子表 Tab新增模式下隐藏 -->
<Tabs <Tabs
v-if="formMode !== 'create' && formData?.id" v-if="formMode !== 'create' && formData?.id"
v-model:active-key="subTab" v-model:active-key="subTab"

View File

@ -12,6 +12,7 @@ import { useVbenForm } from '#/adapter/form';
import { getProcessSimpleList } from '#/api/mes/pro/process'; import { getProcessSimpleList } from '#/api/mes/pro/process';
import { import {
createRouteProcess, createRouteProcess,
getRouteProcess,
updateRouteProcess, updateRouteProcess,
} from '#/api/mes/pro/route/process'; } from '#/api/mes/pro/route/process';
import { $t } from '#/locales'; import { $t } from '#/locales';
@ -48,7 +49,6 @@ async function loadSchema(): Promise<VbenFormSchema[]> {
return useRouteProcessFormSchema(options); return useRouteProcessFormSchema(options);
} }
// TODO @AI
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
const { valid } = await formApi.validate(); const { valid } = await formApi.validate();
@ -56,12 +56,14 @@ const [Modal, modalApi] = useVbenModal({
return; return;
} }
modalApi.lock(); modalApi.lock();
//
const data = const data =
(await formApi.getValues()) as MesProRouteProcessApi.RouteProcess; (await formApi.getValues()) as MesProRouteProcessApi.RouteProcess;
try { try {
await (formData.value?.id await (formData.value?.id
? updateRouteProcess(data) ? updateRouteProcess(data)
: createRouteProcess(data)); : createRouteProcess(data));
//
await modalApi.close(); await modalApi.close();
emit('success'); emit('success');
message.success($t('ui.actionMessage.operationSuccess')); message.success($t('ui.actionMessage.operationSuccess'));
@ -76,22 +78,24 @@ const [Modal, modalApi] = useVbenModal({
} }
modalApi.lock(); modalApi.lock();
try { try {
// schema
const schema = await loadSchema(); const schema = await loadSchema();
formApi.setState({ schema }); formApi.setState({ schema });
await formApi.resetForm(); await formApi.resetForm();
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
} }
//
const data = modalApi.getData<{ const data = modalApi.getData<{
id?: number; id?: number;
maxSort?: number; maxSort?: number;
routeId: number; routeId: number;
row?: MesProRouteProcessApi.RouteProcess;
}>(); }>();
if (!data) { if (!data) {
return; return;
} }
if (!data.id) { if (!data.id) {
// = maxSort + 1
await formApi.setValues({ await formApi.setValues({
colorCode: '#00AEF3', colorCode: '#00AEF3',
routeId: data.routeId, routeId: data.routeId,
@ -99,9 +103,13 @@ const [Modal, modalApi] = useVbenModal({
}); });
return; return;
} }
if (data.row) { modalApi.lock();
formData.value = data.row; try {
await formApi.setValues(data.row); formData.value = await getRouteProcess(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
} }
}, },
}); });

View File

@ -23,8 +23,8 @@ const props = defineProps<{
routeId: number; routeId: number;
}>(); }>();
const isEditable = ref(props.formMode !== 'detail'); const isEditable = ref(props.formMode !== 'detail'); //
const list = ref<MesProRouteProcessApi.RouteProcess[]>([]); const list = ref<MesProRouteProcessApi.RouteProcess[]>([]); // 线
const [ProcessFormModal, processFormModalApi] = useVbenModal({ const [ProcessFormModal, processFormModalApi] = useVbenModal({
connectedComponent: ProcessForm, connectedComponent: ProcessForm,
@ -45,7 +45,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions<MesProRouteProcessApi.RouteProcess>, } as VxeTableGridOptions<MesProRouteProcessApi.RouteProcess>,
}); });
// TODO @AI /** 加载工艺路线工序列表 */
async function getList() { async function getList() {
gridApi.setLoading(true); gridApi.setLoading(true);
try { try {
@ -56,21 +56,18 @@ async function getList() {
} }
} }
// TODO @AI /** 新增工艺路线工序 */
function handleCreate() { function handleCreate() {
const maxSort = const maxSort = Math.max(0, ...list.value.map((item) => item.sort || 0));
list.value.length > 0
? Math.max(...list.value.map((item) => item.sort || 0))
: 0;
processFormModalApi.setData({ maxSort, routeId: props.routeId }).open(); processFormModalApi.setData({ maxSort, routeId: props.routeId }).open();
} }
// TODO @AI /** 编辑工艺路线工序 */
function handleEdit(row: MesProRouteProcessApi.RouteProcess) { function handleEdit(row: MesProRouteProcessApi.RouteProcess) {
processFormModalApi.setData({ id: row.id, routeId: props.routeId, row }).open(); processFormModalApi.setData({ id: row.id, routeId: props.routeId }).open();
} }
// TODO @AI /** 删除工艺路线工序 */
async function handleDelete(row: MesProRouteProcessApi.RouteProcess) { async function handleDelete(row: MesProRouteProcessApi.RouteProcess) {
await deleteRouteProcess(row.id!); await deleteRouteProcess(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', ['工艺路线工序'])); message.success($t('ui.actionMessage.deleteSuccess', ['工艺路线工序']));

View File

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process';
import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom'; import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
@ -25,12 +26,9 @@ const props = defineProps<{
routeId: number; routeId: number;
}>(); }>();
// TODO @AIconst vue agents md const processList = ref<MesProRouteProcessApi.RouteProcess[]>([]); // Tab
const processOptions = ref< const activeProcessId = ref<number>(); // Tab
Array<{ processId: number; processName?: string }> const list = ref<MesProRouteProductBomApi.RouteProductBom[]>([]); // BOM
>([]); // TODO @AIvue
const activeProcessId = ref<string>(''); // TODO @AI number vue3 + ep
const list = ref<MesProRouteProductBomApi.RouteProductBom[]>([]);
const [BomFormModal, bomFormModalApi] = useVbenModal({ const [BomFormModal, bomFormModalApi] = useVbenModal({
connectedComponent: BomForm, connectedComponent: BomForm,
@ -53,16 +51,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
/** 加载路线下的工序列表,用于工序 Tab */ /** 加载路线下的工序列表,用于工序 Tab */
async function loadProcessList() { async function loadProcessList() {
const data = await getRouteProcessListByRoute(props.routeId); processList.value = (await getRouteProcessListByRoute(props.routeId)) || [];
processOptions.value = (data || []).map((item) => ({ if (processList.value.length > 0) {
processId: item.processId!, activeProcessId.value = processList.value[0]!.processId;
processName: item.processName,
}));
if (processOptions.value.length > 0) {
activeProcessId.value = String(processOptions.value[0]!.processId);
await getList(); await getList();
} else { } else {
activeProcessId.value = ''; activeProcessId.value = undefined;
list.value = []; list.value = [];
gridApi.setGridOptions({ data: list.value }); gridApi.setGridOptions({ data: list.value });
} }
@ -76,7 +70,7 @@ async function getList() {
gridApi.setLoading(true); gridApi.setLoading(true);
try { try {
list.value = await getRouteProductBomList({ list.value = await getRouteProductBomList({
processId: Number(activeProcessId.value), processId: activeProcessId.value,
productId: props.productId, productId: props.productId,
routeId: props.routeId, routeId: props.routeId,
}); });
@ -94,7 +88,7 @@ function handleCreate() {
} }
bomFormModalApi bomFormModalApi
.setData({ .setData({
processId: Number(activeProcessId.value), processId: activeProcessId.value,
productId: props.productId, productId: props.productId,
routeId: props.routeId, routeId: props.routeId,
}) })
@ -105,10 +99,10 @@ function handleCreate() {
function handleEdit(row: MesProRouteProductBomApi.RouteProductBom) { function handleEdit(row: MesProRouteProductBomApi.RouteProductBom) {
bomFormModalApi bomFormModalApi
.setData({ .setData({
processId: Number(activeProcessId.value), id: row.id,
processId: activeProcessId.value!,
productId: props.productId, productId: props.productId,
routeId: props.routeId, routeId: props.routeId,
row,
}) })
.open(); .open();
} }
@ -135,8 +129,8 @@ watch(
<BomFormModal @success="getList" /> <BomFormModal @success="getList" />
<Tabs v-model:active-key="activeProcessId" @change="getList"> <Tabs v-model:active-key="activeProcessId" @change="getList">
<TabPane <TabPane
v-for="item in processOptions" v-for="item in processList"
:key="String(item.processId)" :key="item.processId"
:tab="item.processName" :tab="item.processName"
/> />
</Tabs> </Tabs>

View File

@ -10,6 +10,7 @@ import { Divider, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { import {
createRouteProduct, createRouteProduct,
getRouteProduct,
updateRouteProduct, updateRouteProduct,
} from '#/api/mes/pro/route/product'; } from '#/api/mes/pro/route/product';
import { $t } from '#/locales'; import { $t } from '#/locales';
@ -18,7 +19,7 @@ import { useRouteProductFormSchema } from '../data';
import ProductBomList from './product-bom-list.vue'; import ProductBomList from './product-bom-list.vue';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<MesProRouteProductApi.RouteProduct>(); // / const formData = ref<MesProRouteProductApi.RouteProduct>();
const getTitle = computed(() => const getTitle = computed(() =>
formData.value?.id formData.value?.id
? $t('ui.actionTitle.edit', ['工艺路线产品']) ? $t('ui.actionTitle.edit', ['工艺路线产品'])
@ -51,7 +52,6 @@ const [Form, formApi] = useVbenForm({
}); });
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
// TODO @AI
async onConfirm() { async onConfirm() {
const { valid } = await formApi.validate(); const { valid } = await formApi.validate();
if (!valid) { if (!valid) {
@ -64,7 +64,10 @@ const [Modal, modalApi] = useVbenModal({
try { try {
if (formData.value?.id) { if (formData.value?.id) {
await updateRouteProduct(data); await updateRouteProduct(data);
// formData BOM itemId
formData.value = { ...formData.value, ...data };
} else { } else {
// 便 BOM
const id = await createRouteProduct(data); const id = await createRouteProduct(data);
formData.value = { ...data, id }; formData.value = { ...data, id };
await formApi.setFieldValue('id', id); await formApi.setFieldValue('id', id);
@ -85,18 +88,22 @@ const [Modal, modalApi] = useVbenModal({
const data = modalApi.getData<{ const data = modalApi.getData<{
id?: number; id?: number;
routeId: number; routeId: number;
row?: MesProRouteProductApi.RouteProduct;
}>(); }>();
if (!data) { if (!data) {
return; return;
} }
if (data.row) { if (!data.id) {
formData.value = data.row; await formApi.setValues({ routeId: data.routeId });
// values
await formApi.setValues(data.row);
return; return;
} }
await formApi.setValues({ routeId: data.routeId }); modalApi.lock();
try {
formData.value = await getRouteProduct(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
}, },
}); });
</script> </script>
@ -104,7 +111,7 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal :title="getTitle" class="w-3/5"> <Modal :title="getTitle" class="w-3/5">
<Form class="mx-4" /> <Form class="mx-4" />
<!-- 编辑/详情模式下展示产品 BOM 子表新增模式下隐藏 --> <!-- 编辑模式下展示产品 BOM 子表新增模式下隐藏 -->
<template v-if="formData?.id && formData?.itemId"> <template v-if="formData?.id && formData?.itemId">
<Divider class="!my-3" orientation="left">产品 BOM 配置</Divider> <Divider class="!my-3" orientation="left">产品 BOM 配置</Divider>
<div class="mx-4"> <div class="mx-4">

View File

@ -25,15 +25,14 @@ const props = defineProps<{
routeId: number; routeId: number;
}>(); }>();
const isEditable = ref(props.formMode !== 'detail'); const isEditable = ref(props.formMode !== 'detail'); //
const list = ref<MesProRouteProductApi.RouteProduct[]>([]); const list = ref<MesProRouteProductApi.RouteProduct[]>([]); // 线
const [ProductFormModal, productFormModalApi] = useVbenModal({ const [ProductFormModal, productFormModalApi] = useVbenModal({
connectedComponent: ProductForm, connectedComponent: ProductForm,
destroyOnClose: true, destroyOnClose: true,
}); });
// TODO @AIps
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
autoResize: true, autoResize: true,
@ -48,7 +47,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions<MesProRouteProductApi.RouteProduct>, } as VxeTableGridOptions<MesProRouteProductApi.RouteProduct>,
}); });
// TODO @AI /** 加载工艺路线产品列表 */
async function getList() { async function getList() {
gridApi.setLoading(true); gridApi.setLoading(true);
try { try {
@ -59,17 +58,17 @@ async function getList() {
} }
} }
// TODO @AI /** 新增工艺路线产品 */
function handleCreate() { function handleCreate() {
productFormModalApi.setData({ routeId: props.routeId }).open(); productFormModalApi.setData({ routeId: props.routeId }).open();
} }
// TODO @AI /** 编辑工艺路线产品 */
function handleEdit(row: MesProRouteProductApi.RouteProduct) { function handleEdit(row: MesProRouteProductApi.RouteProduct) {
productFormModalApi.setData({ id: row.id, routeId: props.routeId, row }).open(); productFormModalApi.setData({ id: row.id, routeId: props.routeId }).open();
} }
// TODO @AI /** 删除工艺路线产品 */
async function handleDelete(row: MesProRouteProductApi.RouteProduct) { async function handleDelete(row: MesProRouteProductApi.RouteProduct) {
await deleteRouteProduct(row.id!); await deleteRouteProduct(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', ['工艺路线产品'])); message.success($t('ui.actionMessage.deleteSuccess', ['工艺路线产品']));

View File

@ -19,10 +19,11 @@ import { useContentGridColumns } from '../data';
import ContentForm from './content-form.vue'; import ContentForm from './content-form.vue';
const props = defineProps<{ const props = defineProps<{
formMode: 'create' | 'detail' | 'update';
processId: number; processId: number;
readonly?: boolean;
}>(); }>();
const isEditable = ref(props.formMode !== 'detail'); //
const list = ref<MesProProcessContentApi.ProcessContent[]>([]); const list = ref<MesProProcessContentApi.ProcessContent[]>([]);
const [ContentFormModal, contentFormModalApi] = useVbenModal({ const [ContentFormModal, contentFormModalApi] = useVbenModal({
@ -60,10 +61,7 @@ async function getList() {
/** 新增工序步骤 */ /** 新增工序步骤 */
function handleCreate() { function handleCreate() {
const maxSort = const maxSort = Math.max(0, ...list.value.map((item) => item.sort || 0));
list.value.length > 0
? Math.max(...list.value.map((item) => item.sort || 0))
: 0;
contentFormModalApi contentFormModalApi
.setData({ maxSort, processId: props.processId }) .setData({ maxSort, processId: props.processId })
.open(); .open();
@ -94,7 +92,7 @@ watch(
<template> <template>
<ContentFormModal @success="getList" /> <ContentFormModal @success="getList" />
<div v-if="!readonly" class="mb-3 flex items-center justify-start"> <div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction <TableAction
:actions="[ :actions="[
{ {
@ -113,14 +111,14 @@ watch(
label: $t('common.edit'), label: $t('common.edit'),
type: 'primary', type: 'primary',
link: true, link: true,
ifShow: () => !readonly, ifShow: () => isEditable,
onClick: handleEdit.bind(null, row), onClick: handleEdit.bind(null, row),
}, },
{ {
label: $t('common.delete'), label: $t('common.delete'),
type: 'danger', type: 'danger',
link: true, link: true,
ifShow: () => !readonly, ifShow: () => isEditable,
popConfirm: { popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', ['工序步骤']), title: $t('ui.actionMessage.deleteConfirm', ['工序步骤']),
confirm: handleDelete.bind(null, row), confirm: handleDelete.bind(null, row),

View File

@ -22,7 +22,7 @@ type FormMode = 'create' | 'detail' | 'update';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formMode = ref<FormMode>('create'); // const formMode = ref<FormMode>('create'); //
const formData = ref<MesProProcessApi.Process>(); // / const formData = ref<MesProProcessApi.Process>();
const isDetail = computed(() => formMode.value === 'detail'); // const isDetail = computed(() => formMode.value === 'detail'); //
const getTitle = computed(() => { const getTitle = computed(() => {
@ -110,7 +110,7 @@ const processId = computed(() => formData.value?.id);
<template v-if="processId"> <template v-if="processId">
<ElDivider class="!my-3" content-position="left">操作步骤</ElDivider> <ElDivider class="!my-3" content-position="left">操作步骤</ElDivider>
<div class="mx-4"> <div class="mx-4">
<ContentList :process-id="processId" :readonly="isDetail" /> <ContentList :form-mode="formMode" :process-id="processId" />
</div> </div>
</template> </template>
</Modal> </Modal>

View File

@ -14,6 +14,10 @@ import { ElButton } from 'element-plus';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { generateAutoCode } from '#/api/mes/md/autocode/record'; import { generateAutoCode } from '#/api/mes/md/autocode/record';
import {
MdItemSelect,
MdProductBomSelect,
} from '#/views/mes/md/item/components';
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants'; import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
/** 工艺路线表单 */ /** 工艺路线表单 */
@ -348,3 +352,134 @@ export function useRouteProductBomGridColumns(): VxeTableGridOptions<MesProRoute
}, },
]; ];
} }
/** 工艺路线产品表单 */
export function useRouteProductFormSchema(
onItemChange?: (item: any) => void,
): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
{
fieldName: 'routeId',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
// 产品物料使用业务自定义选择器change 回填编码/名称/规格/单位
{
fieldName: 'itemId',
label: '产品',
component: MdItemSelect as any,
componentProps: {
onChange: onItemChange,
},
formItemClass: 'col-span-2',
rules: 'selectRequired',
},
{
fieldName: 'quantity',
label: '生产数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 1,
precision: 0,
},
rules: z.number().default(1),
},
{
fieldName: 'productionTime',
label: '生产用时',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 0,
precision: 2,
},
rules: z.number().default(1),
},
{
fieldName: 'timeUnitType',
label: '时间单位',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.MES_TIME_UNIT_TYPE),
placeholder: '请选择',
},
rules: z.string().default('MINUTE'),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: { maxLength: 250, placeholder: '请输入备注', rows: 2 },
},
];
}
/** 工艺路线产品 BOM 表单 */
export function useRouteProductBomFormSchema(
itemId: () => number,
onBomChange?: (bom: any) => void,
): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
{
fieldName: 'routeId',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
{
fieldName: 'processId',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
{
fieldName: 'productId',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
// BOM 物料:依赖产品物料,使用业务自定义选择器
{
fieldName: 'itemId',
label: 'BOM 物料',
component: MdProductBomSelect as any,
componentProps: () => ({
itemId: itemId(),
onChange: onBomChange,
placeholder: '请选择 BOM 物料',
}),
rules: 'selectRequired',
},
{
fieldName: 'quantity',
label: '用料比例',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 0,
precision: 2,
},
rules: z.number().default(1),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: { maxLength: 250, placeholder: '请输入备注', rows: 2 },
},
];
}

View File

@ -18,11 +18,11 @@ import ProductList from './product-list.vue';
type FormMode = 'create' | 'detail' | 'update'; type FormMode = 'create' | 'detail' | 'update';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formMode = ref<FormMode>('create'); const formMode = ref<FormMode>('create'); //
const subTab = ref('process'); const subTab = ref('process'); // Tab
const formData = ref<MesProRouteApi.Route>(); const formData = ref<MesProRouteApi.Route>();
const isDetail = computed(() => formMode.value === 'detail'); const isDetail = computed(() => formMode.value === 'detail'); //
const getTitle = computed(() => { const getTitle = computed(() => {
if (formMode.value === 'detail') { if (formMode.value === 'detail') {
return $t('ui.actionTitle.detail', ['工艺路线']); return $t('ui.actionTitle.detail', ['工艺路线']);
@ -44,6 +44,7 @@ const [Form, formApi] = useVbenForm({
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
}); });
/** 表单 schema 需要 formApi 引用(生成编码按钮),所以通过 setState 设置 schema */
formApi.setState({ schema: useFormSchema(formApi) }); formApi.setState({ schema: useFormSchema(formApi) });
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
@ -57,9 +58,11 @@ const [Modal, modalApi] = useVbenModal({
return; return;
} }
modalApi.lock(); modalApi.lock();
//
const data = (await formApi.getValues()) as MesProRouteApi.Route; const data = (await formApi.getValues()) as MesProRouteApi.Route;
try { try {
if (formMode.value === 'create') { if (formMode.value === 'create') {
// 便
const id = await createRoute(data); const id = await createRoute(data);
formData.value = { ...data, id }; formData.value = { ...data, id };
await formApi.setFieldValue('id', id); await formApi.setFieldValue('id', id);
@ -81,6 +84,7 @@ const [Modal, modalApi] = useVbenModal({
} }
await formApi.resetForm(); await formApi.resetForm();
subTab.value = 'process'; subTab.value = 'process';
//
const data = modalApi.getData<{ id?: number; type?: FormMode }>(); const data = modalApi.getData<{ id?: number; type?: FormMode }>();
formMode.value = data?.type ?? 'create'; formMode.value = data?.type ?? 'create';
formApi.setDisabled(formMode.value === 'detail'); formApi.setDisabled(formMode.value === 'detail');
@ -91,6 +95,7 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock(); modalApi.lock();
try { try {
formData.value = await getRoute(data.id); formData.value = await getRoute(data.id);
// values
await formApi.setValues(formData.value); await formApi.setValues(formData.value);
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
@ -102,6 +107,7 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal :title="getTitle" class="w-4/5"> <Modal :title="getTitle" class="w-4/5">
<Form class="mx-4" /> <Form class="mx-4" />
<!-- 编辑/详情模式下展示子表 Tab新增模式下隐藏 -->
<ElTabs <ElTabs
v-if="formMode !== 'create' && formData?.id" v-if="formMode !== 'create' && formData?.id"
v-model="subTab" v-model="subTab"

View File

@ -12,6 +12,7 @@ import { useVbenForm } from '#/adapter/form';
import { getProcessSimpleList } from '#/api/mes/pro/process'; import { getProcessSimpleList } from '#/api/mes/pro/process';
import { import {
createRouteProcess, createRouteProcess,
getRouteProcess,
updateRouteProcess, updateRouteProcess,
} from '#/api/mes/pro/route/process'; } from '#/api/mes/pro/route/process';
import { $t } from '#/locales'; import { $t } from '#/locales';
@ -38,6 +39,7 @@ const [Form, formApi] = useVbenForm({
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
}); });
/** 加载工序选项后再生成 schema避免下拉空选项 */
async function loadSchema(): Promise<VbenFormSchema[]> { async function loadSchema(): Promise<VbenFormSchema[]> {
const list = await getProcessSimpleList(); const list = await getProcessSimpleList();
const options = (list || []).map((item) => ({ const options = (list || []).map((item) => ({
@ -54,12 +56,14 @@ const [Modal, modalApi] = useVbenModal({
return; return;
} }
modalApi.lock(); modalApi.lock();
//
const data = const data =
(await formApi.getValues()) as MesProRouteProcessApi.RouteProcess; (await formApi.getValues()) as MesProRouteProcessApi.RouteProcess;
try { try {
await (formData.value?.id await (formData.value?.id
? updateRouteProcess(data) ? updateRouteProcess(data)
: createRouteProcess(data)); : createRouteProcess(data));
//
await modalApi.close(); await modalApi.close();
emit('success'); emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess')); ElMessage.success($t('ui.actionMessage.operationSuccess'));
@ -74,22 +78,24 @@ const [Modal, modalApi] = useVbenModal({
} }
modalApi.lock(); modalApi.lock();
try { try {
// schema
const schema = await loadSchema(); const schema = await loadSchema();
formApi.setState({ schema }); formApi.setState({ schema });
await formApi.resetForm(); await formApi.resetForm();
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
} }
//
const data = modalApi.getData<{ const data = modalApi.getData<{
id?: number; id?: number;
maxSort?: number; maxSort?: number;
routeId: number; routeId: number;
row?: MesProRouteProcessApi.RouteProcess;
}>(); }>();
if (!data) { if (!data) {
return; return;
} }
if (!data.id) { if (!data.id) {
// = maxSort + 1
await formApi.setValues({ await formApi.setValues({
colorCode: '#00AEF3', colorCode: '#00AEF3',
routeId: data.routeId, routeId: data.routeId,
@ -97,9 +103,13 @@ const [Modal, modalApi] = useVbenModal({
}); });
return; return;
} }
if (data.row) { modalApi.lock();
formData.value = data.row; try {
await formApi.setValues(data.row); formData.value = await getRouteProcess(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
} }
}, },
}); });

View File

@ -23,8 +23,8 @@ const props = defineProps<{
routeId: number; routeId: number;
}>(); }>();
const isEditable = ref(props.formMode !== 'detail'); const isEditable = ref(props.formMode !== 'detail'); //
const list = ref<MesProRouteProcessApi.RouteProcess[]>([]); const list = ref<MesProRouteProcessApi.RouteProcess[]>([]); // 线
const [ProcessFormModal, processFormModalApi] = useVbenModal({ const [ProcessFormModal, processFormModalApi] = useVbenModal({
connectedComponent: ProcessForm, connectedComponent: ProcessForm,
@ -45,6 +45,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions<MesProRouteProcessApi.RouteProcess>, } as VxeTableGridOptions<MesProRouteProcessApi.RouteProcess>,
}); });
/** 加载工艺路线工序列表 */
async function getList() { async function getList() {
gridApi.setLoading(true); gridApi.setLoading(true);
try { try {
@ -55,20 +56,18 @@ async function getList() {
} }
} }
/** 新增工艺路线工序 */
function handleCreate() { function handleCreate() {
const maxSort = const maxSort = Math.max(0, ...list.value.map((item) => item.sort || 0));
list.value.length > 0
? Math.max(...list.value.map((item) => item.sort || 0))
: 0;
processFormModalApi.setData({ maxSort, routeId: props.routeId }).open(); processFormModalApi.setData({ maxSort, routeId: props.routeId }).open();
} }
/** 编辑工艺路线工序 */
function handleEdit(row: MesProRouteProcessApi.RouteProcess) { function handleEdit(row: MesProRouteProcessApi.RouteProcess) {
processFormModalApi processFormModalApi.setData({ id: row.id, routeId: props.routeId }).open();
.setData({ id: row.id, routeId: props.routeId, row })
.open();
} }
/** 删除工艺路线工序 */
async function handleDelete(row: MesProRouteProcessApi.RouteProcess) { async function handleDelete(row: MesProRouteProcessApi.RouteProcess) {
await deleteRouteProcess(row.id!); await deleteRouteProcess(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', ['工艺路线工序'])); ElMessage.success($t('ui.actionMessage.deleteSuccess', ['工艺路线工序']));

View File

@ -1,34 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { FormInstance, FormRules } from 'element-plus';
import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process';
import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom'; import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom';
import { computed, reactive, ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { import { useVbenModal } from '@vben/common-ui';
ElDialog,
ElForm, import { ElMessage, ElTabPane, ElTabs } from 'element-plus';
ElFormItem,
ElInput,
ElInputNumber,
ElMessage,
ElTabPane,
ElTabs,
} from 'element-plus';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getRouteProcessListByRoute } from '#/api/mes/pro/route/process'; import { getRouteProcessListByRoute } from '#/api/mes/pro/route/process';
import { import {
createRouteProductBom,
deleteRouteProductBom, deleteRouteProductBom,
getRouteProductBomList, getRouteProductBomList,
updateRouteProductBom,
} from '#/api/mes/pro/route/productbom'; } from '#/api/mes/pro/route/productbom';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { MdProductBomSelect } from '#/views/mes/md/item/components';
import { useRouteProductBomGridColumns } from '../data'; import { useRouteProductBomGridColumns } from '../data';
import BomForm from './bom-form.vue';
const props = defineProps<{ const props = defineProps<{
productId: number; productId: number;
@ -36,27 +26,14 @@ const props = defineProps<{
routeId: number; routeId: number;
}>(); }>();
const processOptions = ref< const processList = ref<MesProRouteProcessApi.RouteProcess[]>([]); // Tab
Array<{ processId: number; processName?: string }> const activeProcessId = ref<number>(); // Tab
>([]); const list = ref<MesProRouteProductBomApi.RouteProductBom[]>([]); // BOM
const activeProcessId = ref<string>('');
const list = ref<MesProRouteProductBomApi.RouteProductBom[]>([]);
const formVisible = ref(false); const [BomFormModal, bomFormModalApi] = useVbenModal({
const formRef = ref<FormInstance>(); connectedComponent: BomForm,
const isUpdate = ref(false); destroyOnClose: true,
const formData = reactive<MesProRouteProductBomApi.RouteProductBom>({
quantity: 1,
}); });
const formRules: FormRules = {
itemId: [{ message: 'BOM 物料不能为空', required: true }],
quantity: [{ message: '用料比例不能为空', required: true }],
};
const formTitle = computed(() =>
isUpdate.value
? $t('ui.actionTitle.edit', ['BOM 物料'])
: $t('ui.actionTitle.create', ['BOM 物料']),
);
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
@ -72,22 +49,20 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions<MesProRouteProductBomApi.RouteProductBom>, } as VxeTableGridOptions<MesProRouteProductBomApi.RouteProductBom>,
}); });
/** 加载路线下的工序列表,用于工序 Tab */
async function loadProcessList() { async function loadProcessList() {
const data = await getRouteProcessListByRoute(props.routeId); processList.value = (await getRouteProcessListByRoute(props.routeId)) || [];
processOptions.value = (data || []).map((item) => ({ if (processList.value.length > 0) {
processId: item.processId!, activeProcessId.value = processList.value[0]!.processId;
processName: item.processName,
}));
if (processOptions.value.length > 0) {
activeProcessId.value = String(processOptions.value[0]!.processId);
await getList(); await getList();
} else { } else {
activeProcessId.value = ''; activeProcessId.value = undefined;
list.value = []; list.value = [];
gridApi.setGridOptions({ data: list.value }); gridApi.setGridOptions({ data: list.value });
} }
} }
/** 加载当前工序下的 BOM 列表 */
async function getList() { async function getList() {
if (!activeProcessId.value) { if (!activeProcessId.value) {
return; return;
@ -95,7 +70,7 @@ async function getList() {
gridApi.setLoading(true); gridApi.setLoading(true);
try { try {
list.value = await getRouteProductBomList({ list.value = await getRouteProductBomList({
processId: Number(activeProcessId.value), processId: activeProcessId.value,
productId: props.productId, productId: props.productId,
routeId: props.routeId, routeId: props.routeId,
}); });
@ -105,69 +80,40 @@ async function getList() {
} }
} }
function resetForm() { /** 新增 BOM */
Object.assign(formData, {
id: undefined,
itemCode: undefined,
itemId: undefined,
itemName: undefined,
processId: Number(activeProcessId.value),
productId: props.productId,
quantity: 1,
remark: undefined,
routeId: props.routeId,
specification: undefined,
unitName: undefined,
});
formRef.value?.clearValidate();
}
function handleCreate() { function handleCreate() {
if (!activeProcessId.value) { if (!activeProcessId.value) {
ElMessage.warning('请先选择工序'); ElMessage.warning('请先选择工序');
return; return;
} }
resetForm(); bomFormModalApi
isUpdate.value = false; .setData({
formVisible.value = true; processId: activeProcessId.value,
productId: props.productId,
routeId: props.routeId,
})
.open();
} }
/** 编辑 BOM */
function handleEdit(row: MesProRouteProductBomApi.RouteProductBom) { function handleEdit(row: MesProRouteProductBomApi.RouteProductBom) {
Object.assign(formData, row); bomFormModalApi
isUpdate.value = true; .setData({
formVisible.value = true; id: row.id,
processId: activeProcessId.value!,
productId: props.productId,
routeId: props.routeId,
})
.open();
} }
/** 删除 BOM */
async function handleDelete(row: MesProRouteProductBomApi.RouteProductBom) { async function handleDelete(row: MesProRouteProductBomApi.RouteProductBom) {
await deleteRouteProductBom(row.id!); await deleteRouteProductBom(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', ['BOM 物料'])); ElMessage.success($t('ui.actionMessage.deleteSuccess', ['BOM 物料']));
await getList(); await getList();
} }
async function submitForm() {
try {
await formRef.value?.validate();
} catch {
return;
}
await (isUpdate.value
? updateRouteProductBom(formData)
: createRouteProductBom(formData));
ElMessage.success($t('ui.actionMessage.operationSuccess'));
formVisible.value = false;
await getList();
}
function handleBomChange(bom?: any) {
if (bom) {
formData.quantity = bom.quantity ?? 1;
formData.itemCode = bom.bomItemCode;
formData.itemName = bom.bomItemName;
formData.specification = bom.specification;
formData.unitName = bom.unitName;
}
}
watch( watch(
() => [props.routeId, props.productId], () => [props.routeId, props.productId],
() => { () => {
@ -180,12 +126,13 @@ watch(
</script> </script>
<template> <template>
<BomFormModal @success="getList" />
<ElTabs v-model="activeProcessId" @tab-change="getList"> <ElTabs v-model="activeProcessId" @tab-change="getList">
<ElTabPane <ElTabPane
v-for="item in processOptions" v-for="item in processList"
:key="String(item.processId)" :key="item.processId"
:label="item.processName" :label="item.processName"
:name="String(item.processId)" :name="item.processId"
/> />
</ElTabs> </ElTabs>
<div class="mb-3 flex items-center justify-start"> <div class="mb-3 flex items-center justify-start">
@ -223,56 +170,4 @@ watch(
/> />
</template> </template>
</Grid> </Grid>
<ElDialog v-model="formVisible" :title="formTitle" width="500px">
<ElForm
ref="formRef"
label-width="100px"
:model="formData"
:rules="formRules"
>
<ElFormItem label="BOM 物料" prop="itemId">
<MdProductBomSelect
v-model="formData.itemId"
:item-id="productId"
placeholder="请选择 BOM 物料"
@change="handleBomChange"
/>
</ElFormItem>
<ElFormItem label="用料比例" prop="quantity">
<ElInputNumber
v-model="formData.quantity"
class="!w-full"
controls-position="right"
:min="0"
:precision="2"
/>
</ElFormItem>
<ElFormItem label="备注" prop="remark">
<ElInput
v-model="formData.remark"
:maxlength="250"
placeholder="请输入备注"
:rows="2"
type="textarea"
/>
</ElFormItem>
</ElForm>
<template #footer>
<TableAction
:actions="[
{
label: $t('common.cancel'),
type: 'default',
onClick: () => (formVisible = false),
},
{
label: $t('common.confirm'),
type: 'primary',
onClick: submitForm,
},
]"
/>
</template>
</ElDialog>
</template> </template>

View File

@ -1,87 +1,76 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { FormInstance, FormRules } from 'element-plus';
import type { MesProRouteProductApi } from '#/api/mes/pro/route/product'; import type { MesProRouteProductApi } from '#/api/mes/pro/route/product';
import { computed, reactive, ref } from 'vue'; import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { import { ElDivider, ElMessage } from 'element-plus';
ElDivider,
ElForm,
ElFormItem,
ElInput,
ElInputNumber,
ElMessage,
ElOption,
ElSelect,
} from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { import {
createRouteProduct, createRouteProduct,
getRouteProduct,
updateRouteProduct, updateRouteProduct,
} from '#/api/mes/pro/route/product'; } from '#/api/mes/pro/route/product';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { useRouteProductFormSchema } from '../data';
import ProductBomList from './product-bom-list.vue'; import ProductBomList from './product-bom-list.vue';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const isUpdate = ref(false); const formData = ref<MesProRouteProductApi.RouteProduct>();
const formRef = ref<FormInstance>();
const formData = reactive<MesProRouteProductApi.RouteProduct>({
productionTime: 1,
quantity: 1,
timeUnitType: 'MINUTE',
});
const formRules: FormRules = {
itemId: [{ message: '产品不能为空', required: true }],
quantity: [{ message: '生产数量不能为空', required: true }],
};
const timeUnitOptions = computed(() =>
getDictOptions(DICT_TYPE.MES_TIME_UNIT_TYPE),
);
const getTitle = computed(() => const getTitle = computed(() =>
isUpdate.value formData.value?.id
? $t('ui.actionTitle.edit', ['工艺路线产品']) ? $t('ui.actionTitle.edit', ['工艺路线产品'])
: $t('ui.actionTitle.create', ['工艺路线产品']), : $t('ui.actionTitle.create', ['工艺路线产品']),
); );
function resetForm(routeId?: number) { /** MdItemSelect change 回调:把物料编码/名称/规格/单位回填到 form */
Object.assign(formData, { async function handleItemChange(item?: any) {
id: undefined, if (!item) {
itemCode: undefined, return;
itemId: undefined, }
itemName: undefined, await formApi.setValues({
productionTime: 1, itemCode: item.code,
quantity: 1, itemName: item.name,
remark: undefined, specification: item.specification,
routeId: routeId ?? formData.routeId, unitName: item.unitMeasureName,
specification: undefined,
timeUnitType: 'MINUTE',
unitName: undefined,
}); });
formRef.value?.clearValidate();
} }
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: { class: 'w-full' },
formItemClass: 'col-span-1',
labelWidth: 100,
},
layout: 'horizontal',
schema: useRouteProductFormSchema(handleItemChange),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
try { const { valid } = await formApi.validate();
await formRef.value?.validate(); if (!valid) {
} catch {
return; return;
} }
modalApi.lock(); modalApi.lock();
//
const data =
(await formApi.getValues()) as MesProRouteProductApi.RouteProduct;
try { try {
if (isUpdate.value) { if (formData.value?.id) {
await updateRouteProduct(formData); await updateRouteProduct(data);
// formData BOM itemId
formData.value = { ...formData.value, ...data };
} else { } else {
const id = await createRouteProduct(formData); // 便 BOM
formData.id = id; const id = await createRouteProduct(data);
isUpdate.value = true; formData.value = { ...data, id };
await formApi.setFieldValue('id', id);
} }
emit('success'); emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess')); ElMessage.success($t('ui.actionMessage.operationSuccess'));
@ -89,102 +78,41 @@ const [Modal, modalApi] = useVbenModal({
modalApi.unlock(); modalApi.unlock();
} }
}, },
onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
resetForm(); formData.value = undefined;
isUpdate.value = false;
return; return;
} }
await formApi.resetForm();
//
const data = modalApi.getData<{ const data = modalApi.getData<{
id?: number; id?: number;
routeId: number; routeId: number;
row?: MesProRouteProductApi.RouteProduct;
}>(); }>();
if (!data) { if (!data) {
return; return;
} }
if (data.row) { if (!data.id) {
Object.assign(formData, data.row); await formApi.setValues({ routeId: data.routeId });
isUpdate.value = true; return;
} else { }
resetForm(data.routeId); modalApi.lock();
isUpdate.value = false; try {
formData.value = await getRouteProduct(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
} }
}, },
}); });
function handleItemChange(item?: any) {
if (!item) {
return;
}
formData.itemCode = item.code;
formData.itemName = item.name;
formData.specification = item.specification;
formData.unitName = item.unitName;
}
</script> </script>
<template> <template>
<Modal :title="getTitle" class="w-3/5"> <Modal :title="getTitle" class="w-3/5">
<ElForm <Form class="mx-4" />
ref="formRef" <!-- 编辑模式下展示产品 BOM 子表新增模式下隐藏 -->
class="mx-4" <template v-if="formData?.id && formData?.itemId">
label-width="120px"
:model="formData"
:rules="formRules"
>
<div class="grid grid-cols-2 gap-x-4">
<ElFormItem class="col-span-2" label="产品" prop="itemId">
<MdItemSelect
v-model="formData.itemId"
@change="handleItemChange"
/>
</ElFormItem>
<ElFormItem label="生产数量" prop="quantity">
<ElInputNumber
v-model="formData.quantity"
class="!w-full"
controls-position="right"
:min="1"
:precision="0"
/>
</ElFormItem>
<ElFormItem label="生产用时" prop="productionTime">
<ElInputNumber
v-model="formData.productionTime"
class="!w-full"
controls-position="right"
:min="0"
:precision="2"
/>
</ElFormItem>
<ElFormItem label="时间单位" prop="timeUnitType">
<ElSelect
v-model="formData.timeUnitType"
class="!w-full"
clearable
placeholder="请选择"
>
<ElOption
v-for="dict in timeUnitOptions"
:key="dict.value as string"
:label="dict.label"
:value="dict.value as string"
/>
</ElSelect>
</ElFormItem>
<ElFormItem class="col-span-2" label="备注" prop="remark">
<ElInput
v-model="formData.remark"
:maxlength="250"
placeholder="请输入备注"
:rows="2"
type="textarea"
/>
</ElFormItem>
</div>
</ElForm>
<template v-if="isUpdate && formData.id && formData.itemId">
<ElDivider class="!my-3" content-position="left">产品 BOM 配置</ElDivider> <ElDivider class="!my-3" content-position="left">产品 BOM 配置</ElDivider>
<div class="mx-4"> <div class="mx-4">
<ProductBomList <ProductBomList

View File

@ -25,8 +25,8 @@ const props = defineProps<{
routeId: number; routeId: number;
}>(); }>();
const isEditable = ref(props.formMode !== 'detail'); const isEditable = ref(props.formMode !== 'detail'); //
const list = ref<MesProRouteProductApi.RouteProduct[]>([]); const list = ref<MesProRouteProductApi.RouteProduct[]>([]); // 线
const [ProductFormModal, productFormModalApi] = useVbenModal({ const [ProductFormModal, productFormModalApi] = useVbenModal({
connectedComponent: ProductForm, connectedComponent: ProductForm,
@ -47,6 +47,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions<MesProRouteProductApi.RouteProduct>, } as VxeTableGridOptions<MesProRouteProductApi.RouteProduct>,
}); });
/** 加载工艺路线产品列表 */
async function getList() { async function getList() {
gridApi.setLoading(true); gridApi.setLoading(true);
try { try {
@ -57,16 +58,17 @@ async function getList() {
} }
} }
/** 新增工艺路线产品 */
function handleCreate() { function handleCreate() {
productFormModalApi.setData({ routeId: props.routeId }).open(); productFormModalApi.setData({ routeId: props.routeId }).open();
} }
/** 编辑工艺路线产品 */
function handleEdit(row: MesProRouteProductApi.RouteProduct) { function handleEdit(row: MesProRouteProductApi.RouteProduct) {
productFormModalApi productFormModalApi.setData({ id: row.id, routeId: props.routeId }).open();
.setData({ id: row.id, routeId: props.routeId, row })
.open();
} }
/** 删除工艺路线产品 */
async function handleDelete(row: MesProRouteProductApi.RouteProduct) { async function handleDelete(row: MesProRouteProductApi.RouteProduct) {
await deleteRouteProduct(row.id!); await deleteRouteProduct(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', ['工艺路线产品'])); ElMessage.success($t('ui.actionMessage.deleteSuccess', ['工艺路线产品']));