feat(mes-qc): 迁移 antd 来料检验及检测结果、缺陷记录组件(代码优化)

pull/350/head
YunaiV 2026-05-29 16:27:15 +08:00
parent abc8789fe3
commit cea628b1a1
9 changed files with 302 additions and 245 deletions

View File

@ -16,6 +16,13 @@ export namespace MesQcDefectRecordApi {
}
}
/** 查询质检缺陷记录 */
export function getDefectRecord(id: number) {
return requestClient.get<MesQcDefectRecordApi.DefectRecord>(
`/mes/qc/defect-record/get?id=${id}`,
);
}
/** 查询质检缺陷记录分页 */
export function getDefectRecordPage(
params: PageParam & { lineId?: number; qcId?: number; qcType?: number; },

View File

@ -0,0 +1,93 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcDefectRecordApi } from '#/api/mes/qc/defectrecord';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
/** 表单类型 */
export type FormType = 'create' | 'update';
/** 新增/修改缺陷记录的表单 */
export function useDefectRecordInlineFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '缺陷描述',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入缺陷描述',
rows: 2,
},
rules: 'required',
},
{
fieldName: 'level',
label: '缺陷等级',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
placeholder: '请选择缺陷等级',
},
rules: 'selectRequired',
},
{
fieldName: 'quantity',
label: '缺陷数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 1,
placeholder: '请输入缺陷数量',
},
rules: 'required',
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入备注',
},
},
];
}
/** 缺陷记录列表的字段 */
export function useDefectRecordInlineGridColumns(): VxeTableGridOptions<MesQcDefectRecordApi.DefectRecord>['columns'] {
return [
{
field: 'name',
title: '缺陷描述',
minWidth: 200,
},
{
field: 'level',
title: '缺陷等级',
width: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_DEFECT_LEVEL },
},
},
{
field: 'quantity',
title: '缺陷数量',
width: 100,
},
{
field: 'remark',
title: '备注',
minWidth: 150,
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@ -1,11 +1,11 @@
<script lang="ts" setup>
import type { FormType } from './data';
import type { MesQcDefectRecordApi } from '#/api/mes/qc/defectrecord';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { message } from 'ant-design-vue';
@ -17,8 +17,10 @@ import {
} from '#/api/mes/qc/defectrecord';
import { $t } from '#/locales';
interface OpenData {
formType: 'create' | 'update';
import { useDefectRecordInlineFormSchema } from './data';
interface CtxData {
formType: FormType;
id?: number;
lineId: number;
qcId: number;
@ -28,8 +30,7 @@ interface OpenData {
defineOptions({ name: 'DefectRecordInlineForm' });
const emit = defineEmits(['success']);
// TODO @AI createupdate
const formType = ref<'create' | 'update'>('create');
const formType = ref<FormType>('create');
const formData = ref<MesQcDefectRecordApi.DefectRecord>();
const getTitle = computed(() =>
@ -38,8 +39,6 @@ const getTitle = computed(() =>
: $t('ui.actionTitle.create', ['缺陷记录']),
);
// TODO @AI data.ts
// TODO @AI
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
@ -49,50 +48,7 @@ const [Form, formApi] = useVbenForm({
labelWidth: 90,
},
layout: 'horizontal',
schema: [
{
fieldName: 'name',
label: '缺陷描述',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入缺陷描述',
rows: 2,
},
rules: 'required',
},
{
fieldName: 'level',
label: '缺陷等级',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_DEFECT_LEVEL, 'number'),
placeholder: '请选择缺陷等级',
},
rules: 'selectRequired',
},
{
fieldName: 'quantity',
label: '缺陷数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 1,
placeholder: '请输入缺陷数量',
},
rules: 'required',
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入备注',
},
},
],
schema: useDefectRecordInlineFormSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
@ -107,15 +63,16 @@ const [Modal, modalApi] = useVbenModal({
return;
}
modalApi.lock();
//
const values =
(await formApi.getValues()) as MesQcDefectRecordApi.DefectRecord;
const payload: MesQcDefectRecordApi.DefectRecord = {
...values,
lineId: formData.value.lineId,
qcId: formData.value.qcId,
qcType: formData.value.qcType,
};
try {
const values =
(await formApi.getValues()) as MesQcDefectRecordApi.DefectRecord;
const payload: MesQcDefectRecordApi.DefectRecord = {
...values,
lineId: formData.value.lineId,
qcId: formData.value.qcId,
qcType: formData.value.qcType,
};
if (formType.value === 'update') {
await updateDefectRecord({ ...payload, id: formData.value.id });
message.success($t('common.updateSuccess'));
@ -123,6 +80,7 @@ const [Modal, modalApi] = useVbenModal({
await createDefectRecord(payload);
message.success($t('common.createSuccess'));
}
//
await modalApi.close();
emit('success');
} finally {
@ -134,18 +92,10 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
const data = modalApi.getData<OpenData>();
//
const data = modalApi.getData<CtxData>();
formType.value = data.formType;
if (data.id) {
// id
modalApi.lock();
try {
formData.value = await getDefectRecord(data.id);
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
} else {
if (!data.id) {
// 1
formData.value = {
lineId: data.lineId,
@ -153,6 +103,15 @@ const [Modal, modalApi] = useVbenModal({
qcType: data.qcType,
};
await formApi.setValues({ quantity: 1 });
return;
}
modalApi.lock();
try {
formData.value = await getDefectRecord(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});

View File

@ -5,7 +5,6 @@ import type { MesQcDefectRecordApi } from '#/api/mes/qc/defectrecord';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { message } from 'ant-design-vue';
@ -16,22 +15,22 @@ import {
} from '#/api/mes/qc/defectrecord';
import { $t } from '#/locales';
import { useDefectRecordInlineGridColumns } from './data';
import DefectRecordInlineForm from './defect-record-inline-form.vue';
defineOptions({ name: 'DefectRecordInlineList' });
const emit = defineEmits(['success']);
// TODO @AI QcData
interface OpenData {
interface CtxData {
formType?: string;
lineId: number;
qcId: number;
qcType: number;
}
const qcData = ref<OpenData>();
const isReadonly = computed(() => qcData.value?.formType === 'detail');
const ctxData = ref<CtxData>();
const isReadonly = computed(() => ctxData.value?.formType === 'detail');
/** 刷新表格 */
function handleRefresh() {
@ -46,31 +45,31 @@ const [FormModal, formModalApi] = useVbenModal({
/** 新增缺陷 */
function handleCreate() {
if (!qcData.value) {
if (!ctxData.value) {
return;
}
formModalApi
.setData({
formType: 'create',
lineId: qcData.value.lineId,
qcId: qcData.value.qcId,
qcType: qcData.value.qcType,
lineId: ctxData.value.lineId,
qcId: ctxData.value.qcId,
qcType: ctxData.value.qcType,
})
.open();
}
/** 编辑缺陷 */
function handleEdit(row: MesQcDefectRecordApi.DefectRecord) {
if (!qcData.value) {
if (!ctxData.value) {
return;
}
formModalApi
.setData({
formType: 'update',
id: row.id,
lineId: qcData.value.lineId,
qcId: qcData.value.qcId,
qcType: qcData.value.qcType,
lineId: ctxData.value.lineId,
qcId: ctxData.value.qcId,
qcType: ctxData.value.qcType,
})
.open();
}
@ -90,55 +89,22 @@ async function handleDelete(row: MesQcDefectRecordApi.DefectRecord) {
}
}
// TODO @AI data.ts
// TODO @AI
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: [
{
field: 'name',
title: '缺陷描述',
minWidth: 200,
},
{
field: 'level',
title: '缺陷等级',
width: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_DEFECT_LEVEL },
},
},
{
field: 'quantity',
title: '缺陷数量',
width: 100,
},
{
field: 'remark',
title: '备注',
minWidth: 150,
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
],
columns: useDefectRecordInlineGridColumns(),
height: 320,
proxyConfig: {
ajax: {
query: async ({ page }) => {
if (!qcData.value) {
if (!ctxData.value) {
return { list: [], total: 0 };
}
return await getDefectRecordPage({
lineId: qcData.value.lineId,
lineId: ctxData.value.lineId,
pageNo: page.currentPage,
pageSize: page.pageSize,
qcId: qcData.value.qcId,
qcType: qcData.value.qcType,
qcId: ctxData.value.qcId,
qcType: ctxData.value.qcType,
});
},
},
@ -158,10 +124,10 @@ const [Modal, modalApi] = useVbenModal({
showConfirmButton: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
qcData.value = undefined;
ctxData.value = undefined;
return;
}
qcData.value = modalApi.getData<OpenData>();
ctxData.value = modalApi.getData<CtxData>();
await gridApi.query();
},
});

View File

@ -0,0 +1,89 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcIndicatorResultApi } from '#/api/mes/qc/indicatorresult';
import { h } from 'vue';
import { Button } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { MesAutoCodeRuleCode } from '#/views/mes/utils/constants';
/** 表单类型 */
export type FormType = 'create' | 'update';
/** 新增/修改检测结果的表单 */
export function useQcIndicatorResultFormSchema(
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_INDICATOR_RESULT_CODE,
);
await formApi?.setFieldValue('code', code);
} catch (error) {
console.error(error);
}
},
},
{ default: () => '生成' },
),
},
{
fieldName: 'sn',
label: '物资 SN',
component: 'Input',
componentProps: {
placeholder: '请输入物资 SN',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}
/** 检测结果列表的字段 */
export function useQcIndicatorResultGridColumns(): VxeTableGridOptions<MesQcIndicatorResultApi.IndicatorResult>['columns'] {
return [
{ field: 'code', title: '样品编号', width: 200 },
{ field: 'sn', title: '物资SN', minWidth: 200 },
{ field: 'remark', title: '备注', minWidth: 200 },
{
title: '操作',
width: 150,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@ -1,13 +1,14 @@
<script lang="ts" setup>
import type { FormType } from './data';
import type { MesQcIndicatorResultApi } from '#/api/mes/qc/indicatorresult';
import { computed, h, ref } from 'vue';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import {
Form as AForm,
Button,
Divider,
Input,
InputNumber,
@ -17,29 +18,29 @@ import {
} from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import {
createIndicatorResult,
getIndicatorResultDetail,
updateIndicatorResult,
} from '#/api/mes/qc/indicatorresult';
import { $t } from '#/locales';
import {
MesAutoCodeRuleCode,
MesQcResultValueType,
} from '#/views/mes/utils/constants';
import { MesQcResultValueType } from '#/views/mes/utils/constants';
interface OpenData {
formType: 'create' | 'update';
import { useQcIndicatorResultFormSchema } from './data';
interface CtxData {
formType: FormType;
id?: number;
qcId: number;
qcType: number;
}
defineOptions({ name: 'QcIndicatorResultForm' });
const emit = defineEmits(['success']);
const formType = ref<'create' | 'update'>('create');
const formType = ref<FormType>('create');
const items = ref<MesQcIndicatorResultApi.IndicatorResultDetail[]>([]);
const ctxData = ref<OpenData>();
const ctxData = ref<CtxData>();
const getTitle = computed(() =>
formType.value === 'update'
@ -55,66 +56,10 @@ const [Form, formApi] = useVbenForm({
formItemClass: 'col-span-1',
labelWidth: 100,
},
wrapperClass: 'grid-cols-2',
layout: 'horizontal',
// TODO @AI data.ts
// TODO @AI
schema: [
{
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_INDICATOR_RESULT_CODE,
);
await formApi.setFieldValue('code', code);
} catch (error) {
console.error(error);
}
},
},
{ default: () => '生成' },
),
},
{
fieldName: 'sn',
label: '物资 SN',
component: 'Input',
componentProps: {
placeholder: '请输入物资 SN',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
],
schema: [],
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
/** 解析字典选项字符串value=label;value=label */
@ -132,7 +77,6 @@ function parseValueOptions(spec?: string) {
});
}
// TODO @AI
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
if (!ctxData.value) {
@ -143,31 +87,33 @@ const [Modal, modalApi] = useVbenModal({
return;
}
modalApi.lock();
try {
const head = (await formApi.getValues()) as MesQcIndicatorResultApi.IndicatorResult;
const submitItems = items.value.map((item) => {
const submit: MesQcIndicatorResultApi.IndicatorResultDetail = {
id: item.id,
indicatorId: item.indicatorId,
remark: item.remark,
};
if (
item.valueType === MesQcResultValueType.FLOAT ||
item.valueType === MesQcResultValueType.INTEGER
) {
submit.value =
item.valueNumber == null ? undefined : String(item.valueNumber);
} else {
submit.value = item.value;
}
return submit;
});
const payload: MesQcIndicatorResultApi.IndicatorResult = {
...head,
qcId: ctxData.value.qcId,
qcType: ctxData.value.qcType,
items: submitItems,
//
const head =
(await formApi.getValues()) as MesQcIndicatorResultApi.IndicatorResult;
const submitItems = items.value.map((item) => {
const submit: MesQcIndicatorResultApi.IndicatorResultDetail = {
id: item.id,
indicatorId: item.indicatorId,
remark: item.remark,
};
if (
item.valueType === MesQcResultValueType.FLOAT ||
item.valueType === MesQcResultValueType.INTEGER
) {
submit.value =
item.valueNumber == null ? undefined : String(item.valueNumber);
} else {
submit.value = item.value;
}
return submit;
});
const payload: MesQcIndicatorResultApi.IndicatorResult = {
...head,
items: submitItems,
qcId: ctxData.value.qcId,
qcType: ctxData.value.qcType,
};
try {
if (formType.value === 'update') {
await updateIndicatorResult(payload);
message.success($t('common.updateSuccess'));
@ -175,6 +121,7 @@ const [Modal, modalApi] = useVbenModal({
await createIndicatorResult(payload);
message.success($t('common.createSuccess'));
}
//
await modalApi.close();
emit('success');
} finally {
@ -187,7 +134,9 @@ const [Modal, modalApi] = useVbenModal({
items.value = [];
return;
}
const data = modalApi.getData<OpenData>();
formApi.setState({ schema: useQcIndicatorResultFormSchema(formApi) });
//
const data = modalApi.getData<CtxData>();
ctxData.value = data;
formType.value = data.formType;
modalApi.lock();
@ -207,11 +156,12 @@ const [Modal, modalApi] = useVbenModal({
? Number(item.value)
: undefined,
}));
// values
await formApi.setValues({
id: detail.id,
code: detail.code,
sn: detail.sn,
remark: detail.remark,
sn: detail.sn,
});
} finally {
modalApi.unlock();
@ -225,14 +175,16 @@ const [Modal, modalApi] = useVbenModal({
<div class="px-4">
<Form />
<Divider>检测值</Divider>
<!-- TODO @AI这里可以改成更大化使用 schema 方式么 -->
<!-- 检测值控件由每行 valueType 驱动逐行渲染FLOAT/INTEGER InputNumberDICT Select 并按 valueSpecification 解析选项 -->
<AForm :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<div
v-for="(item, index) in items"
:key="item.indicatorId ?? index"
class="mb-2"
>
<AForm.Item :label="`检测项${index + 1}${item.indicatorName ?? ''}`">
<AForm.Item
:label="`检测项${index + 1}${item.indicatorName ?? ''}`"
>
<InputNumber
v-if="
item.valueType === MesQcResultValueType.FLOAT ||

View File

@ -13,6 +13,7 @@ import {
} from '#/api/mes/qc/indicatorresult';
import { $t } from '#/locales';
import { useQcIndicatorResultGridColumns } from './data';
import QcIndicatorResultForm from './qc-indicator-result-form.vue';
const props = defineProps<{
@ -69,21 +70,9 @@ async function handleDelete(row: MesQcIndicatorResultApi.IndicatorResult) {
}
}
// TODO @AI data.ts
// TODO @AI
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: [
{ field: 'code', title: '样品编号', width: 200 },
{ field: 'sn', title: '物资SN', minWidth: 200 },
{ field: 'remark', title: '备注', minWidth: 200 },
{
title: '操作',
width: 150,
fixed: 'right',
slots: { default: 'actions' },
},
],
columns: useQcIndicatorResultGridColumns(),
height: 360,
proxyConfig: {
ajax: {
@ -101,8 +90,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
},
rowConfig: {
keyField: 'id',
isHover: true,
keyField: 'id',
},
toolbarConfig: {
refresh: true,

View File

@ -62,17 +62,12 @@ const [Form, formApi] = useVbenForm({
showDefaultActions: false,
});
// TODO @AI handleRefresh
/** 重新加载主表头数据(用于子表变更后刷新缺陷统计) */
async function reloadHead() {
/** 子表变更后刷新主表头数据(缺陷统计等) */
async function handleRefresh() {
if (!formData.value?.id) {
return;
}
try {
formData.value = await getIqc(formData.value.id);
} catch (error) {
console.error('[IqcForm] reload head failed:', error);
}
formData.value = await getIqc(formData.value.id);
}
/** 提交表单 */
@ -222,7 +217,7 @@ const [Modal, modalApi] = useVbenModal({
<LineList
:form-type="formType"
:iqc-id="formData.id"
@refresh="reloadHead"
@refresh="handleRefresh"
/>
</Tabs.TabPane>
<Tabs.TabPane key="result" tab="检测结果">

View File

@ -16,6 +16,13 @@ export namespace MesQcDefectRecordApi {
}
}
/** 查询质检缺陷记录 */
export function getDefectRecord(id: number) {
return requestClient.get<MesQcDefectRecordApi.DefectRecord>(
`/mes/qc/defect-record/get?id=${id}`,
);
}
/** 查询质检缺陷记录分页 */
export function getDefectRecordPage(
params: PageParam & { lineId?: number; qcId?: number; qcType?: number; },