feat(mes/qc): 迁移质检方案管理

pull/350/head
YunaiV 2026-05-29 08:36:16 +08:00
parent 16d6ab0722
commit ec5b607171
14 changed files with 2542 additions and 0 deletions

View File

@ -0,0 +1,507 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateApi } from '#/api/mes/qc/template';
import type { MesQcTemplateIndicatorApi } from '#/api/mes/qc/template/indicator';
import type { MesQcTemplateItemApi } from '#/api/mes/qc/template/item';
import { h, markRaw } from 'vue';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Button } from 'ant-design-vue';
import { z } from '#/adapter/form';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import MdItemSelect from '#/views/mes/md/item/components/md-item-select.vue';
import MdUnitMeasureSelect from '#/views/mes/md/unitmeasure/components/md-unit-measure-select.vue';
import QcIndicatorSelect from '#/views/mes/qc/indicator/components/qc-indicator-select.vue';
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
/** 表单类型 */
export type FormType = 'create' | 'detail' | 'update';
/** 新增/修改的表单 */
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'code',
label: '方案编号',
component: 'Input',
componentProps: {
placeholder: '请输入方案编号',
},
rules: 'required',
suffix: () =>
h(
Button,
{
type: 'default',
onClick: async () => {
try {
const code = await generateAutoCode(
MesAutoCodeRuleCode.QC_TEMPLATE_CODE,
);
await formApi?.setFieldValue('code', code);
} catch (error) {
console.error(error);
}
},
},
{ default: () => '自动生成' },
),
},
{
fieldName: 'name',
label: '方案名称',
component: 'Input',
componentProps: {
placeholder: '请输入方案名称',
},
rules: 'required',
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'types',
label: '检测种类',
component: 'Select',
formItemClass: 'col-span-3',
componentProps: {
allowClear: true,
mode: 'multiple',
options: getDictOptions(DICT_TYPE.MES_QC_TYPE, 'number'),
placeholder: '请选择检测种类',
},
rules: z.array(z.number()).min(1, '检测种类不能为空'),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '方案编号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入方案编号',
},
},
{
fieldName: 'name',
label: '方案名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入方案名称',
},
},
{
fieldName: 'type',
label: '检测种类',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_QC_TYPE, 'number'),
placeholder: '请选择检测种类',
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesQcTemplateApi.Template>['columns'] {
return [
{
field: 'code',
title: '方案编号',
width: 150,
slots: { default: 'code' },
},
{
field: 'name',
title: '方案名称',
minWidth: 180,
},
{
field: 'types',
title: '检测种类',
minWidth: 200,
slots: { default: 'types' },
},
{
field: 'status',
title: '状态',
width: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'remark',
title: '备注',
minWidth: 160,
},
{
field: 'createTime',
title: '创建时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 160,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 检测指标项子表的字段 */
export function useIndicatorGridColumns(): VxeTableGridOptions<MesQcTemplateIndicatorApi.TemplateIndicator>['columns'] {
return [
{
field: 'indicatorCode',
title: '检测项编码',
width: 140,
},
{
field: 'indicatorName',
title: '检测项名称',
minWidth: 150,
},
{
field: 'indicatorType',
title: '检测项类型',
width: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_INDICATOR_TYPE },
},
},
{
field: 'indicatorTool',
title: '检测工具',
width: 120,
},
{
field: 'checkMethod',
title: '检测方法',
minWidth: 180,
},
{
field: 'standardValue',
title: '标准值',
width: 100,
},
{
field: 'unitMeasureName',
title: '单位',
width: 80,
},
{
field: 'thresholdMax',
title: '误差上限',
width: 100,
},
{
field: 'thresholdMin',
title: '误差下限',
width: 100,
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 检测指标项子表的表单 */
export function useIndicatorFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'indicatorId',
label: '质检指标',
component: markRaw(QcIndicatorSelect),
componentProps: {
placeholder: '请选择质检指标',
},
formItemClass: 'col-span-2',
rules: 'required',
},
{
fieldName: 'standardValue',
label: '标准值',
component: 'InputNumber',
componentProps: {
class: '!w-full',
precision: 4,
placeholder: '请输入标准值',
},
},
{
fieldName: 'unitMeasureId',
label: '计量单位',
component: markRaw(MdUnitMeasureSelect),
componentProps: {
placeholder: '请选择计量单位',
},
},
{
fieldName: 'thresholdMax',
label: '误差上限',
component: 'InputNumber',
componentProps: {
class: '!w-full',
precision: 4,
placeholder: '请输入',
},
},
{
fieldName: 'thresholdMin',
label: '误差下限',
component: 'InputNumber',
componentProps: {
class: '!w-full',
precision: 4,
placeholder: '请输入',
},
},
{
fieldName: 'checkMethod',
label: '检测方法',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入检测方法',
rows: 3,
},
},
{
fieldName: 'docUrl',
label: '说明图URL',
component: 'Input',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入说明图URL',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}
/** 产品关联子表的字段 */
export function useItemGridColumns(): VxeTableGridOptions<MesQcTemplateItemApi.TemplateItem>['columns'] {
return [
{
field: 'itemCode',
title: '物料编码',
width: 130,
},
{
field: 'itemName',
title: '物料名称',
minWidth: 150,
},
{
field: 'specification',
title: '规格型号',
minWidth: 130,
},
{
field: 'unitMeasureName',
title: '计量单位',
width: 100,
},
{
field: 'quantityCheck',
title: '最低检测数',
width: 110,
},
{
field: 'quantityUnqualified',
title: '最大不合格数',
width: 120,
formatter: ({ cellValue }) =>
cellValue === 0 ? '不启用' : (cellValue ?? ''),
},
{
field: 'criticalRate',
title: '最大致命缺陷率(%)',
width: 150,
},
{
field: 'majorRate',
title: '最大严重缺陷率(%)',
width: 150,
},
{
field: 'minorRate',
title: '最大轻微缺陷率(%)',
width: 150,
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 产品关联子表的表单 */
export function useItemFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'itemId',
label: '产品物料',
component: markRaw(MdItemSelect),
componentProps: {
placeholder: '请选择产品物料',
},
formItemClass: 'col-span-3',
rules: 'required',
},
{
fieldName: 'quantityCheck',
label: '最低检测数',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 1,
placeholder: '请输入最低检测数',
},
rules: z.number().default(1),
},
{
fieldName: 'quantityUnqualified',
label: '最大不合格数',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 0,
placeholder: '0表示不启用',
},
rules: z.number().default(0),
help: '超出最大不合格数后整批判定不合格0表示不启用',
},
{
fieldName: 'criticalRate',
label: '致命缺陷率(%)',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 0,
max: 100,
precision: 2,
placeholder: '0表示不允许',
},
rules: z.number().default(0),
help: '缺陷比例超出后整批判定不合格0表示不允许出现',
},
{
fieldName: 'majorRate',
label: '严重缺陷率(%)',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 0,
max: 100,
precision: 2,
placeholder: '0表示不允许',
},
rules: z.number().default(0),
help: '缺陷比例超出后整批判定不合格0表示不允许出现',
},
{
fieldName: 'minorRate',
label: '轻微缺陷率(%)',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 0,
max: 100,
precision: 2,
},
rules: z.number().default(100),
help: '缺陷比例超出后整批判定不合格',
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}

View File

@ -0,0 +1,172 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateApi } from '#/api/mes/qc/template';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteTemplate,
exportTemplate,
getTemplatePage,
} from '#/api/mes/qc/template';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建质检方案 */
function handleCreate() {
formModalApi.setData({ formType: 'create' }).open();
}
/** 查看质检方案 */
function handleDetail(row: MesQcTemplateApi.Template) {
formModalApi.setData({ formType: 'detail', id: row.id }).open();
}
/** 编辑质检方案 */
function handleEdit(row: MesQcTemplateApi.Template) {
formModalApi.setData({ formType: 'update', id: row.id }).open();
}
/** 删除质检方案 */
async function handleDelete(row: MesQcTemplateApi.Template) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
});
try {
await deleteTemplate(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
hideLoading();
}
}
/** 导出表格 */
async function handleExport() {
const data = await exportTemplate(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '质检方案.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getTemplatePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesQcTemplateApi.Template>,
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【质量】质检方案"
url="https://doc.iocoder.cn/mes/qc/template/"
/>
</template>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['质检方案']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:qc-template:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['mes:qc-template:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #code="{ row }">
<Button type="link" @click="handleDetail(row)">
{{ row.code }}
</Button>
</template>
<template #types="{ row }">
<DictTag
v-for="(type, index) in row.types"
:key="index"
:type="DICT_TYPE.MES_QC_TYPE"
:value="type"
class="mr-1"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['mes:qc-template:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:qc-template:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,127 @@
<script lang="ts" setup>
import type { FormType } from '../data';
import type { MesQcTemplateApi } from '#/api/mes/qc/template';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Tabs } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createTemplate,
getTemplate,
updateTemplate,
} from '#/api/mes/qc/template';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
import IndicatorList from './indicator-list.vue';
import ItemList from './item-list.vue';
const emit = defineEmits(['success']);
const formType = ref<FormType>('create'); // //
const subTabsName = ref('indicator'); // tab
const formData = ref<MesQcTemplateApi.Template>();
const getTitle = computed(() => {
if (formType.value === 'detail') {
return $t('ui.actionTitle.view', ['质检方案']);
}
return formType.value === 'update'
? $t('ui.actionTitle.edit', ['质检方案'])
: $t('ui.actionTitle.create', ['质检方案']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 100,
},
wrapperClass: 'grid-cols-3',
layout: 'horizontal',
schema: [],
showDefaultActions: false,
});
/** 表单 schema 需要 formApi 引用(生成编码按钮),所以通过 setState 设置 schema */
formApi.setState({ schema: useFormSchema(formApi) });
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
if (formType.value === 'detail') {
await modalApi.close();
return;
}
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesQcTemplateApi.Template;
try {
await (formData.value?.id ? updateTemplate(data) : createTemplate(data));
//
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
subTabsName.value = 'indicator';
//
const data = modalApi.getData<{ formType: FormType; id?: number }>();
formType.value = data.formType;
formApi.setDisabled(formType.value === 'detail');
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTemplate(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-4/5">
<Form class="mx-4" />
<Tabs
v-if="formType !== 'create' && formData?.id"
v-model:active-key="subTabsName"
class="mx-4 mt-4"
>
<Tabs.TabPane key="indicator" tab="检测指标项">
<IndicatorList
:readonly="formType === 'detail'"
:template-id="formData.id"
/>
</Tabs.TabPane>
<Tabs.TabPane key="item" tab="产品关联">
<ItemList
:readonly="formType === 'detail'"
:template-id="formData.id"
/>
</Tabs.TabPane>
</Tabs>
</Modal>
</template>

View File

@ -0,0 +1,97 @@
<script lang="ts" setup>
import type { MesQcTemplateIndicatorApi } from '#/api/mes/qc/template/indicator';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createTemplateIndicator,
getTemplateIndicator,
updateTemplateIndicator,
} from '#/api/mes/qc/template/indicator';
import { $t } from '#/locales';
import { useIndicatorFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesQcTemplateIndicatorApi.TemplateIndicator>();
const templateId = ref<number>(); //
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['指标项'])
: $t('ui.actionTitle.create', ['指标项']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 110,
},
wrapperClass: 'grid-cols-2',
layout: 'horizontal',
schema: useIndicatorFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data =
(await formApi.getValues()) as MesQcTemplateIndicatorApi.TemplateIndicator;
data.templateId = templateId.value;
try {
await (formData.value?.id
? updateTemplateIndicator(data)
: createTemplateIndicator(data));
//
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
//
const data = modalApi.getData<
MesQcTemplateIndicatorApi.TemplateIndicator & { templateId: number }
>();
templateId.value = data.templateId;
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTemplateIndicator(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-3/5">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -0,0 +1,137 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateIndicatorApi } from '#/api/mes/qc/template/indicator';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteTemplateIndicator,
getTemplateIndicatorPage,
} from '#/api/mes/qc/template/indicator';
import { $t } from '#/locales';
import { useIndicatorGridColumns } from '../data';
import IndicatorForm from './indicator-form.vue';
const props = defineProps<{
readonly?: boolean;
templateId: number;
}>();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: IndicatorForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建指标项 */
function handleCreate() {
formModalApi.setData({ templateId: props.templateId }).open();
}
/** 编辑指标项 */
function handleEdit(row: MesQcTemplateIndicatorApi.TemplateIndicator) {
formModalApi.setData({ ...row, templateId: props.templateId }).open();
}
/** 删除指标项 */
async function handleDelete(
row: MesQcTemplateIndicatorApi.TemplateIndicator,
) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.indicatorName]),
duration: 0,
});
try {
await deleteTemplateIndicator(row.id!);
message.success(
$t('ui.actionMessage.deleteSuccess', [row.indicatorName]),
);
handleRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useIndicatorGridColumns(),
height: 360,
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getTemplateIndicatorPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
templateId: props.templateId,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<MesQcTemplateIndicatorApi.TemplateIndicator>,
});
</script>
<template>
<div>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template v-if="!readonly" #toolbar-tools>
<TableAction
:actions="[
{
label: '新增指标项',
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:qc-template:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
row.indicatorName,
]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,96 @@
<script lang="ts" setup>
import type { MesQcTemplateItemApi } from '#/api/mes/qc/template/item';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createTemplateItem,
getTemplateItem,
updateTemplateItem,
} from '#/api/mes/qc/template/item';
import { $t } from '#/locales';
import { useItemFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesQcTemplateItemApi.TemplateItem>();
const templateId = ref<number>(); //
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['产品关联'])
: $t('ui.actionTitle.create', ['产品关联']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 130,
},
wrapperClass: 'grid-cols-3',
layout: 'horizontal',
schema: useItemFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesQcTemplateItemApi.TemplateItem;
data.templateId = templateId.value;
try {
await (formData.value?.id
? updateTemplateItem(data)
: createTemplateItem(data));
//
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
//
const data = modalApi.getData<
MesQcTemplateItemApi.TemplateItem & { templateId: number }
>();
templateId.value = data.templateId;
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTemplateItem(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-3/5">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateItemApi } from '#/api/mes/qc/template/item';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteTemplateItem,
getTemplateItemPage,
} from '#/api/mes/qc/template/item';
import { $t } from '#/locales';
import { useItemGridColumns } from '../data';
import ItemForm from './item-form.vue';
const props = defineProps<{
readonly?: boolean;
templateId: number;
}>();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: ItemForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建产品关联 */
function handleCreate() {
formModalApi.setData({ templateId: props.templateId }).open();
}
/** 编辑产品关联 */
function handleEdit(row: MesQcTemplateItemApi.TemplateItem) {
formModalApi.setData({ ...row, templateId: props.templateId }).open();
}
/** 删除产品关联 */
async function handleDelete(row: MesQcTemplateItemApi.TemplateItem) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.itemName]),
duration: 0,
});
try {
await deleteTemplateItem(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
handleRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useItemGridColumns(),
height: 360,
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getTemplateItemPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
templateId: props.templateId,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<MesQcTemplateItemApi.TemplateItem>,
});
</script>
<template>
<div>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template v-if="!readonly" #toolbar-tools>
<TableAction
:actions="[
{
label: '新增产品关联',
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:qc-template:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,514 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateApi } from '#/api/mes/qc/template';
import type { MesQcTemplateIndicatorApi } from '#/api/mes/qc/template/indicator';
import type { MesQcTemplateItemApi } from '#/api/mes/qc/template/item';
import { h, markRaw } from 'vue';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { ElButton } from 'element-plus';
import { z } from '#/adapter/form';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import MdItemSelect from '#/views/mes/md/item/components/md-item-select.vue';
import MdUnitMeasureSelect from '#/views/mes/md/unitmeasure/components/md-unit-measure-select.vue';
import QcIndicatorSelect from '#/views/mes/qc/indicator/components/qc-indicator-select.vue';
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
/** 表单类型 */
export type FormType = 'create' | 'detail' | 'update';
/** 新增/修改的表单 */
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'code',
label: '方案编号',
component: 'Input',
componentProps: {
placeholder: '请输入方案编号',
},
rules: 'required',
suffix: () =>
h(
ElButton,
{
onClick: async () => {
try {
const code = await generateAutoCode(
MesAutoCodeRuleCode.QC_TEMPLATE_CODE,
);
await formApi?.setFieldValue('code', code);
} catch (error) {
console.error(error);
}
},
},
{ default: () => '自动生成' },
),
},
{
fieldName: 'name',
label: '方案名称',
component: 'Input',
componentProps: {
placeholder: '请输入方案名称',
},
rules: 'required',
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'types',
label: '检测种类',
component: 'Select',
formItemClass: 'col-span-3',
componentProps: {
clearable: true,
multiple: true,
options: getDictOptions(DICT_TYPE.MES_QC_TYPE, 'number'),
placeholder: '请选择检测种类',
},
rules: z.array(z.number()).min(1, '检测种类不能为空'),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '方案编号',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入方案编号',
},
},
{
fieldName: 'name',
label: '方案名称',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入方案名称',
},
},
{
fieldName: 'type',
label: '检测种类',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.MES_QC_TYPE, 'number'),
placeholder: '请选择检测种类',
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesQcTemplateApi.Template>['columns'] {
return [
{
field: 'code',
title: '方案编号',
width: 150,
slots: { default: 'code' },
},
{
field: 'name',
title: '方案名称',
minWidth: 180,
},
{
field: 'types',
title: '检测种类',
minWidth: 200,
slots: { default: 'types' },
},
{
field: 'status',
title: '状态',
width: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'remark',
title: '备注',
minWidth: 160,
},
{
field: 'createTime',
title: '创建时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 160,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 检测指标项子表的字段 */
export function useIndicatorGridColumns(): VxeTableGridOptions<MesQcTemplateIndicatorApi.TemplateIndicator>['columns'] {
return [
{
field: 'indicatorCode',
title: '检测项编码',
width: 140,
},
{
field: 'indicatorName',
title: '检测项名称',
minWidth: 150,
},
{
field: 'indicatorType',
title: '检测项类型',
width: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_INDICATOR_TYPE },
},
},
{
field: 'indicatorTool',
title: '检测工具',
width: 120,
},
{
field: 'checkMethod',
title: '检测方法',
minWidth: 180,
},
{
field: 'standardValue',
title: '标准值',
width: 100,
},
{
field: 'unitMeasureName',
title: '单位',
width: 80,
},
{
field: 'thresholdMax',
title: '误差上限',
width: 100,
},
{
field: 'thresholdMin',
title: '误差下限',
width: 100,
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 检测指标项子表的表单 */
export function useIndicatorFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'indicatorId',
label: '质检指标',
component: markRaw(QcIndicatorSelect),
componentProps: {
placeholder: '请选择质检指标',
},
formItemClass: 'col-span-2',
rules: 'required',
},
{
fieldName: 'standardValue',
label: '标准值',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
placeholder: '请输入标准值',
precision: 4,
},
},
{
fieldName: 'unitMeasureId',
label: '计量单位',
component: markRaw(MdUnitMeasureSelect),
componentProps: {
placeholder: '请选择计量单位',
},
},
{
fieldName: 'thresholdMax',
label: '误差上限',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
placeholder: '请输入',
precision: 4,
},
},
{
fieldName: 'thresholdMin',
label: '误差下限',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
placeholder: '请输入',
precision: 4,
},
},
{
fieldName: 'checkMethod',
label: '检测方法',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入检测方法',
rows: 3,
},
},
{
fieldName: 'docUrl',
label: '说明图URL',
component: 'Input',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入说明图URL',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}
/** 产品关联子表的字段 */
export function useItemGridColumns(): VxeTableGridOptions<MesQcTemplateItemApi.TemplateItem>['columns'] {
return [
{
field: 'itemCode',
title: '物料编码',
width: 130,
},
{
field: 'itemName',
title: '物料名称',
minWidth: 150,
},
{
field: 'specification',
title: '规格型号',
minWidth: 130,
},
{
field: 'unitMeasureName',
title: '计量单位',
width: 100,
},
{
field: 'quantityCheck',
title: '最低检测数',
width: 110,
},
{
field: 'quantityUnqualified',
title: '最大不合格数',
width: 120,
formatter: ({ cellValue }) =>
cellValue === 0 ? '不启用' : (cellValue ?? ''),
},
{
field: 'criticalRate',
title: '最大致命缺陷率(%)',
width: 150,
},
{
field: 'majorRate',
title: '最大严重缺陷率(%)',
width: 150,
},
{
field: 'minorRate',
title: '最大轻微缺陷率(%)',
width: 150,
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 产品关联子表的表单 */
export function useItemFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'itemId',
label: '产品物料',
component: markRaw(MdItemSelect),
componentProps: {
placeholder: '请选择产品物料',
},
formItemClass: 'col-span-3',
rules: 'required',
},
{
fieldName: 'quantityCheck',
label: '最低检测数',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 1,
placeholder: '请输入最低检测数',
},
rules: z.number().default(1),
},
{
fieldName: 'quantityUnqualified',
label: '最大不合格数',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 0,
placeholder: '0表示不启用',
},
rules: z.number().default(0),
help: '超出最大不合格数后整批判定不合格0表示不启用',
},
{
fieldName: 'criticalRate',
label: '致命缺陷率(%)',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
max: 100,
min: 0,
placeholder: '0表示不允许',
precision: 2,
},
rules: z.number().default(0),
help: '缺陷比例超出后整批判定不合格0表示不允许出现',
},
{
fieldName: 'majorRate',
label: '严重缺陷率(%)',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
max: 100,
min: 0,
placeholder: '0表示不允许',
precision: 2,
},
rules: z.number().default(0),
help: '缺陷比例超出后整批判定不合格0表示不允许出现',
},
{
fieldName: 'minorRate',
label: '轻微缺陷率(%)',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
max: 100,
min: 0,
precision: 2,
},
rules: z.number().default(100),
help: '缺陷比例超出后整批判定不合格',
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}

View File

@ -0,0 +1,172 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateApi } from '#/api/mes/qc/template';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
import { ElButton, ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteTemplate,
exportTemplate,
getTemplatePage,
} from '#/api/mes/qc/template';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建质检方案 */
function handleCreate() {
formModalApi.setData({ formType: 'create' }).open();
}
/** 查看质检方案 */
function handleDetail(row: MesQcTemplateApi.Template) {
formModalApi.setData({ formType: 'detail', id: row.id }).open();
}
/** 编辑质检方案 */
function handleEdit(row: MesQcTemplateApi.Template) {
formModalApi.setData({ formType: 'update', id: row.id }).open();
}
/** 删除质检方案 */
async function handleDelete(row: MesQcTemplateApi.Template) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [row.name]),
});
try {
await deleteTemplate(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
loadingInstance.close();
}
}
/** 导出表格 */
async function handleExport() {
const data = await exportTemplate(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '质检方案.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getTemplatePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesQcTemplateApi.Template>,
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【质量】质检方案"
url="https://doc.iocoder.cn/mes/qc/template/"
/>
</template>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['质检方案']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:qc-template:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['mes:qc-template:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #code="{ row }">
<ElButton link type="primary" @click="handleDetail(row)">
{{ row.code }}
</ElButton>
</template>
<template #types="{ row }">
<DictTag
v-for="(type, index) in row.types"
:key="index"
:type="DICT_TYPE.MES_QC_TYPE"
:value="type"
class="mr-1"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['mes:qc-template:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:qc-template:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,127 @@
<script lang="ts" setup>
import type { FormType } from '../data';
import type { MesQcTemplateApi } from '#/api/mes/qc/template';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ElMessage, ElTabPane, ElTabs } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import {
createTemplate,
getTemplate,
updateTemplate,
} from '#/api/mes/qc/template';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
import IndicatorList from './indicator-list.vue';
import ItemList from './item-list.vue';
const emit = defineEmits(['success']);
const formType = ref<FormType>('create'); // //
const subTabsName = ref('indicator'); // tab
const formData = ref<MesQcTemplateApi.Template>();
const getTitle = computed(() => {
if (formType.value === 'detail') {
return $t('ui.actionTitle.view', ['质检方案']);
}
return formType.value === 'update'
? $t('ui.actionTitle.edit', ['质检方案'])
: $t('ui.actionTitle.create', ['质检方案']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 100,
},
wrapperClass: 'grid-cols-3',
layout: 'horizontal',
schema: [],
showDefaultActions: false,
});
/** 表单 schema 需要 formApi 引用(生成编码按钮),所以通过 setState 设置 schema */
formApi.setState({ schema: useFormSchema(formApi) });
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
if (formType.value === 'detail') {
await modalApi.close();
return;
}
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesQcTemplateApi.Template;
try {
await (formData.value?.id ? updateTemplate(data) : createTemplate(data));
//
await modalApi.close();
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
subTabsName.value = 'indicator';
//
const data = modalApi.getData<{ formType: FormType; id?: number }>();
formType.value = data.formType;
formApi.setDisabled(formType.value === 'detail');
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTemplate(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-4/5">
<Form class="mx-4" />
<ElTabs
v-if="formType !== 'create' && formData?.id"
v-model="subTabsName"
class="mx-4 mt-4"
>
<ElTabPane label="检测指标项" name="indicator">
<IndicatorList
:readonly="formType === 'detail'"
:template-id="formData.id"
/>
</ElTabPane>
<ElTabPane label="产品关联" name="item">
<ItemList
:readonly="formType === 'detail'"
:template-id="formData.id"
/>
</ElTabPane>
</ElTabs>
</Modal>
</template>

View File

@ -0,0 +1,97 @@
<script lang="ts" setup>
import type { MesQcTemplateIndicatorApi } from '#/api/mes/qc/template/indicator';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import {
createTemplateIndicator,
getTemplateIndicator,
updateTemplateIndicator,
} from '#/api/mes/qc/template/indicator';
import { $t } from '#/locales';
import { useIndicatorFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesQcTemplateIndicatorApi.TemplateIndicator>();
const templateId = ref<number>(); //
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['指标项'])
: $t('ui.actionTitle.create', ['指标项']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 110,
},
wrapperClass: 'grid-cols-2',
layout: 'horizontal',
schema: useIndicatorFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data =
(await formApi.getValues()) as MesQcTemplateIndicatorApi.TemplateIndicator;
data.templateId = templateId.value;
try {
await (formData.value?.id
? updateTemplateIndicator(data)
: createTemplateIndicator(data));
//
await modalApi.close();
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
//
const data = modalApi.getData<
MesQcTemplateIndicatorApi.TemplateIndicator & { templateId: number }
>();
templateId.value = data.templateId;
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTemplateIndicator(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-3/5">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -0,0 +1,137 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateIndicatorApi } from '#/api/mes/qc/template/indicator';
import { useVbenModal } from '@vben/common-ui';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteTemplateIndicator,
getTemplateIndicatorPage,
} from '#/api/mes/qc/template/indicator';
import { $t } from '#/locales';
import { useIndicatorGridColumns } from '../data';
import IndicatorForm from './indicator-form.vue';
const props = defineProps<{
readonly?: boolean;
templateId: number;
}>();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: IndicatorForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建指标项 */
function handleCreate() {
formModalApi.setData({ templateId: props.templateId }).open();
}
/** 编辑指标项 */
function handleEdit(row: MesQcTemplateIndicatorApi.TemplateIndicator) {
formModalApi.setData({ ...row, templateId: props.templateId }).open();
}
/** 删除指标项 */
async function handleDelete(
row: MesQcTemplateIndicatorApi.TemplateIndicator,
) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [row.indicatorName]),
});
try {
await deleteTemplateIndicator(row.id!);
ElMessage.success(
$t('ui.actionMessage.deleteSuccess', [row.indicatorName]),
);
handleRefresh();
} finally {
loadingInstance.close();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useIndicatorGridColumns(),
height: 360,
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getTemplateIndicatorPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
templateId: props.templateId,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<MesQcTemplateIndicatorApi.TemplateIndicator>,
});
</script>
<template>
<div>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template v-if="!readonly" #toolbar-tools>
<TableAction
:actions="[
{
label: '新增指标项',
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:qc-template:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
row.indicatorName,
]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</div>
</template>

View File

@ -0,0 +1,97 @@
<script lang="ts" setup>
import type { MesQcTemplateItemApi } from '#/api/mes/qc/template/item';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import {
createTemplateItem,
getTemplateItem,
updateTemplateItem,
} from '#/api/mes/qc/template/item';
import { $t } from '#/locales';
import { useItemFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesQcTemplateItemApi.TemplateItem>();
const templateId = ref<number>(); //
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['产品关联'])
: $t('ui.actionTitle.create', ['产品关联']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-1',
labelWidth: 130,
},
wrapperClass: 'grid-cols-3',
layout: 'horizontal',
schema: useItemFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data =
(await formApi.getValues()) as MesQcTemplateItemApi.TemplateItem;
data.templateId = templateId.value;
try {
await (formData.value?.id
? updateTemplateItem(data)
: createTemplateItem(data));
//
await modalApi.close();
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
await formApi.resetForm();
//
const data = modalApi.getData<
MesQcTemplateItemApi.TemplateItem & { templateId: number }
>();
templateId.value = data.templateId;
if (!data?.id) {
return;
}
modalApi.lock();
try {
formData.value = await getTemplateItem(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-3/5">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateItemApi } from '#/api/mes/qc/template/item';
import { useVbenModal } from '@vben/common-ui';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteTemplateItem,
getTemplateItemPage,
} from '#/api/mes/qc/template/item';
import { $t } from '#/locales';
import { useItemGridColumns } from '../data';
import ItemForm from './item-form.vue';
const props = defineProps<{
readonly?: boolean;
templateId: number;
}>();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: ItemForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建产品关联 */
function handleCreate() {
formModalApi.setData({ templateId: props.templateId }).open();
}
/** 编辑产品关联 */
function handleEdit(row: MesQcTemplateItemApi.TemplateItem) {
formModalApi.setData({ ...row, templateId: props.templateId }).open();
}
/** 删除产品关联 */
async function handleDelete(row: MesQcTemplateItemApi.TemplateItem) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [row.itemName]),
});
try {
await deleteTemplateItem(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
handleRefresh();
} finally {
loadingInstance.close();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useItemGridColumns(),
height: 360,
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }) => {
return await getTemplateItemPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
templateId: props.templateId,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<MesQcTemplateItemApi.TemplateItem>,
});
</script>
<template>
<div>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template v-if="!readonly" #toolbar-tools>
<TableAction
:actions="[
{
label: '新增产品关联',
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:qc-template:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:qc-template:update'],
ifShow: !readonly,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</div>
</template>