commit
e943f4fcfd
|
|
@ -14,6 +14,9 @@ export namespace AlertConfigApi {
|
|||
receiveUserIds?: number[];
|
||||
receiveUserNames?: string[];
|
||||
receiveTypes?: number[];
|
||||
smsTemplateCode?: string;
|
||||
mailTemplateCode?: string;
|
||||
notifyTemplateCode?: string;
|
||||
createTime?: Date;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export namespace MemberUserApi {
|
|||
loginIp: string;
|
||||
mark: string;
|
||||
mobile: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
nickname?: string;
|
||||
registerIp: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MesProTaskIssueApi {
|
||||
/** MES 生产任务投料 */
|
||||
export interface TaskIssue {
|
||||
id?: number; // 编号
|
||||
taskId?: number; // 生产任务编号
|
||||
workOrderId?: number; // 生产工单编号
|
||||
workstationId?: number; // 工作站编号
|
||||
sourceDocType?: string; // 来源单据类型
|
||||
sourceDocId?: number; // 来源单据编号
|
||||
sourceLineId?: number; // 来源单据行编号
|
||||
sourceDocCode?: string; // 来源单据编码
|
||||
batchCode?: string; // 投料批次
|
||||
itemId?: number; // 产品物料编号
|
||||
itemName?: string; // 产品名称
|
||||
itemCode?: string; // 产品编码
|
||||
itemSpecification?: string; // 规格型号
|
||||
unitMeasureId?: number; // 单位编号
|
||||
unitMeasureName?: string; // 单位名称
|
||||
issuedQuantity?: number; // 总投料数量
|
||||
availableQuantity?: number; // 当前可用数量
|
||||
usedQuantity?: number; // 当前使用数量
|
||||
remark?: string; // 备注
|
||||
}
|
||||
|
||||
/** MES 生产任务投料分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
taskId?: number; // 生产任务编号
|
||||
workOrderId?: number; // 生产工单编号
|
||||
workstationId?: number; // 工作站编号
|
||||
itemId?: number; // 产品物料编号
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询生产任务投料分页 */
|
||||
export function getTaskIssuePage(params: MesProTaskIssueApi.PageParams) {
|
||||
return requestClient.get<PageResult<MesProTaskIssueApi.TaskIssue>>(
|
||||
'/mes/pro/task-issue/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询生产任务投料详情 */
|
||||
export function getTaskIssue(id: number) {
|
||||
return requestClient.get<MesProTaskIssueApi.TaskIssue>(
|
||||
`/mes/pro/task-issue/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增生产任务投料 */
|
||||
export function createTaskIssue(data: MesProTaskIssueApi.TaskIssue) {
|
||||
return requestClient.post('/mes/pro/task-issue/create', data);
|
||||
}
|
||||
|
||||
/** 修改生产任务投料 */
|
||||
export function updateTaskIssue(data: MesProTaskIssueApi.TaskIssue) {
|
||||
return requestClient.put('/mes/pro/task-issue/update', data);
|
||||
}
|
||||
|
||||
/** 删除生产任务投料 */
|
||||
export function deleteTaskIssue(id: number) {
|
||||
return requestClient.delete(`/mes/pro/task-issue/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 按生产任务查询投料列表 */
|
||||
export function getTaskIssueListByTask(taskId: number) {
|
||||
return requestClient.get<MesProTaskIssueApi.TaskIssue[]>(
|
||||
`/mes/pro/task-issue/list-by-task?taskId=${taskId}`,
|
||||
);
|
||||
}
|
||||
|
|
@ -5,17 +5,17 @@ import { requestClient } from '#/api/request';
|
|||
export namespace MesWmBarcodeApi {
|
||||
/** MES 条码清单 */
|
||||
export interface Barcode {
|
||||
id?: number;
|
||||
configId?: number;
|
||||
format?: number;
|
||||
bizType?: number;
|
||||
content?: string;
|
||||
bizId?: number;
|
||||
bizCode?: string;
|
||||
bizName?: string;
|
||||
status?: number;
|
||||
remark?: string;
|
||||
createTime?: Date;
|
||||
id?: number; // 条码编号
|
||||
configId?: number; // 条码配置编号
|
||||
format?: number; // 条码格式
|
||||
bizType?: number; // 业务类型
|
||||
content?: string; // 条码内容
|
||||
bizId?: number; // 业务对象编号
|
||||
bizCode?: string; // 业务对象编码
|
||||
bizName?: string; // 业务对象名称
|
||||
status?: number; // 状态
|
||||
remark?: string; // 备注
|
||||
createTime?: Date; // 创建时间
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,22 +39,22 @@ export namespace MesWmBatchApi {
|
|||
|
||||
/** MES 批次分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
code?: string;
|
||||
itemId?: number;
|
||||
vendorId?: number;
|
||||
clientId?: number;
|
||||
workOrderId?: number;
|
||||
taskId?: number;
|
||||
workstationId?: number;
|
||||
toolId?: number;
|
||||
moldId?: number;
|
||||
salesOrderCode?: string;
|
||||
purchaseOrderCode?: string;
|
||||
lotNumber?: string;
|
||||
qualityStatus?: number;
|
||||
produceDate?: string[];
|
||||
expireDate?: string[];
|
||||
receiptDate?: string[];
|
||||
code?: string; // 批次号
|
||||
itemId?: number; // 物料编号
|
||||
vendorId?: number; // 供应商编号
|
||||
clientId?: number; // 客户编号
|
||||
workOrderId?: number; // 工单编号
|
||||
taskId?: number; // 生产任务编号
|
||||
workstationId?: number; // 工作站编号
|
||||
toolId?: number; // 工具编号
|
||||
moldId?: number; // 模具编号
|
||||
salesOrderCode?: string; // 销售订单号
|
||||
purchaseOrderCode?: string; // 采购订单号
|
||||
lotNumber?: string; // 批号
|
||||
qualityStatus?: number; // 质量状态
|
||||
produceDate?: string[]; // 生产日期
|
||||
expireDate?: string[]; // 过期日期
|
||||
receiptDate?: string[]; // 入库日期
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ export namespace MesWmSnApi {
|
|||
|
||||
/** MES SN 码分组分页查询参数 */
|
||||
export interface PageParams extends PageParam {
|
||||
uuid?: string;
|
||||
code?: string;
|
||||
itemId?: number;
|
||||
batchCode?: string;
|
||||
createTime?: string[];
|
||||
uuid?: string; // 分组 UUID
|
||||
code?: string; // SN 码
|
||||
itemId?: number; // 物料编号
|
||||
batchCode?: string; // 批次号
|
||||
createTime?: string[]; // 创建时间
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ export namespace SystemMailTemplateApi {
|
|||
createTime: Date;
|
||||
}
|
||||
|
||||
/** 邮件模版精简信息 */
|
||||
export interface MailTemplateSimple {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
/** 邮件发送信息 */
|
||||
export interface MailSendReqVO {
|
||||
toMails: string[];
|
||||
|
|
@ -35,6 +42,13 @@ export function getMailTemplatePage(params: PageParam) {
|
|||
);
|
||||
}
|
||||
|
||||
/** 查询邮件模版精简列表 */
|
||||
export function getSimpleMailTemplateList() {
|
||||
return requestClient.get<SystemMailTemplateApi.MailTemplateSimple[]>(
|
||||
'/system/mail-template/simple-list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询邮件模版详情 */
|
||||
export function getMailTemplate(id: number) {
|
||||
return requestClient.get<SystemMailTemplateApi.MailTemplate>(
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ export namespace SystemNotifyTemplateApi {
|
|||
remark: string;
|
||||
}
|
||||
|
||||
/** 站内信模板精简信息 */
|
||||
export interface NotifyTemplateSimple {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
/** 发送站内信请求 */
|
||||
export interface NotifySendReqVO {
|
||||
userId: number;
|
||||
|
|
@ -33,6 +40,13 @@ export function getNotifyTemplatePage(params: PageParam) {
|
|||
);
|
||||
}
|
||||
|
||||
/** 查询站内信模板精简列表 */
|
||||
export function getSimpleNotifyTemplateList() {
|
||||
return requestClient.get<SystemNotifyTemplateApi.NotifyTemplateSimple[]>(
|
||||
'/system/notify-template/simple-list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询站内信模板详情 */
|
||||
export function getNotifyTemplate(id: number) {
|
||||
return requestClient.get<SystemNotifyTemplateApi.NotifyTemplate>(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ export namespace SystemSmsTemplateApi {
|
|||
createTime?: Date;
|
||||
}
|
||||
|
||||
/** 短信模板精简信息 */
|
||||
export interface SmsTemplateSimple {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
/** 发送短信请求 */
|
||||
export interface SmsSendReqVO {
|
||||
mobile: string;
|
||||
|
|
@ -35,6 +42,13 @@ export function getSmsTemplatePage(params: PageParam) {
|
|||
);
|
||||
}
|
||||
|
||||
/** 查询短信模板精简列表 */
|
||||
export function getSimpleSmsTemplateList() {
|
||||
return requestClient.get<SystemSmsTemplateApi.SmsTemplateSimple[]>(
|
||||
'/system/sms-template/simple-list',
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询短信模板详情 */
|
||||
export function getSmsTemplate(id: number) {
|
||||
return requestClient.get<SystemSmsTemplateApi.SmsTemplate>(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function useAreaSelectRule() {
|
|||
title: label,
|
||||
info: '',
|
||||
$required: false,
|
||||
modelField: 'value', // 特殊:ele 里是 model-value,antd 里是 value
|
||||
modelField: 'value', // Ant Design Vue 组件使用 value;web-ele 自定义组件使用默认 modelValue
|
||||
};
|
||||
},
|
||||
props(_: any, { t }: any) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function useDictSelectRule() {
|
|||
title: label,
|
||||
info: '',
|
||||
$required: false,
|
||||
modelField: 'value', // 特殊:ele 里是 model-value,antd 里是 value
|
||||
modelField: 'value', // Ant Design Vue 组件使用 value;web-ele 自定义组件使用默认 modelValue
|
||||
};
|
||||
},
|
||||
props(_: any, { t }: any) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function useIframeRule() {
|
|||
title: label,
|
||||
info: '',
|
||||
$required: false,
|
||||
modelField: 'value', // 特殊:ele 里是 model-value,antd 里是 value
|
||||
modelField: 'value', // Ant Design Vue 组件使用 value;web-ele 自定义组件使用默认 modelValue
|
||||
};
|
||||
},
|
||||
props(_: any, { t }: any) {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
/** 获取打印数据 */
|
||||
async function fetchPrintData(id: string) {
|
||||
printData.value = await getProcessInstancePrintData(id);
|
||||
printTime.value = formatDate(new Date(), 'YYYY-MM-DD HH:mm');
|
||||
initPrintDataMap();
|
||||
await parseFormFields();
|
||||
}
|
||||
|
|
@ -154,7 +155,7 @@ function tryFormatDate(value: unknown) {
|
|||
return '';
|
||||
}
|
||||
const formatted = formatDate(value as Date | number | string);
|
||||
return formatted === 'Invalid Date' ? String(value) : formatted;
|
||||
return formatted === 'Invalid Date' ? escapeHtml(value) : formatted;
|
||||
}
|
||||
|
||||
function formatDateValue(value: unknown) {
|
||||
|
|
@ -164,6 +165,15 @@ function formatDateValue(value: unknown) {
|
|||
return tryFormatDate(value);
|
||||
}
|
||||
|
||||
function escapeHtml(value: unknown) {
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function formatPrimitiveValue(value: unknown): string {
|
||||
if (isEmptyValue(value)) {
|
||||
return '';
|
||||
|
|
@ -185,9 +195,9 @@ function formatPrimitiveValue(value: unknown): string {
|
|||
getRecordValue(record, 'url') ??
|
||||
getRecordValue(record, 'value') ??
|
||||
JSON.stringify(value);
|
||||
return String(displayValue);
|
||||
return escapeHtml(displayValue);
|
||||
}
|
||||
return String(value);
|
||||
return escapeHtml(value);
|
||||
}
|
||||
|
||||
function createImageHtml(url: string) {
|
||||
|
|
@ -248,7 +258,7 @@ function mapValuesWithOptions(
|
|||
(option) =>
|
||||
option?.value === item || String(option?.value ?? '') === String(item),
|
||||
);
|
||||
return matched?.label ?? String(item);
|
||||
return escapeHtml(matched?.label ?? String(item));
|
||||
})
|
||||
.filter((s) => isNotEmptyString(s));
|
||||
return labels.join(', ');
|
||||
|
|
@ -276,9 +286,11 @@ function mapValueWithLabelMap(
|
|||
) {
|
||||
const values = toValueArray(value);
|
||||
const labels = values
|
||||
.map((item) => labelMap.get(String(item)) ?? String(item))
|
||||
.map((item) => escapeHtml(labelMap.get(String(item)) ?? String(item)))
|
||||
.filter((s) => isNotEmptyString(s));
|
||||
return labels.length > 0 ? labels.join(separator) : formatPrimitiveValue(values);
|
||||
return labels.length > 0
|
||||
? labels.join(escapeHtml(separator))
|
||||
: formatPrimitiveValue(values);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -380,7 +392,12 @@ function formatPrintField(
|
|||
const options = getDictOptions(dictType, valueType);
|
||||
return mapValuesWithOptions(value, options);
|
||||
}
|
||||
case 'FileUpload': {
|
||||
case 'Editor':
|
||||
case 'Tinymce': {
|
||||
return isEmptyValue(value) ? '' : String(value);
|
||||
}
|
||||
case 'FileUpload':
|
||||
case 'UploadFile': {
|
||||
return renderFileListHtml(value);
|
||||
}
|
||||
case 'IframeComponent': {
|
||||
|
|
@ -394,21 +411,20 @@ function formatPrintField(
|
|||
}
|
||||
case 'ImagesUpload':
|
||||
case 'ImageUpload':
|
||||
case 'UploadImg': {
|
||||
case 'UploadImg':
|
||||
case 'UploadImgs': {
|
||||
return renderImageListHtml(value);
|
||||
}
|
||||
case 'switch': {
|
||||
if (isEmptyValue(value)) return '否';
|
||||
const checkedVal = getRuleProp(rule, 'checkedValue');
|
||||
const checkedVal =
|
||||
getRuleProp(rule, 'checkedValue') ?? getRuleProp(rule, 'activeValue');
|
||||
const isChecked =
|
||||
checkedVal !== undefined && checkedVal !== null
|
||||
? value === checkedVal
|
||||
: Boolean(value);
|
||||
return isChecked ? '是' : '否';
|
||||
}
|
||||
case 'Tinymce': {
|
||||
return isEmptyValue(value) ? '' : String(value);
|
||||
}
|
||||
case 'UserSelect': {
|
||||
if (String(getRuleProp(rule, 'returnType')) === 'name') {
|
||||
return formatPrimitiveValue(value);
|
||||
|
|
@ -481,7 +497,7 @@ function getPrintTemplateHTML() {
|
|||
const headTd = document.createElement('td');
|
||||
headTd.setAttribute('colspan', '2');
|
||||
headTd.setAttribute('class', 'border border-black p-1.5 text-center');
|
||||
headTd.innerHTML = '流程记录';
|
||||
headTd.textContent = '流程记录';
|
||||
headTr.append(headTd);
|
||||
processRecordTable.append(headTr);
|
||||
|
||||
|
|
@ -489,10 +505,10 @@ function getPrintTemplateHTML() {
|
|||
const tr = document.createElement('tr');
|
||||
const td1 = document.createElement('td');
|
||||
td1.setAttribute('class', 'border border-black p-1.5');
|
||||
td1.innerHTML = item.name;
|
||||
td1.textContent = item.name;
|
||||
const td2 = document.createElement('td');
|
||||
td2.setAttribute('class', 'border border-black p-1.5');
|
||||
td2.innerHTML = item.description;
|
||||
td2.textContent = item.description;
|
||||
tr.append(td1);
|
||||
tr.append(td2);
|
||||
processRecordTable.append(tr);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ const [Grid] = useVbenVxeGrid({
|
|||
return await getCluePage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
sceneType: 1,
|
||||
transformStatus: false,
|
||||
...formValues,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,12 +2,25 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AlertConfigApi } from '#/api/iot/alert/config';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
IotAlertReceiveTypeEnum,
|
||||
} from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { getSimpleRuleSceneList } from '#/api/iot/rule/scene';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
import { MailTemplateSelect } from '#/views/system/mail/template/components';
|
||||
import { NotifyTemplateSelect } from '#/views/system/notify/template/components';
|
||||
import { SmsTemplateSelect } from '#/views/system/sms/template/components';
|
||||
|
||||
function hasReceiveType(values: Partial<Record<string, any>>, type: number) {
|
||||
return Array.isArray(values.receiveTypes) && values.receiveTypes.includes(type);
|
||||
}
|
||||
|
||||
/** 新增/修改告警配置的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
|
|
@ -100,6 +113,60 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
defaultValue: [],
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'smsTemplateCode',
|
||||
label: '短信模板',
|
||||
component: markRaw(SmsTemplateSelect),
|
||||
dependencies: {
|
||||
triggerFields: ['receiveTypes'],
|
||||
show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.SMS),
|
||||
trigger: async (values, formApi) => {
|
||||
if (
|
||||
!hasReceiveType(values, IotAlertReceiveTypeEnum.SMS) &&
|
||||
values.smsTemplateCode
|
||||
) {
|
||||
await formApi.setFieldValue('smsTemplateCode', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'mailTemplateCode',
|
||||
label: '邮件模板',
|
||||
component: markRaw(MailTemplateSelect),
|
||||
dependencies: {
|
||||
triggerFields: ['receiveTypes'],
|
||||
show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.MAIL),
|
||||
trigger: async (values, formApi) => {
|
||||
if (
|
||||
!hasReceiveType(values, IotAlertReceiveTypeEnum.MAIL) &&
|
||||
values.mailTemplateCode
|
||||
) {
|
||||
await formApi.setFieldValue('mailTemplateCode', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'notifyTemplateCode',
|
||||
label: '站内信模板',
|
||||
component: markRaw(NotifyTemplateSelect),
|
||||
dependencies: {
|
||||
triggerFields: ['receiveTypes'],
|
||||
show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY),
|
||||
trigger: async (values, formApi) => {
|
||||
if (
|
||||
!hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY) &&
|
||||
values.notifyTemplateCode
|
||||
) {
|
||||
await formApi.setFieldValue('notifyTemplateCode', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,7 @@ import type { RuleSceneApi } from '#/api/iot/rule/scene';
|
|||
import { computed, nextTick, reactive, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
IotRuleSceneActionTypeEnum,
|
||||
IotRuleSceneTriggerConditionTypeEnum,
|
||||
IotRuleSceneTriggerTimeOperatorEnum,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
isDeviceTrigger,
|
||||
} from '@vben/constants';
|
||||
import { CronUtils } from '@vben/utils';
|
||||
import { CommonStatusEnum, IotRuleSceneTriggerTypeEnum } from '@vben/constants';
|
||||
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
|
||||
|
|
@ -22,6 +14,10 @@ import {
|
|||
updateSceneRule,
|
||||
} from '#/api/iot/rule/scene';
|
||||
import { $t } from '#/locales';
|
||||
import {
|
||||
validateSceneRuleActions,
|
||||
validateSceneRuleTriggers,
|
||||
} from '#/views/iot/utils/scene-rule';
|
||||
|
||||
import ActionSection from '../form/sections/action-section.vue';
|
||||
import BasicInfoSection from '../form/sections/basic-info-section.vue';
|
||||
|
|
@ -47,6 +43,16 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||
} catch {
|
||||
return;
|
||||
}
|
||||
const triggerError = validateSceneRuleTriggers(formData.value.triggers);
|
||||
if (triggerError) {
|
||||
message.error(triggerError);
|
||||
return;
|
||||
}
|
||||
const actionError = validateSceneRuleActions(formData.value.actions);
|
||||
if (actionError) {
|
||||
message.error(actionError);
|
||||
return;
|
||||
}
|
||||
drawerApi.lock();
|
||||
try {
|
||||
const data = { ...formData.value } as RuleSceneApi.SceneRule;
|
||||
|
|
@ -117,194 +123,21 @@ function normalizeFormData(result: any): RuleSceneApi.SceneRule {
|
|||
|
||||
/** 触发器校验 */
|
||||
function validateTriggers(_rule: any, value: any, callback: any) {
|
||||
if (!value || !Array.isArray(value) || value.length === 0) {
|
||||
callback(new Error('至少需要一个触发器'));
|
||||
const error = validateSceneRuleTriggers(value);
|
||||
if (error) {
|
||||
callback(new Error(error));
|
||||
return;
|
||||
}
|
||||
for (const [i, trigger] of value.entries()) {
|
||||
if (!trigger.type) {
|
||||
callback(new Error(`触发器 ${i + 1}:触发器类型不能为空`));
|
||||
return;
|
||||
}
|
||||
if (isDeviceTrigger(trigger.type)) {
|
||||
if (!trigger.productId) {
|
||||
callback(new Error(`触发器 ${i + 1}:产品不能为空`));
|
||||
return;
|
||||
}
|
||||
// deviceId = 0 表示「全部设备」(DEVICE_SELECTOR_OPTIONS.ALL_DEVICES),是合法值;仅 undefined / null 视为未选
|
||||
if (trigger.deviceId === undefined || trigger.deviceId === null) {
|
||||
callback(new Error(`触发器 ${i + 1}:设备不能为空`));
|
||||
return;
|
||||
}
|
||||
const isStateUpdate =
|
||||
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE;
|
||||
if (!isStateUpdate && !trigger.identifier) {
|
||||
callback(new Error(`触发器 ${i + 1}:物模型标识符不能为空`));
|
||||
return;
|
||||
}
|
||||
// 事件上报 / 服务调用:operator 由前端自动设为 '=',参数值留空表示「事件 / 调用发生即匹配」
|
||||
const isEventOrService =
|
||||
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
|
||||
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
|
||||
if (!isEventOrService) {
|
||||
if (!trigger.operator) {
|
||||
callback(new Error(`触发器 ${i + 1}:操作符不能为空`));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
trigger.value === undefined ||
|
||||
trigger.value === null ||
|
||||
trigger.value === ''
|
||||
) {
|
||||
callback(new Error(`触发器 ${i + 1}:参数值不能为空`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
|
||||
if (!trigger.cronExpression) {
|
||||
callback(new Error(`触发器 ${i + 1}:CRON 表达式不能为空`));
|
||||
return;
|
||||
}
|
||||
if (!CronUtils.validate(trigger.cronExpression)) {
|
||||
callback(new Error(`触发器 ${i + 1}:CRON 表达式格式不正确`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 递归校验 conditionGroups(嵌套条件组)
|
||||
if (trigger.conditionGroups?.length) {
|
||||
for (const [gi, group] of trigger.conditionGroups.entries()) {
|
||||
if (!Array.isArray(group) || group.length === 0) {
|
||||
callback(
|
||||
new Error(`触发器 ${i + 1}:条件组 ${gi + 1} 不能为空`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (const [ci, condition] of group.entries()) {
|
||||
const prefix = `触发器 ${i + 1} 条件组 ${gi + 1} 条件 ${ci + 1}`;
|
||||
if (!condition.type) {
|
||||
callback(new Error(`${prefix}:条件类型不能为空`));
|
||||
return;
|
||||
}
|
||||
const isDeviceStatus =
|
||||
condition.type ===
|
||||
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS;
|
||||
const isDeviceProperty =
|
||||
condition.type ===
|
||||
IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY;
|
||||
const isCurrentTime =
|
||||
condition.type ===
|
||||
IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME;
|
||||
if (isDeviceStatus || isDeviceProperty) {
|
||||
if (!condition.productId) {
|
||||
callback(new Error(`${prefix}:产品不能为空`));
|
||||
return;
|
||||
}
|
||||
// deviceId = 0 表示「全部设备」(DEVICE_SELECTOR_OPTIONS.ALL_DEVICES),是合法值
|
||||
if (
|
||||
condition.deviceId === undefined ||
|
||||
condition.deviceId === null
|
||||
) {
|
||||
callback(new Error(`${prefix}:设备不能为空`));
|
||||
return;
|
||||
}
|
||||
if (isDeviceProperty && !condition.identifier) {
|
||||
callback(new Error(`${prefix}:物模型标识符不能为空`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!condition.operator) {
|
||||
callback(new Error(`${prefix}:操作符不能为空`));
|
||||
return;
|
||||
}
|
||||
// 设备状态:param 是状态值(必填);设备属性:param 是比较值(必填)
|
||||
if (
|
||||
(isDeviceStatus || isDeviceProperty) &&
|
||||
(condition.param === undefined ||
|
||||
condition.param === null ||
|
||||
condition.param === '')
|
||||
) {
|
||||
callback(
|
||||
new Error(
|
||||
`${prefix}:${isDeviceStatus ? '设备状态' : '比较值'}不能为空`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 当前时间:TODAY 不需要 param;BETWEEN_TIME 需要双段「v1,v2」;其它需要单段
|
||||
if (isCurrentTime) {
|
||||
const op = condition.operator;
|
||||
if (op === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {
|
||||
// TODAY 无需 param
|
||||
} else if (
|
||||
op === IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value
|
||||
) {
|
||||
const parts = condition.param
|
||||
? String(condition.param).split(',')
|
||||
: [];
|
||||
if (parts.length < 2 || !parts[0] || !parts[1]) {
|
||||
callback(new Error(`${prefix}:起止时间不能为空`));
|
||||
return;
|
||||
}
|
||||
} else if (!condition.param) {
|
||||
callback(new Error(`${prefix}:时间值不能为空`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
/** 执行器校验 */
|
||||
function validateActions(_rule: any, value: any, callback: any) {
|
||||
if (!value || !Array.isArray(value) || value.length === 0) {
|
||||
callback(new Error('至少需要一个执行器'));
|
||||
const error = validateSceneRuleActions(value);
|
||||
if (error) {
|
||||
callback(new Error(error));
|
||||
return;
|
||||
}
|
||||
for (const [i, action] of value.entries()) {
|
||||
if (!action.type) {
|
||||
callback(new Error(`执行器 ${i + 1}:执行器类型不能为空`));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
|
||||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
) {
|
||||
if (!action.productId) {
|
||||
callback(new Error(`执行器 ${i + 1}:产品不能为空`));
|
||||
return;
|
||||
}
|
||||
// deviceId = 0 表示「全部设备」(DEVICE_SELECTOR_OPTIONS.ALL_DEVICES);
|
||||
// 后端 IotDevicePropertySetSceneRuleAction / IotDeviceServiceInvokeSceneRuleAction
|
||||
// 均支持广播执行,因此 0 是合法值,仅 undefined / null 视为未选
|
||||
if (action.deviceId === undefined || action.deviceId === null) {
|
||||
callback(new Error(`执行器 ${i + 1}:设备不能为空`));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE &&
|
||||
!action.identifier
|
||||
) {
|
||||
callback(new Error(`执行器 ${i + 1}:服务不能为空`));
|
||||
return;
|
||||
}
|
||||
if (!action.params || Object.keys(action.params).length === 0) {
|
||||
callback(new Error(`执行器 ${i + 1}:参数配置不能为空`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 仅恢复告警动作需要选择已有告警配置;触发告警动作不需要预选 alertConfigId
|
||||
if (
|
||||
action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER &&
|
||||
!action.alertConfigId
|
||||
) {
|
||||
callback(new Error(`执行器 ${i + 1}:告警配置不能为空`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,401 @@
|
|||
import type { RuleSceneApi } from '#/api/iot/rule/scene';
|
||||
|
||||
import {
|
||||
IotRuleSceneActionTypeEnum,
|
||||
IotRuleSceneTriggerConditionTypeEnum,
|
||||
IotRuleSceneTriggerTimeOperatorEnum,
|
||||
IotRuleSceneTriggerTypeEnum,
|
||||
isDeviceTrigger,
|
||||
} from '@vben/constants';
|
||||
import { CronUtils, isEmptyVal, isObject } from '@vben/utils';
|
||||
|
||||
/**
|
||||
* 判断普通 ID 选择值是否缺失。
|
||||
*
|
||||
* 产品、告警配置等普通业务 ID 应为正数;`0` 不代表有效业务数据。
|
||||
*
|
||||
* @param value 普通业务 ID
|
||||
* @returns 是否缺失
|
||||
*/
|
||||
function isRequiredIdMissing(value: unknown): boolean {
|
||||
return !value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断设备 ID 选择值是否缺失。
|
||||
*
|
||||
* 场景联动的设备选择器中,`0` 表示「全部设备」,是合法值;因此这里只能把
|
||||
* `undefined`、`null`、空字符串视为未选择,不能使用普通 falsy 判断。
|
||||
*
|
||||
* @param value 设备 ID
|
||||
* @returns 是否缺失
|
||||
*/
|
||||
function isDeviceIdMissing(value: unknown): boolean {
|
||||
return isEmptyVal(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断执行器参数是否为空。
|
||||
*
|
||||
* 参数配置当前以 JSON 字符串为主,同时兼容历史对象值:
|
||||
* - 空字符串、空白字符串视为空;
|
||||
* - 空对象 `{}` 视为空;
|
||||
* - 非空 JSON 对象视为已配置;
|
||||
* - 非法 JSON 不在这里判空,交给 JSON 格式校验返回更准确的错误。
|
||||
*
|
||||
* @param params 执行器参数
|
||||
* @returns 是否为空
|
||||
*/
|
||||
export function isActionParamsEmpty(params?: unknown): boolean {
|
||||
if (isEmptyVal(params)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof params === 'string') {
|
||||
if (!params.trim()) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(params);
|
||||
if (isObject(parsed) && !Array.isArray(parsed)) {
|
||||
return Object.keys(parsed).length === 0;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isObject(params) && !Array.isArray(params)) {
|
||||
return Object.keys(params).length === 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断执行器参数是否为合法 JSON。
|
||||
*
|
||||
* 参数编辑组件正常会保存 JSON 字符串;编辑旧数据时可能仍然是对象,对象可直接视为合法。
|
||||
*
|
||||
* @param params 执行器参数
|
||||
* @returns 是否合法
|
||||
*/
|
||||
function isActionParamsJsonValid(params?: unknown): boolean {
|
||||
if (isObject(params)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
JSON.parse(String(params));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个附加子条件。
|
||||
*
|
||||
* 该方法用于提交前兜底校验,错误提示会带上 path,方便定位到第几个触发器、
|
||||
* 第几个条件组和第几个条件。
|
||||
*
|
||||
* @param condition 子条件配置
|
||||
* @param path 错误提示前缀
|
||||
* @returns 错误信息,通过则返回 null
|
||||
*/
|
||||
export function validateTriggerCondition(
|
||||
condition: RuleSceneApi.TriggerCondition,
|
||||
path: string,
|
||||
): null | string {
|
||||
if (!condition.type) {
|
||||
return `${path}:条件类型不能为空`;
|
||||
}
|
||||
|
||||
const isDeviceStatus =
|
||||
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS;
|
||||
const isDeviceProperty =
|
||||
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY;
|
||||
|
||||
// 设备状态和设备属性都必须先选择产品、设备;deviceId = 0 表示全部设备。
|
||||
if (isDeviceStatus || isDeviceProperty) {
|
||||
if (isRequiredIdMissing(condition.productId)) {
|
||||
return `${path}:产品不能为空`;
|
||||
}
|
||||
if (isDeviceIdMissing(condition.deviceId)) {
|
||||
return `${path}:设备不能为空`;
|
||||
}
|
||||
}
|
||||
|
||||
// 设备状态只校验操作符和状态枚举值。
|
||||
if (isDeviceStatus) {
|
||||
if (!condition.operator) {
|
||||
return `${path}:操作符不能为空`;
|
||||
}
|
||||
if (isEmptyVal(condition.param)) {
|
||||
return `${path}:设备状态不能为空`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 设备属性需要校验物模型标识符、操作符和比较值。
|
||||
if (isDeviceProperty) {
|
||||
if (!condition.identifier) {
|
||||
return `${path}:监控项不能为空`;
|
||||
}
|
||||
if (!condition.operator) {
|
||||
return `${path}:操作符不能为空`;
|
||||
}
|
||||
if (isEmptyVal(condition.param)) {
|
||||
return `${path}:比较值不能为空`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 当前时间按操作符动态判断 param 是否需要填写。
|
||||
if (condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME) {
|
||||
if (!condition.operator) {
|
||||
return `${path}:时间条件不能为空`;
|
||||
}
|
||||
|
||||
if (
|
||||
condition.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isEmptyVal(condition.param)) {
|
||||
return `${path}:时间值不能为空`;
|
||||
}
|
||||
|
||||
if (
|
||||
condition.operator ===
|
||||
IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value
|
||||
) {
|
||||
const parts = String(condition.param).split(',');
|
||||
if (!parts[0]?.trim() || !parts[1]?.trim()) {
|
||||
return `${path}:开始和结束时间不能为空`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验触发器的附加条件组。
|
||||
*
|
||||
* 条件组结构是二维数组:外层是 OR 关系的条件组,内层是 AND 关系的条件列表。
|
||||
* 这里逐组、逐条件返回第一条错误,避免一次性弹出过多提示。
|
||||
*
|
||||
* @param groups 附加条件组
|
||||
* @param triggerIndex 触发器在列表中的下标,用于生成可定位的错误提示
|
||||
* @returns 错误信息,通过则返回 null
|
||||
*/
|
||||
export function validateTriggerConditionGroups(
|
||||
groups: RuleSceneApi.TriggerCondition[][] | undefined,
|
||||
triggerIndex: number,
|
||||
): null | string {
|
||||
if (!groups?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const [groupIndex, group] of groups.entries()) {
|
||||
// 空条件组没有实际过滤条件,提交后语义不明确,需要拦截。
|
||||
if (!Array.isArray(group) || group.length === 0) {
|
||||
return `触发器 ${triggerIndex + 1}:条件组 ${groupIndex + 1} 不能为空`;
|
||||
}
|
||||
|
||||
for (const [conditionIndex, condition] of group.entries()) {
|
||||
const error = validateTriggerCondition(
|
||||
condition,
|
||||
`触发器 ${triggerIndex + 1} 条件组 ${groupIndex + 1} 条件 ${
|
||||
conditionIndex + 1
|
||||
}`,
|
||||
);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个触发器配置。
|
||||
*
|
||||
* 该方法用于场景联动表单提交前兜底校验,避免触发器配置没有独立表单项 prop 时漏掉必填项。
|
||||
* 校验逻辑需要和触发器主条件 UI 保持一致,同时继续校验附加条件组。
|
||||
*
|
||||
* @param trigger 触发器配置
|
||||
* @param index 触发器在列表中的下标,用于生成可定位的错误提示
|
||||
* @returns 错误信息,通过则返回 null
|
||||
*/
|
||||
export function validateTriggerItem(
|
||||
trigger: RuleSceneApi.Trigger,
|
||||
index: number,
|
||||
): null | string {
|
||||
const prefix = `触发器 ${index + 1}`;
|
||||
|
||||
if (!trigger.type) {
|
||||
return `${prefix}:触发器类型不能为空`;
|
||||
}
|
||||
|
||||
// 设备类触发器都有产品、设备两个基础字段;deviceId = 0 表示全部设备。
|
||||
if (isDeviceTrigger(trigger.type)) {
|
||||
if (isRequiredIdMissing(trigger.productId)) {
|
||||
return `${prefix}:产品不能为空`;
|
||||
}
|
||||
if (isDeviceIdMissing(trigger.deviceId)) {
|
||||
return `${prefix}:设备不能为空`;
|
||||
}
|
||||
|
||||
// 设备状态变化不依赖物模型标识符,只校验操作符和状态值。
|
||||
if (trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) {
|
||||
if (!trigger.operator) {
|
||||
return `${prefix}:操作符不能为空`;
|
||||
}
|
||||
if (isEmptyVal(trigger.value)) {
|
||||
return `${prefix}:设备状态不能为空`;
|
||||
}
|
||||
} else {
|
||||
if (!trigger.identifier) {
|
||||
return `${prefix}:监控项不能为空`;
|
||||
}
|
||||
|
||||
// 事件上报和服务调用只监听是否发生,不需要额外的操作符和比较值。
|
||||
const isEventOrService =
|
||||
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
|
||||
trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
|
||||
if (!isEventOrService) {
|
||||
if (!trigger.operator) {
|
||||
return `${prefix}:操作符不能为空`;
|
||||
}
|
||||
if (isEmptyVal(trigger.value)) {
|
||||
return `${prefix}:参数值不能为空`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 定时触发器需要 CRON 表达式,并继续校验 CRON 格式。
|
||||
if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
|
||||
if (!trigger.cronExpression) {
|
||||
return `${prefix}:CRON 表达式不能为空`;
|
||||
}
|
||||
if (!CronUtils.validate(trigger.cronExpression)) {
|
||||
return `${prefix}:CRON 表达式格式不正确`;
|
||||
}
|
||||
}
|
||||
|
||||
return validateTriggerConditionGroups(trigger.conditionGroups, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验触发器列表。
|
||||
*
|
||||
* 场景联动至少需要一个触发器;列表内逐条返回第一条错误,避免一次提交出现多条提示。
|
||||
*
|
||||
* @param triggers 触发器列表
|
||||
* @returns 错误信息,通过则返回 null
|
||||
*/
|
||||
export function validateSceneRuleTriggers(
|
||||
triggers?: RuleSceneApi.Trigger[],
|
||||
): null | string {
|
||||
if (!triggers?.length) {
|
||||
return '至少需要一个触发器';
|
||||
}
|
||||
|
||||
for (const [index, trigger] of triggers.entries()) {
|
||||
const error = validateTriggerItem(trigger, index);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个执行器配置。
|
||||
*
|
||||
* 该方法用于场景联动表单提交前兜底校验,避免执行器配置没有独立表单项 prop 时漏掉必填项。
|
||||
* 校验逻辑需要和执行器 UI 保持一致。
|
||||
*
|
||||
* @param action 执行器配置
|
||||
* @param index 执行器在列表中的下标,用于生成可定位的错误提示
|
||||
* @returns 错误信息,通过则返回 null
|
||||
*/
|
||||
export function validateActionItem(
|
||||
action: RuleSceneApi.Action,
|
||||
index: number,
|
||||
): null | string {
|
||||
const prefix = `执行器 ${index + 1}`;
|
||||
|
||||
if (!action.type) {
|
||||
return `${prefix}:执行器类型不能为空`;
|
||||
}
|
||||
|
||||
// 设备属性设置和设备服务调用都需要指定设备,并填写物模型参数。
|
||||
if (
|
||||
action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
|
||||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
|
||||
) {
|
||||
if (isRequiredIdMissing(action.productId)) {
|
||||
return `${prefix}:产品不能为空`;
|
||||
}
|
||||
if (isDeviceIdMissing(action.deviceId)) {
|
||||
return `${prefix}:设备不能为空`;
|
||||
}
|
||||
if (
|
||||
action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE &&
|
||||
!action.identifier
|
||||
) {
|
||||
return `${prefix}:服务不能为空`;
|
||||
}
|
||||
|
||||
if (isActionParamsEmpty(action.params)) {
|
||||
return `${prefix}:参数配置不能为空`;
|
||||
}
|
||||
if (!isActionParamsJsonValid(action.params)) {
|
||||
return `${prefix}:参数格式须为合法 JSON`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 告警恢复执行器需要绑定具体告警配置;触发告警不需要预选告警配置。
|
||||
if (
|
||||
action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER &&
|
||||
!action.alertConfigId
|
||||
) {
|
||||
return `${prefix}:告警配置不能为空`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验执行器列表。
|
||||
*
|
||||
* 场景联动至少需要一个执行器;列表内逐条返回第一条错误,避免一次提交出现多条提示。
|
||||
*
|
||||
* @param actions 执行器列表
|
||||
* @returns 错误信息,通过则返回 null
|
||||
*/
|
||||
export function validateSceneRuleActions(
|
||||
actions?: RuleSceneApi.Action[],
|
||||
): null | string {
|
||||
if (!actions?.length) {
|
||||
return '至少需要一个执行器';
|
||||
}
|
||||
|
||||
for (const [index, action] of actions.entries()) {
|
||||
const error = validateActionItem(action, index);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -36,6 +36,17 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'email',
|
||||
label: '邮箱',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
maxlength: 50,
|
||||
placeholder: '请输入邮箱',
|
||||
},
|
||||
rules: z.string().email('邮箱格式不正确').or(z.literal('')).optional(),
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
|
|
@ -153,6 +164,15 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'email',
|
||||
label: '邮箱',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入邮箱',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'loginDate',
|
||||
label: '登录时间',
|
||||
|
|
@ -236,6 +256,11 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||
title: '手机号',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
title: '邮箱',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'nickname',
|
||||
title: '昵称',
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ const [Descriptions] = useDescription({
|
|||
field: 'mobile',
|
||||
label: '手机号',
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: '邮箱',
|
||||
},
|
||||
{
|
||||
field: 'sex',
|
||||
label: '性别',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ import { getRangePickerDefaultProps } from '#/utils';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改排班计划的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -43,18 +46,21 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入计划编码',
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_PLAN_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_PLAN_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -103,11 +103,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
subTabsName.value = 'shift';
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改班组的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -35,18 +38,21 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入班组编码',
|
||||
},
|
||||
rules: z.string().min(1, '班组编码不能为空').max(64),
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_TEAM_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_TEAM_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -79,11 +79,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
subTabsName.value = 'member';
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export { default as DvCheckPlanSelectDialog } from './select-dialog.vue';
|
||||
export { default as DvCheckPlanSelect } from './select.vue';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesDvCheckPlanApi } from '#/api/mes/dv/checkplan';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { Button, message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getCheckPlanPage } from '#/api/mes/dv/checkplan';
|
||||
|
||||
import {
|
||||
useCheckPlanSelectGridColumns,
|
||||
useCheckPlanSelectGridFormSchema,
|
||||
} from '../data';
|
||||
|
||||
defineOptions({ name: 'DvCheckPlanSelectDialog' });
|
||||
|
||||
const emit = defineEmits<{
|
||||
selected: [rows: MesDvCheckPlanApi.CheckPlan[]];
|
||||
}>();
|
||||
|
||||
const open = ref(false); // 弹窗是否打开
|
||||
const multiple = ref(false); // 是否多选
|
||||
const fixedType = ref<number>(); // 固定计划类型筛选
|
||||
const fixedStatus = ref<number>(); // 固定状态筛选
|
||||
const selectedRows = ref<MesDvCheckPlanApi.CheckPlan[]>([]); // 已选方案列表
|
||||
const preSelectedIds = ref<number[]>([]); // 预选方案编号列表
|
||||
|
||||
/** 获取当前表格数据 */
|
||||
function getTableRows() {
|
||||
return gridApi.grid.getTableData().fullData as MesDvCheckPlanApi.CheckPlan[];
|
||||
}
|
||||
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesDvCheckPlanApi.CheckPlan>();
|
||||
const records = [
|
||||
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
|
||||
...(gridApi.grid.getCheckboxRecords?.() ?? []),
|
||||
] as MesDvCheckPlanApi.CheckPlan[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId != null) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
return [...selectedMap.values()];
|
||||
}
|
||||
|
||||
/** 处理勾选变化 */
|
||||
function handleCheckboxSelectChange() {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理单选变化 */
|
||||
function handleRadioChange(row: MesDvCheckPlanApi.CheckPlan) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
|
||||
/** 多选模式下切换行勾选 */
|
||||
async function toggleMultipleRow(row: MesDvCheckPlanApi.CheckPlan) {
|
||||
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
|
||||
await gridApi.grid.setCheckboxRow(row, !selected);
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理行双击 */
|
||||
async function handleCellDblclick({ row }: { row: MesDvCheckPlanApi.CheckPlan }) {
|
||||
if (multiple.value) {
|
||||
await toggleMultipleRow(row);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = [row];
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
handleConfirm();
|
||||
}
|
||||
|
||||
/** 回显预选方案 */
|
||||
async function applyPreSelection() {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = getTableRows();
|
||||
for (const row of rows) {
|
||||
if (row.id == null || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
} else {
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
selectedRows.value = [row];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiple.value) {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useCheckPlanSelectGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useCheckPlanSelectGridColumns(false),
|
||||
height: 520,
|
||||
keepSource: true,
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
range: true,
|
||||
reserve: true,
|
||||
},
|
||||
radioConfig: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getCheckPlanPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
status: fixedStatus.value,
|
||||
type: fixedType.value,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<MesDvCheckPlanApi.CheckPlan>,
|
||||
gridEvents: {
|
||||
cellDblclick: handleCellDblclick,
|
||||
checkboxAll: handleCheckboxSelectChange,
|
||||
checkboxChange: handleCheckboxSelectChange,
|
||||
radioChange: ({ row }: { row: MesDvCheckPlanApi.CheckPlan }) => {
|
||||
handleRadioChange(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 重置查询和选择状态 */
|
||||
async function resetQueryState() {
|
||||
selectedRows.value = [];
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
await gridApi.grid.clearRadioRow();
|
||||
await gridApi.formApi.resetForm();
|
||||
}
|
||||
|
||||
/** 打开方案选择弹窗 */
|
||||
async function openModal(
|
||||
selectedIds?: number[],
|
||||
options?: { multiple?: boolean; status?: number; type?: number },
|
||||
) {
|
||||
open.value = true;
|
||||
multiple.value = options?.multiple ?? false;
|
||||
fixedType.value = options?.type;
|
||||
fixedStatus.value = options?.status;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useCheckPlanSelectGridColumns(multiple.value),
|
||||
});
|
||||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭方案选择弹窗 */
|
||||
async function closeModal() {
|
||||
open.value = false;
|
||||
await resetQueryState();
|
||||
}
|
||||
|
||||
/** 确认选择方案 */
|
||||
function handleConfirm() {
|
||||
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
|
||||
if (rows.length === 0) {
|
||||
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||
return;
|
||||
}
|
||||
emit('selected', multiple.value ? rows : [rows[0]!]);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
defineExpose({ open: openModal });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="open"
|
||||
title="点检方案选择"
|
||||
width="70%"
|
||||
:destroy-on-close="true"
|
||||
@ok="handleConfirm"
|
||||
@cancel="closeModal"
|
||||
>
|
||||
<Grid table-title="点检方案列表" />
|
||||
<template #footer>
|
||||
<Button @click="closeModal"> 取消 </Button>
|
||||
<Button type="primary" @click="handleConfirm"> 确定 </Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
<script lang="ts" setup>
|
||||
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||
|
||||
import type { MesDvCheckPlanApi } from '#/api/mes/dv/checkplan';
|
||||
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { computed, ref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { Select } from 'ant-design-vue';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { getCheckPlanPage } from '#/api/mes/dv/checkplan';
|
||||
import { Input, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { getCheckPlan } from '#/api/mes/dv/checkplan';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
import DvCheckPlanSelectDialog from './select-dialog.vue';
|
||||
|
||||
defineOptions({ name: 'DvCheckPlanSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
|
@ -22,53 +28,125 @@ const props = withDefaults(
|
|||
allowClear: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择计划',
|
||||
placeholder: '请选择保养方案',
|
||||
status: undefined,
|
||||
type: undefined,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
change: [row?: MesDvCheckPlanApi.CheckPlan];
|
||||
'update:modelValue': [value?: number];
|
||||
change: [item: MesDvCheckPlanApi.CheckPlan | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
const list = ref<MesDvCheckPlanApi.CheckPlan[]>([]); // 点检计划列表
|
||||
const attrs = useAttrs(); // 透传属性
|
||||
const dialogRef = ref<InstanceType<typeof DvCheckPlanSelectDialog>>(); // 方案选择弹窗
|
||||
const hovering = ref(false); // 是否悬停
|
||||
const selectedItem = ref<MesDvCheckPlanApi.CheckPlan>(); // 当前选中方案
|
||||
|
||||
/** 加载点检计划列表 */
|
||||
async function getList() {
|
||||
const data = await getCheckPlanPage({
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据方案编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id == null) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
selectedItem.value = await getCheckPlan(id);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
resolveItemById(value);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 清空已选方案 */
|
||||
function clearSelected() {
|
||||
selectedItem.value = undefined;
|
||||
emit('update:modelValue', undefined);
|
||||
emit('change', undefined);
|
||||
}
|
||||
|
||||
/** 打开方案选择弹窗 */
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLElement;
|
||||
if (showClear.value && target.closest('.ant-input-suffix')) {
|
||||
event.stopPropagation();
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, {
|
||||
multiple: false,
|
||||
status: props.status,
|
||||
type: props.type,
|
||||
});
|
||||
list.value = data.list || [];
|
||||
}
|
||||
|
||||
/** 处理点检计划选择变化 */
|
||||
function handleChange(value: SelectValue) {
|
||||
const planId = typeof value === 'number' ? value : undefined;
|
||||
emit('update:modelValue', planId);
|
||||
emit(
|
||||
'change',
|
||||
list.value.find((item) => item.id === planId),
|
||||
);
|
||||
/** 回填选中的方案 */
|
||||
function handleSelected(rows: MesDvCheckPlanApi.CheckPlan[]) {
|
||||
const item = rows[0];
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
selectedItem.value = item;
|
||||
emit('update:modelValue', item.id);
|
||||
emit('change', item);
|
||||
}
|
||||
|
||||
watch(() => [props.status, props.type], getList);
|
||||
onMounted(getList);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select
|
||||
:allow-clear="allowClear"
|
||||
:disabled="disabled"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
:options="list"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
<div
|
||||
v-bind="attrs"
|
||||
class="w-full"
|
||||
option-filter-prop="name"
|
||||
show-search
|
||||
@change="handleChange"
|
||||
/>
|
||||
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
||||
@click="handleClick"
|
||||
@mouseenter="hovering = true"
|
||||
@mouseleave="hovering = false"
|
||||
>
|
||||
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
|
||||
<template #title>
|
||||
<div v-if="selectedItem" class="leading-6">
|
||||
<div>编码:{{ selectedItem.code || '-' }}</div>
|
||||
<div>名称:{{ selectedItem.name || '-' }}</div>
|
||||
<div class="flex items-center">
|
||||
频度:{{ selectedItem.cycleCount ?? '-' }}
|
||||
<DictTag
|
||||
class="ml-1"
|
||||
:type="DICT_TYPE.MES_DV_CYCLE_TYPE"
|
||||
:value="selectedItem.cycleType"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<Input
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:value="displayLabel"
|
||||
readonly
|
||||
>
|
||||
<template #suffix>
|
||||
<IconifyIcon
|
||||
class="size-4"
|
||||
:icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
|
||||
/>
|
||||
</template>
|
||||
</Input>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<DvCheckPlanSelectDialog ref="dialogRef" @selected="handleSelected" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ import { getRangePickerDefaultProps } from '#/utils';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改点检保养方案的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -44,18 +47,21 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入方案编码',
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_CHECK_PLAN_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_CHECK_PLAN_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
@ -239,3 +245,68 @@ export function useGridColumns(): VxeTableGridOptions<MesDvCheckPlanApi.CheckPla
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 点检方案选择弹窗的搜索表单 */
|
||||
export function useCheckPlanSelectGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '计划编号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入计划编号',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '计划名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入计划名称',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 点检方案选择弹窗的字段 */
|
||||
export function useCheckPlanSelectGridColumns(
|
||||
multiple = false,
|
||||
): VxeTableGridOptions<MesDvCheckPlanApi.CheckPlan>['columns'] {
|
||||
return [
|
||||
{ type: multiple ? 'checkbox' : 'radio', width: 50 },
|
||||
{ field: 'code', title: '计划编码', minWidth: 180 },
|
||||
{ field: 'name', title: '计划名称', minWidth: 150 },
|
||||
{
|
||||
field: 'type',
|
||||
title: '计划类型',
|
||||
width: 120,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_DV_SUBJECT_TYPE },
|
||||
},
|
||||
},
|
||||
{ field: 'startDate', title: '开始日期', width: 120, formatter: 'formatDate' },
|
||||
{ field: 'endDate', title: '结束日期', width: 120, formatter: 'formatDate' },
|
||||
{ field: 'cycleCount', title: '频率', width: 100 },
|
||||
{
|
||||
field: 'cycleType',
|
||||
title: '周期类型',
|
||||
width: 120,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_DV_CYCLE_TYPE },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_DV_CHECK_PLAN_STATUS },
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,11 +78,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
subTabsName.value = 'machinery';
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
type: MesDvSubjectTypeEnum.CHECK,
|
||||
placeholder: '请选择计划',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'userId',
|
||||
|
|
@ -66,7 +65,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
placeholder: '请选择点检人',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'checkTime',
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesDvCheckRecordLineApi } from '#/api/mes/dv/checkrecord/line';
|
||||
|
||||
import { computed, MesDvCheckResultEnum, MesDvSubjectTypeEnum, ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { DICT_TYPE, MesDvCheckResultEnum, MesDvSubjectTypeEnum } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export { default as DvMachinerySelectDialog } from './select-dialog.vue';
|
||||
export { default as DvMachinerySelect } from './select.vue';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,225 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesDvMachineryApi } from '#/api/mes/dv/machinery';
|
||||
import type { MesDvMachineryTypeApi } from '#/api/mes/dv/machinery/type';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getMachineryPage } from '#/api/mes/dv/machinery';
|
||||
import { MachineryTypeTree } from '#/views/mes/dv/machinery/type/components';
|
||||
|
||||
import {
|
||||
useMachinerySelectGridColumns,
|
||||
useMachinerySelectGridFormSchema,
|
||||
} from '../data';
|
||||
|
||||
defineOptions({ name: 'DvMachinerySelectDialog' });
|
||||
|
||||
const emit = defineEmits<{
|
||||
selected: [rows: MesDvMachineryApi.Machinery[]];
|
||||
}>();
|
||||
|
||||
const open = ref(false); // 弹窗是否打开
|
||||
const multiple = ref(false); // 是否多选
|
||||
const selectedRows = ref<MesDvMachineryApi.Machinery[]>([]); // 已选设备列表
|
||||
const selectedMachineryTypeId = ref<number>(); // 当前筛选设备类型编号
|
||||
const preSelectedIds = ref<number[]>([]); // 预选设备编号列表
|
||||
const typeTreeRef = ref<InstanceType<typeof MachineryTypeTree>>(); // 设备类型树
|
||||
|
||||
/** 获取当前表格数据 */
|
||||
function getTableRows() {
|
||||
return gridApi.grid.getTableData().fullData as MesDvMachineryApi.Machinery[];
|
||||
}
|
||||
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesDvMachineryApi.Machinery>();
|
||||
const records = [
|
||||
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
|
||||
...(gridApi.grid.getCheckboxRecords?.() ?? []),
|
||||
] as MesDvMachineryApi.Machinery[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId != null) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
return [...selectedMap.values()];
|
||||
}
|
||||
|
||||
/** 处理勾选变化 */
|
||||
function handleCheckboxSelectChange() {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理单选变化 */
|
||||
function handleRadioChange(row: MesDvMachineryApi.Machinery) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
|
||||
/** 多选模式下切换行勾选 */
|
||||
async function toggleMultipleRow(row: MesDvMachineryApi.Machinery) {
|
||||
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
|
||||
await gridApi.grid.setCheckboxRow(row, !selected);
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理行双击 */
|
||||
async function handleCellDblclick({ row }: { row: MesDvMachineryApi.Machinery }) {
|
||||
if (multiple.value) {
|
||||
await toggleMultipleRow(row);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = [row];
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
handleConfirm();
|
||||
}
|
||||
|
||||
/** 按设备类型筛选 */
|
||||
function handleTypeNodeClick(row: MesDvMachineryTypeApi.MachineryType | undefined) {
|
||||
selectedMachineryTypeId.value = row?.id;
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 回显预选设备 */
|
||||
async function applyPreSelection() {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = getTableRows();
|
||||
for (const row of rows) {
|
||||
if (row.id == null || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
} else {
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
selectedRows.value = [row];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiple.value) {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useMachinerySelectGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useMachinerySelectGridColumns(false),
|
||||
height: 520,
|
||||
keepSource: true,
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
range: true,
|
||||
reserve: true,
|
||||
},
|
||||
radioConfig: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getMachineryPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
machineryTypeId: selectedMachineryTypeId.value,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<MesDvMachineryApi.Machinery>,
|
||||
gridEvents: {
|
||||
cellDblclick: handleCellDblclick,
|
||||
checkboxAll: handleCheckboxSelectChange,
|
||||
checkboxChange: handleCheckboxSelectChange,
|
||||
radioChange: ({ row }: { row: MesDvMachineryApi.Machinery }) => {
|
||||
handleRadioChange(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 重置查询和选择状态 */
|
||||
async function resetQueryState() {
|
||||
selectedMachineryTypeId.value = undefined;
|
||||
selectedRows.value = [];
|
||||
typeTreeRef.value?.reset();
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
await gridApi.grid.clearRadioRow();
|
||||
await gridApi.formApi.resetForm();
|
||||
}
|
||||
|
||||
/** 打开设备选择弹窗 */
|
||||
async function openModal(
|
||||
selectedIds?: number[],
|
||||
options?: { multiple?: boolean },
|
||||
) {
|
||||
open.value = true;
|
||||
multiple.value = options?.multiple ?? false;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useMachinerySelectGridColumns(multiple.value),
|
||||
});
|
||||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭设备选择弹窗 */
|
||||
async function closeModal() {
|
||||
open.value = false;
|
||||
await resetQueryState();
|
||||
}
|
||||
|
||||
/** 确认选择设备 */
|
||||
function handleConfirm() {
|
||||
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
|
||||
if (rows.length === 0) {
|
||||
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||
return;
|
||||
}
|
||||
emit('selected', multiple.value ? rows : [rows[0]!]);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
defineExpose({ open: openModal });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="open"
|
||||
title="设备选择"
|
||||
width="80%"
|
||||
:destroy-on-close="true"
|
||||
@ok="handleConfirm"
|
||||
@cancel="closeModal"
|
||||
>
|
||||
<div class="flex h-full w-full">
|
||||
<div class="mr-4 h-full w-1/5">
|
||||
<MachineryTypeTree ref="typeTreeRef" @node-click="handleTypeNodeClick" />
|
||||
</div>
|
||||
<div class="w-4/5">
|
||||
<Grid table-title="设备列表" />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -1,58 +1,138 @@
|
|||
<script lang="ts" setup>
|
||||
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||
|
||||
import type { MesDvMachineryApi } from '#/api/mes/dv/machinery';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, ref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { Select } from 'ant-design-vue';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { getMachinerySimpleList } from '#/api/mes/dv/machinery';
|
||||
import { Input, Tooltip } from 'ant-design-vue';
|
||||
|
||||
withDefaults(
|
||||
import { getMachinery } from '#/api/mes/dv/machinery';
|
||||
|
||||
import DvMachinerySelectDialog from './select-dialog.vue';
|
||||
|
||||
defineOptions({ name: 'DvMachinerySelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
allowClear?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{ allowClear: true, disabled: false, modelValue: undefined, placeholder: '请选择设备' },
|
||||
{
|
||||
allowClear: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择设备',
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
change: [row?: MesDvMachineryApi.Machinery];
|
||||
'update:modelValue': [value?: number];
|
||||
change: [item: MesDvMachineryApi.Machinery | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
const list = ref<MesDvMachineryApi.Machinery[]>([]); // 设备列表
|
||||
const attrs = useAttrs(); // 透传属性
|
||||
const dialogRef = ref<InstanceType<typeof DvMachinerySelectDialog>>(); // 设备选择弹窗
|
||||
const hovering = ref(false); // 是否悬停
|
||||
const selectedItem = ref<MesDvMachineryApi.Machinery>(); // 当前选中设备
|
||||
|
||||
/** 加载设备列表 */
|
||||
async function getList() {
|
||||
list.value = await getMachinerySimpleList();
|
||||
const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据设备编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id == null) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
selectedItem.value = await getMachinery(id);
|
||||
}
|
||||
|
||||
/** 处理设备选择变化 */
|
||||
function handleChange(value: SelectValue) {
|
||||
const machineryId = typeof value === 'number' ? value : undefined;
|
||||
emit('update:modelValue', machineryId);
|
||||
emit(
|
||||
'change',
|
||||
list.value.find((item) => item.id === machineryId),
|
||||
);
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
resolveItemById(value);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 清空已选设备 */
|
||||
function clearSelected() {
|
||||
selectedItem.value = undefined;
|
||||
emit('update:modelValue', undefined);
|
||||
emit('change', undefined);
|
||||
}
|
||||
|
||||
onMounted(getList);
|
||||
/** 打开设备选择弹窗 */
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLElement;
|
||||
if (showClear.value && target.closest('.ant-input-suffix')) {
|
||||
event.stopPropagation();
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, { multiple: false });
|
||||
}
|
||||
|
||||
/** 回填选中的设备 */
|
||||
function handleSelected(rows: MesDvMachineryApi.Machinery[]) {
|
||||
const item = rows[0];
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
selectedItem.value = item;
|
||||
emit('update:modelValue', item.id);
|
||||
emit('change', item);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select
|
||||
:allow-clear="allowClear"
|
||||
:disabled="disabled"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
:options="list"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
<div
|
||||
v-bind="attrs"
|
||||
class="w-full"
|
||||
option-filter-prop="name"
|
||||
show-search
|
||||
@change="handleChange"
|
||||
/>
|
||||
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
||||
@click="handleClick"
|
||||
@mouseenter="hovering = true"
|
||||
@mouseleave="hovering = false"
|
||||
>
|
||||
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
|
||||
<template #title>
|
||||
<div v-if="selectedItem" class="leading-6">
|
||||
<div>设备编码:{{ selectedItem.code || '-' }}</div>
|
||||
<div>设备名称:{{ selectedItem.name || '-' }}</div>
|
||||
<div v-if="selectedItem.brand">品牌:{{ selectedItem.brand }}</div>
|
||||
<div v-if="selectedItem.specification">
|
||||
规格型号:{{ selectedItem.specification }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<Input
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:value="displayLabel"
|
||||
readonly
|
||||
>
|
||||
<template #suffix>
|
||||
<IconifyIcon
|
||||
class="size-4"
|
||||
:icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
|
||||
/>
|
||||
</template>
|
||||
</Input>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<DvMachinerySelectDialog ref="dialogRef" @selected="handleSelected" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -41,18 +41,21 @@ export function useFormSchema(formType: FormType, formApi?: VbenFormApi): VbenFo
|
|||
componentProps: (values) => ({ disabled: !!values.id }),
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_MACHINERY_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_MACHINERY_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
@ -273,3 +276,65 @@ export function useImportFormSchema(): VbenFormSchema[] {
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 设备选择弹窗的搜索表单 */
|
||||
export function useMachinerySelectGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '设备编码',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入设备编码',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '设备名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入设备名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'workshopId',
|
||||
label: '所属车间',
|
||||
component: markRaw(MdWorkshopSelect),
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择所属车间',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 设备选择弹窗的字段 */
|
||||
export function useMachinerySelectGridColumns(
|
||||
multiple = false,
|
||||
): VxeTableGridOptions<MesDvMachineryApi.Machinery>['columns'] {
|
||||
return [
|
||||
{ type: multiple ? 'checkbox' : 'radio', width: 50 },
|
||||
{ field: 'code', title: '设备编码', width: 120 },
|
||||
{ field: 'name', title: '设备名称', minWidth: 120 },
|
||||
{ field: 'brand', title: '品牌', minWidth: 120 },
|
||||
{ field: 'specification', title: '规格型号', minWidth: 120 },
|
||||
{ field: 'workshopName', title: '所属车间', width: 120 },
|
||||
{
|
||||
field: 'status',
|
||||
title: '设备状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_DV_MACHINERY_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesDvMachineryTypeApi } from '#/api/mes/dv/machinery/type';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
|
|
@ -14,8 +14,14 @@ import { z } from '#/adapter/form';
|
|||
import { getMachineryTypeList } from '#/api/mes/dv/machinery/type';
|
||||
import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
||||
|
||||
/** 表单类型 */
|
||||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改设备类型的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -52,18 +58,23 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入类型编码',
|
||||
},
|
||||
rules: z.string().min(1, '类型编码不能为空').max(64),
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_MACHINERY_TYPE_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.DV_MACHINERY_TYPE_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -64,9 +64,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
// 加载数据
|
||||
const data = modalApi.getData<MesDvMachineryTypeApi.MachineryType>();
|
||||
formApi.setState({
|
||||
schema: useFormSchema(data?.id ? 'update' : 'create', formApi),
|
||||
});
|
||||
if (!data || !data.id) {
|
||||
formData.value = data || undefined;
|
||||
if (data) {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
type: MesDvSubjectTypeEnum.MAINTENANCE,
|
||||
placeholder: '请选择计划',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'userId',
|
||||
|
|
@ -66,7 +65,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
placeholder: '请选择保养人',
|
||||
valueField: 'id',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
fieldName: 'maintenTime',
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesDvMaintenRecordLineApi } from '#/api/mes/dv/maintenrecord/line';
|
||||
|
||||
import { computed, MesDvMaintenStatusEnum, MesDvSubjectTypeEnum, ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { DICT_TYPE, MesDvMaintenStatusEnum, MesDvSubjectTypeEnum } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
|
|
|||
|
|
@ -16,8 +16,17 @@ import { DvMachinerySelect } from '#/views/mes/dv/machinery/components';
|
|||
/** 表单类型 */
|
||||
export type FormType = 'confirm' | 'create' | 'detail' | 'finish' | 'update';
|
||||
|
||||
/** 表头是否只读(完成维修、验收、详情态;finishDate 在 confirm 单独放开) */
|
||||
function isHeaderReadonly(formType: FormType): boolean {
|
||||
return ['confirm', 'detail', 'finish'].includes(formType);
|
||||
}
|
||||
|
||||
/** 新增/修改维修工单的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
const headerReadonly = isHeaderReadonly(formType);
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -45,27 +54,30 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
},
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
componentProps: (values) => ({ disabled: !!values.id }),
|
||||
componentProps: (values) => ({ disabled: headerReadonly || !!values.id }),
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_REPAIR_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix: headerReadonly
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_REPAIR_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '维修单名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
disabled: headerReadonly,
|
||||
placeholder: '请输入维修单名称',
|
||||
},
|
||||
rules: 'required',
|
||||
|
|
@ -75,6 +87,7 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
label: '设备',
|
||||
component: markRaw(DvMachinerySelect),
|
||||
componentProps: {
|
||||
disabled: headerReadonly,
|
||||
placeholder: '请选择设备',
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
|
|
@ -84,6 +97,7 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
label: '报修日期',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
disabled: headerReadonly,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: '请选择报修日期',
|
||||
showTime: true,
|
||||
|
|
@ -98,10 +112,18 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
disabled: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择维修人',
|
||||
valueField: 'id',
|
||||
},
|
||||
// 维修人为待验收(≥APPROVING)态自动产生的只读回显字段
|
||||
dependencies: {
|
||||
triggerFields: ['status'],
|
||||
if: (values) =>
|
||||
values.status != null &&
|
||||
values.status >= MesDvRepairStatusEnum.APPROVING,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'finishDate',
|
||||
|
|
@ -113,6 +135,15 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
showTime: true,
|
||||
valueFormat: 'x',
|
||||
},
|
||||
// 维修中(≥CONFIRMED)态展示;仅"完成维修"弹窗可编辑并必填,其余态只读回显
|
||||
dependencies: {
|
||||
triggerFields: ['status'],
|
||||
if: (values) =>
|
||||
values.status != null &&
|
||||
values.status >= MesDvRepairStatusEnum.CONFIRMED,
|
||||
disabled: formType !== 'confirm',
|
||||
rules: () => (formType === 'confirm' ? 'required' : null),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'confirmUserId',
|
||||
|
|
@ -121,21 +152,37 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleUserList,
|
||||
disabled: true,
|
||||
labelField: 'nickname',
|
||||
placeholder: '请选择验收人',
|
||||
valueField: 'id',
|
||||
},
|
||||
// 验收信息为已确认(≥FINISHED)态自动产生的只读回显字段
|
||||
dependencies: {
|
||||
triggerFields: ['status'],
|
||||
if: (values) =>
|
||||
values.status != null &&
|
||||
values.status >= MesDvRepairStatusEnum.FINISHED,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'confirmDate',
|
||||
label: '验收日期',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: '请选择验收日期',
|
||||
showTime: true,
|
||||
valueFormat: 'x',
|
||||
},
|
||||
// 验收信息为已确认(≥FINISHED)态自动产生的只读回显字段
|
||||
dependencies: {
|
||||
triggerFields: ['status'],
|
||||
if: (values) =>
|
||||
values.status != null &&
|
||||
values.status >= MesDvRepairStatusEnum.FINISHED,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'result',
|
||||
|
|
@ -143,9 +190,17 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
buttonStyle: 'solid',
|
||||
disabled: true,
|
||||
optionType: 'button',
|
||||
options: getDictOptions(DICT_TYPE.MES_DV_REPAIR_RESULT, 'number'),
|
||||
},
|
||||
// 验收信息为已确认(≥FINISHED)态自动产生的只读回显字段
|
||||
dependencies: {
|
||||
triggerFields: ['status'],
|
||||
if: (values) =>
|
||||
values.status != null &&
|
||||
values.status >= MesDvRepairStatusEnum.FINISHED,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
|
|
@ -153,6 +208,7 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
component: 'Textarea',
|
||||
formItemClass: 'col-span-3',
|
||||
componentProps: {
|
||||
disabled: headerReadonly,
|
||||
placeholder: '请输入备注',
|
||||
rows: 3,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,20 +28,29 @@ const emit = defineEmits(['success']);
|
|||
const formType = ref<FormType>('create');
|
||||
const formData = ref<MesDvRepairApi.Repair>();
|
||||
const isDetail = computed(() => formType.value === 'detail');
|
||||
const isReadonly = computed(() => ['confirm', 'detail', 'finish'].includes(formType.value));
|
||||
const isLineReadonly = computed(() =>
|
||||
// 明细只读:完成维修/验收/详情态明细均不可编辑
|
||||
['confirm', 'detail', 'finish'].includes(formType.value),
|
||||
);
|
||||
const isFormDisabled = computed(() =>
|
||||
// 整表禁用:仅详情/验收态;完成维修态需放开 finishDate,故单头只读改由 schema 逐字段控制
|
||||
['detail', 'finish'].includes(formType.value),
|
||||
);
|
||||
const canSubmit = computed(
|
||||
() => formType.value === 'update' && formData.value?.status === MesDvRepairStatusEnum.PREPARE,
|
||||
);
|
||||
const getTitle = computed(
|
||||
() =>
|
||||
({
|
||||
create: '新增维修工单',
|
||||
update: '修改维修工单',
|
||||
confirm: '完成维修',
|
||||
finish: '验收维修',
|
||||
detail: '查看维修工单',
|
||||
})[formType.value],
|
||||
);
|
||||
const getTitle = computed(() => {
|
||||
if (formType.value === 'detail') {
|
||||
return '查看维修工单';
|
||||
}
|
||||
if (formType.value === 'confirm') {
|
||||
return '完成维修';
|
||||
}
|
||||
if (formType.value === 'finish') {
|
||||
return '验收维修';
|
||||
}
|
||||
return formType.value === 'update' ? '修改维修工单' : '新增维修工单';
|
||||
});
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
|
|
@ -161,11 +170,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setDisabled(isReadonly.value);
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(isFormDisabled.value);
|
||||
modalApi.setState({ showConfirmButton: ['create', 'update'].includes(formType.value) });
|
||||
if (!data?.id) {
|
||||
return;
|
||||
|
|
@ -184,7 +193,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
<template>
|
||||
<Modal :title="getTitle" class="w-4/5">
|
||||
<Form class="mx-4" />
|
||||
<LineList v-if="formData?.id" :disabled="isReadonly" :repair-id="formData.id" />
|
||||
<LineList v-if="formData?.id" :disabled="isLineReadonly" :repair-id="formData.id" />
|
||||
<template #prepend-footer>
|
||||
<div class="flex flex-auto items-center gap-2">
|
||||
<Popconfirm
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@ import type { MesDvSubjectApi } from '#/api/mes/dv/subject';
|
|||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { CommonStatusEnum } from '@vben/constants';
|
||||
|
||||
import { Select } from 'ant-design-vue';
|
||||
|
||||
import { getSubjectSimpleList } from '#/api/mes/dv/subject';
|
||||
|
||||
defineOptions({ name: 'DvSubjectSelect' });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
allowClear?: boolean;
|
||||
|
|
@ -30,9 +34,26 @@ const emit = defineEmits<{
|
|||
'update:modelValue': [value?: number];
|
||||
}>();
|
||||
const list = ref<MesDvSubjectApi.Subject[]>([]); // 项目列表
|
||||
const filteredList = computed( // 筛选后的项目列表
|
||||
() => list.value.filter((item) => !props.type || item.type === props.type),
|
||||
);
|
||||
const filteredList = computed(() => {
|
||||
// 仅展示启用且类型匹配的项目,避免新建时带出禁用项目
|
||||
const result: Array<MesDvSubjectApi.Subject & { disabled?: boolean }> =
|
||||
list.value.filter(
|
||||
(item) =>
|
||||
item.status === CommonStatusEnum.ENABLE &&
|
||||
(!props.type || item.type === props.type),
|
||||
);
|
||||
// 历史数据可能绑定已禁用或其它类型的项目,补充当前选中项用于回显,并禁止重新选择
|
||||
if (
|
||||
props.modelValue != null &&
|
||||
!result.some((item) => item.id === props.modelValue)
|
||||
) {
|
||||
const current = list.value.find((item) => item.id === props.modelValue);
|
||||
if (current) {
|
||||
result.push({ ...current, disabled: true });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
/** 加载项目列表 */
|
||||
async function getList() {
|
||||
|
|
@ -43,10 +64,7 @@ async function getList() {
|
|||
function handleChange(value: SelectValue) {
|
||||
const subjectId = typeof value === 'number' ? value : undefined;
|
||||
emit('update:modelValue', subjectId);
|
||||
emit(
|
||||
'change',
|
||||
list.value.find((item) => item.id === subjectId),
|
||||
);
|
||||
emit('change', list.value.find((item) => item.id === subjectId));
|
||||
}
|
||||
|
||||
onMounted(getList);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesDvSubjectApi } from '#/api/mes/dv/subject';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode, MesDvSubjectTypeEnum } from '@vben/constants';
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
MesAutoCodeRuleCode,
|
||||
MesDvSubjectTypeEnum,
|
||||
} from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -16,7 +21,10 @@ import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改点检保养项目的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -34,18 +42,21 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入项目编码',
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_SUBJECT_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.DV_SUBJECT_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -66,10 +66,10 @@ const [Modal, modalApi] = useVbenModal({
|
|||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -448,10 +448,7 @@ export function usePartGridColumns(): VxeTableGridOptions<MesMdAutoCodePartApi.A
|
|||
title: '循环方式',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_MD_AUTO_CODE_CYCLE_METHOD },
|
||||
},
|
||||
slots: { default: 'cycleMethod' },
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { MesMdAutoCodePartApi } from '#/api/mes/md/autocode/part';
|
|||
import { ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ import {
|
|||
deleteAutoCodePart,
|
||||
getAutoCodePartListByRuleId,
|
||||
} from '#/api/mes/md/autocode/part';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { usePartGridColumns } from '../data';
|
||||
|
|
@ -107,6 +109,13 @@ watch(
|
|||
/>
|
||||
</div>
|
||||
<Grid class="w-full" table-title="规则组成">
|
||||
<template #cycleMethod="{ row }">
|
||||
<DictTag
|
||||
v-if="row.cycleFlag"
|
||||
:type="DICT_TYPE.MES_MD_AUTO_CODE_CYCLE_METHOD"
|
||||
:value="row.cycleMethod"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import {
|
|||
useClientSelectGridFormSchema,
|
||||
} from '../data';
|
||||
|
||||
defineOptions({ name: 'MdClientSelectDialog' });
|
||||
|
||||
const emit = defineEmits<{
|
||||
selected: [rows: MesMdClientApi.Client[]];
|
||||
}>();
|
||||
|
|
@ -24,43 +26,12 @@ const open = ref(false); // 弹窗是否打开
|
|||
const multiple = ref(true); // 是否多选
|
||||
const selectedRows = ref<MesMdClientApi.Client[]>([]); // 已选客户列表
|
||||
const preSelectedIds = ref<number[]>([]); // 预选客户编号列表
|
||||
const latestQueryRows = ref<MesMdClientApi.Client[]>([]); // 最近一次查询返回的客户列表
|
||||
const queryFinished = ref(false); // 最近一次查询是否完成
|
||||
|
||||
// TODO @芋艿:是否有必要搞的这么复杂???后续测试看看。
|
||||
const MAX_TABLE_READY_FRAMES = 60;
|
||||
|
||||
/** 等待下一帧 */
|
||||
function waitNextFrame(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
requestAnimationFrame(() => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取当前表格数据 */
|
||||
function getTableRows() {
|
||||
return gridApi.grid.getTableData().fullData as MesMdClientApi.Client[];
|
||||
}
|
||||
|
||||
/** 等待 VXE 将当前查询结果写入表格数据后再回显选中 */
|
||||
async function waitTableReady(): Promise<void> {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (let index = 0; index < MAX_TABLE_READY_FRAMES; index += 1) {
|
||||
if (queryFinished.value) {
|
||||
const rows = getTableRows();
|
||||
if (latestQueryRows.value.length === 0 && rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (latestQueryRows.value.length > 0 && rows.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await waitNextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesMdClientApi.Client>();
|
||||
|
|
@ -70,7 +41,7 @@ function getMultipleSelectedRows() {
|
|||
] as MesMdClientApi.Client[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId !== null && rowId !== undefined) {
|
||||
if (rowId != null) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
|
|
@ -110,10 +81,9 @@ async function applyPreSelection() {
|
|||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
// proxy 表格回显选中时要读取 fullData,否则首次打开可能读不到刚查询出的数据。
|
||||
const rows = getTableRows();
|
||||
for (const row of rows) {
|
||||
if (row.id === null || !preSelectedIds.value.includes(row.id as number)) {
|
||||
if (row.id == null || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
|
|
@ -149,15 +119,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
const data = await getClientPage({
|
||||
return await getClientPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
});
|
||||
latestQueryRows.value = data.list || [];
|
||||
queryFinished.value = true;
|
||||
return data;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -197,8 +164,6 @@ async function openModal(
|
|||
open.value = true;
|
||||
multiple.value = options?.multiple ?? true;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
latestQueryRows.value = [];
|
||||
queryFinished.value = false;
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useClientSelectGridColumns(multiple.value),
|
||||
|
|
@ -206,13 +171,13 @@ async function openModal(
|
|||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
await waitTableReady();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭客户选择弹窗 */
|
||||
function closeModal() {
|
||||
async function closeModal() {
|
||||
open.value = false;
|
||||
await resetQueryState();
|
||||
}
|
||||
|
||||
/** 确认选择客户 */
|
||||
|
|
|
|||
|
|
@ -38,28 +38,23 @@ const selectedItem = ref<MesMdClientApi.Client>(); // 当前选中客户
|
|||
|
||||
const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
// 是否显示清空图标
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue !== null,
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据客户编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id === null) {
|
||||
if (id == null) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getClient(id as number);
|
||||
} catch (error) {
|
||||
console.error('[MdClientSelect] resolveItemById failed:', error);
|
||||
}
|
||||
selectedItem.value = await getClient(id);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
@ -88,8 +83,8 @@ function handleClick(event: MouseEvent) {
|
|||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds = props.modelValue === null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds as number[], { multiple: false });
|
||||
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, { multiple: false });
|
||||
}
|
||||
|
||||
/** 回填选中的客户 */
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesMdClientApi } from '#/api/mes/md/client';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -16,7 +16,10 @@ import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改客户的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -33,27 +36,24 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
componentProps: {
|
||||
placeholder: '请输入客户编码',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
componentProps: (values) => ({
|
||||
disabled: !!values.id,
|
||||
}),
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_CLIENT_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '自动生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_CLIENT_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '自动生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
subTabsName.value = 'productSalesLine';
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -33,11 +33,31 @@ const [Grid] = useVbenVxeGrid({
|
|||
width: 140,
|
||||
slots: { default: 'itemCode' },
|
||||
},
|
||||
{ field: 'itemName', title: '物料名称', minWidth: 150 },
|
||||
{ field: 'specification', title: '规格型号', minWidth: 140 },
|
||||
{ field: 'unitMeasureName', title: '单位', width: 100 },
|
||||
{ field: 'quantity', title: '出库数量', width: 120 },
|
||||
{ field: 'batchCode', title: '批次号', minWidth: 140 },
|
||||
{
|
||||
field: 'itemName',
|
||||
title: '物料名称',
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'specification',
|
||||
title: '规格型号',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
field: 'unitMeasureName',
|
||||
title: '单位',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '出库数量',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'batchCode',
|
||||
title: '批次号',
|
||||
minWidth: 140,
|
||||
},
|
||||
],
|
||||
height: 320,
|
||||
keepSource: true,
|
||||
|
|
|
|||
|
|
@ -2,21 +2,50 @@
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getProductSalesPage } from '#/api/mes/wm/productsales';
|
||||
import ProductSalesForm from '#/views/mes/wm/productsales/modules/form.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
clientId: number;
|
||||
}>();
|
||||
|
||||
const [DetailModal, detailModalApi] = useVbenModal({
|
||||
connectedComponent: ProductSalesForm,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 查看销售出库单详情 */
|
||||
function handleViewSales(row: MesWmProductSalesApi.ProductSales) {
|
||||
if (row.id) {
|
||||
detailModalApi.setData({ formType: 'detail', id: row.id }).open();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
columns: [
|
||||
{ field: 'code', title: '出库单编号', minWidth: 160 },
|
||||
{ field: 'name', title: '出库单名称', minWidth: 150 },
|
||||
{ field: 'salesOrderCode', title: '销售订单编号', minWidth: 140 },
|
||||
{
|
||||
field: 'code',
|
||||
title: '出库单编号',
|
||||
minWidth: 160,
|
||||
slots: { default: 'code' },
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '出库单名称',
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'salesOrderCode',
|
||||
title: '销售订单编号',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
field: 'salesDate',
|
||||
title: '出库日期',
|
||||
|
|
@ -58,5 +87,12 @@ const [Grid] = useVbenVxeGrid({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Grid table-title="销售记录" />
|
||||
<DetailModal />
|
||||
<Grid table-title="销售记录">
|
||||
<template #code="{ row }">
|
||||
<Button type="link" @click="handleViewSales(row)">
|
||||
{{ row.code }}
|
||||
</Button>
|
||||
</template>
|
||||
</Grid>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ async function openModal(itemId: number, selectedBomItemId?: number) {
|
|||
list.value = await getProductBomListByItemId(itemId);
|
||||
gridApi.setGridOptions({ data: list.value });
|
||||
await nextTick();
|
||||
if (selectedBomItemId !== null) {
|
||||
if (selectedBomItemId != null) {
|
||||
const match = list.value.find(
|
||||
(row) => row.bomItemId === selectedBomItemId,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,29 +40,24 @@ const selectedBom = ref<MesMdProductBomApi.ProductBom>(); // 当前选中的 BOM
|
|||
|
||||
const displayLabel = computed(() => selectedBom.value?.bomItemName ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
// 是否显示清空图标
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue !== null,
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据 BOM 子物料编号回显选择器 */
|
||||
async function resolveBomById(bomItemId: number | undefined) {
|
||||
if (bomItemId === null || props.itemId === null) {
|
||||
if (bomItemId == null || props.itemId == null) {
|
||||
selectedBom.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedBom.value?.bomItemId === bomItemId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const list = await getProductBomListByItemId(props.itemId as number);
|
||||
selectedBom.value = list.find((item) => item.bomItemId === bomItemId);
|
||||
} catch (error) {
|
||||
console.error('[MdProductBomSelect] resolveBomById failed:', error);
|
||||
}
|
||||
const list = await getProductBomListByItemId(props.itemId);
|
||||
selectedBom.value = list.find((item) => item.bomItemId === bomItemId);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
@ -91,7 +86,7 @@ function clearSelected() {
|
|||
|
||||
/** 打开 BOM 物料选择弹窗 */
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (props.disabled || props.itemId === null) {
|
||||
if (props.disabled || props.itemId == null) {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLElement;
|
||||
|
|
@ -100,7 +95,7 @@ function handleClick(event: MouseEvent) {
|
|||
clearSelected();
|
||||
return;
|
||||
}
|
||||
dialogRef.value?.open(props.itemId as number, props.modelValue);
|
||||
dialogRef.value?.open(props.itemId, props.modelValue);
|
||||
}
|
||||
|
||||
/** 回填选中的 BOM 物料 */
|
||||
|
|
|
|||
|
|
@ -23,52 +23,58 @@ const emit = defineEmits<{
|
|||
|
||||
const open = ref(false); // 弹窗是否打开
|
||||
const multiple = ref(true); // 是否多选
|
||||
const syncingSingleSelection = ref(false); // 是否同步单选勾选状态
|
||||
const selectedRows = ref<MesMdItemApi.Item[]>([]); // 已选物料列表
|
||||
const selectedItemTypeId = ref<number>(); // 当前筛选分类编号
|
||||
const preSelectedIds = ref<number[]>([]); // 预选物料编号列表
|
||||
const typeTreeRef = ref<InstanceType<typeof MdItemTypeTree>>(); // 物料分类树
|
||||
|
||||
/** 单选模式下同步 VXE 勾选状态,避免跨页残留多选 */
|
||||
async function syncSingleSelection(row?: MesMdItemApi.Item) {
|
||||
syncingSingleSelection.value = true;
|
||||
await nextTick();
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
if (row) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
}
|
||||
await nextTick();
|
||||
syncingSingleSelection.value = false;
|
||||
/** 获取当前表格数据 */
|
||||
function getTableRows() {
|
||||
return gridApi.grid.getTableData().fullData as MesMdItemApi.Item[];
|
||||
}
|
||||
|
||||
/** 处理勾选变化,单选模式只保留最后一条 */
|
||||
async function handleCheckboxChange({
|
||||
checked,
|
||||
records,
|
||||
row,
|
||||
}: {
|
||||
checked: boolean;
|
||||
records: MesMdItemApi.Item[];
|
||||
row?: MesMdItemApi.Item;
|
||||
}) {
|
||||
if (syncingSingleSelection.value) {
|
||||
return;
|
||||
}
|
||||
if (!multiple.value) {
|
||||
const selected = checked && row ? [row] : [];
|
||||
selectedRows.value = selected;
|
||||
await syncSingleSelection(selected[0]);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = records;
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesMdItemApi.Item>();
|
||||
const records = [
|
||||
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
|
||||
...(gridApi.grid.getCheckboxRecords?.() ?? []),
|
||||
] as MesMdItemApi.Item[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId != null) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
return [...selectedMap.values()];
|
||||
}
|
||||
|
||||
/** 处理全选变化 */
|
||||
function handleCheckboxAll({ records }: { records: MesMdItemApi.Item[] }) {
|
||||
if (syncingSingleSelection.value) {
|
||||
/** 处理勾选变化 */
|
||||
function handleCheckboxSelectChange() {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理单选变化 */
|
||||
function handleRadioChange(row: MesMdItemApi.Item) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
|
||||
/** 多选模式下切换行勾选 */
|
||||
async function toggleMultipleRow(row: MesMdItemApi.Item) {
|
||||
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
|
||||
await gridApi.grid.setCheckboxRow(row, !selected);
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理行双击 */
|
||||
async function handleCellDblclick({ row }: { row: MesMdItemApi.Item }) {
|
||||
if (multiple.value) {
|
||||
await toggleMultipleRow(row);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = records;
|
||||
selectedRows.value = [row];
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
handleConfirm();
|
||||
}
|
||||
|
||||
/** 按分类筛选物料 */
|
||||
|
|
@ -78,18 +84,25 @@ function handleItemTypeNodeClick(row: MesMdItemTypeApi.ItemType | undefined) {
|
|||
}
|
||||
|
||||
/** 回显预选物料 */
|
||||
function applyPreSelection() {
|
||||
async function applyPreSelection() {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = gridApi.grid.getData() as MesMdItemApi.Item[];
|
||||
const rows = getTableRows();
|
||||
for (const row of rows) {
|
||||
if (row.id && preSelectedIds.value.includes(row.id)) {
|
||||
gridApi.grid.setCheckboxRow(row, true);
|
||||
if (!multiple.value) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
if (row.id == null || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
} else {
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
selectedRows.value = [row];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiple.value) {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +111,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
schema: useItemSelectGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useItemSelectGridColumns(),
|
||||
columns: useItemSelectGridColumns(true),
|
||||
height: 560,
|
||||
keepSource: true,
|
||||
checkboxConfig: {
|
||||
|
|
@ -106,6 +119,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
range: true,
|
||||
reserve: true,
|
||||
},
|
||||
radioConfig: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
|
|
@ -129,8 +146,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
},
|
||||
} as VxeTableGridOptions<MesMdItemApi.Item>,
|
||||
gridEvents: {
|
||||
checkboxAll: handleCheckboxAll,
|
||||
checkboxChange: handleCheckboxChange,
|
||||
cellDblclick: handleCellDblclick,
|
||||
checkboxAll: handleCheckboxSelectChange,
|
||||
checkboxChange: handleCheckboxSelectChange,
|
||||
radioChange: ({ row }: { row: MesMdItemApi.Item }) => {
|
||||
handleRadioChange(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -140,6 +161,8 @@ async function resetQueryState() {
|
|||
selectedRows.value = [];
|
||||
typeTreeRef.value?.reset();
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
await gridApi.grid.clearRadioRow();
|
||||
await gridApi.formApi.resetForm();
|
||||
}
|
||||
|
||||
|
|
@ -152,10 +175,13 @@ async function openModal(
|
|||
multiple.value = options?.multiple ?? true;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useItemSelectGridColumns(multiple.value),
|
||||
});
|
||||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
applyPreSelection();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭物料选择弹窗 */
|
||||
|
|
@ -166,14 +192,12 @@ async function closeModal() {
|
|||
|
||||
/** 确认选择物料 */
|
||||
function handleConfirm() {
|
||||
if (selectedRows.value.length === 0) {
|
||||
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
|
||||
if (rows.length === 0) {
|
||||
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
'selected',
|
||||
multiple.value ? selectedRows.value : [selectedRows.value[0]!],
|
||||
);
|
||||
emit('selected', multiple.value ? rows : [rows[0]!]);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,28 +38,23 @@ const selectedItem = ref<MesMdItemApi.Item>(); // 当前选中物料
|
|||
|
||||
const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
// 是否显示清空图标
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue !== null,
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据物料编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id === null) {
|
||||
if (id == null) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getItem(id as number);
|
||||
} catch (error) {
|
||||
console.error('[MdItemSelect] resolveItemById failed:', error);
|
||||
}
|
||||
selectedItem.value = await getItem(id);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
@ -88,8 +83,8 @@ function handleClick(event: MouseEvent) {
|
|||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds = props.modelValue === null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds as number[], { multiple: false });
|
||||
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, { multiple: false });
|
||||
}
|
||||
|
||||
/** 回填选中的物料 */
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||
import type { MesMdItemApi } from '#/api/mes/md/item';
|
||||
import type { MesMdProductBomApi } from '#/api/mes/md/item/productBom';
|
||||
|
||||
import { DICT_TYPE, h, markRaw } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -19,7 +19,10 @@ import { MdUnitMeasureSelect } from '#/views/mes/md/unitmeasure/components';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改物料产品的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -38,20 +41,23 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入物料编码',
|
||||
},
|
||||
rules: z.string().min(1, '物料编码不能为空').max(64),
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_ITEM_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_ITEM_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
@ -356,9 +362,11 @@ export function useItemSelectGridFormSchema(): VbenFormSchema[] {
|
|||
}
|
||||
|
||||
/** 物料选择弹窗列表字段 */
|
||||
export function useItemSelectGridColumns(): VxeTableGridOptions<MesMdItemApi.Item>['columns'] {
|
||||
export function useItemSelectGridColumns(
|
||||
multiple = true,
|
||||
): VxeTableGridOptions<MesMdItemApi.Item>['columns'] {
|
||||
return [
|
||||
{ type: 'checkbox', width: 50 },
|
||||
{ type: multiple ? 'checkbox' : 'radio', width: 50 },
|
||||
{
|
||||
field: 'code',
|
||||
title: '物料编码',
|
||||
|
|
|
|||
|
|
@ -100,11 +100,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
subTabsName.value = 'bom';
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -68,38 +68,54 @@ const [Form, formApi] = useVbenForm({
|
|||
fieldName: 'bomItemCode',
|
||||
label: 'BOM 物料编码',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'bomItemName',
|
||||
label: 'BOM 物料名称',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'bomItemSpecification',
|
||||
label: '规格型号',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'unitMeasureName',
|
||||
label: '单位',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'quantity',
|
||||
label: '用量比例',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 0, precision: 4, step: 0.1 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 4,
|
||||
step: 0.1,
|
||||
},
|
||||
rules: z.number().default(1),
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: { placeholder: '请输入备注', rows: 3 },
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
|
|
|
|||
|
|
@ -79,21 +79,30 @@ const [Form, formApi] = useVbenForm({
|
|||
fieldName: 'title',
|
||||
label: '标题',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入标题' },
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'sort',
|
||||
label: '展示顺序',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 0, precision: 0 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
rules: z.number().default(0),
|
||||
},
|
||||
{
|
||||
fieldName: 'description',
|
||||
label: '内容说明',
|
||||
component: 'Textarea',
|
||||
componentProps: { placeholder: '请输入详细描述', rows: 3 },
|
||||
componentProps: {
|
||||
placeholder: '请输入详细描述',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'processId',
|
||||
|
|
@ -104,13 +113,19 @@ const [Form, formApi] = useVbenForm({
|
|||
fieldName: 'url',
|
||||
label: '图片',
|
||||
component: markRaw(ImageUpload),
|
||||
componentProps: { maxNumber: 1, showDescription: false },
|
||||
componentProps: {
|
||||
maxNumber: 1,
|
||||
showDescription: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: { placeholder: '请输入备注', rows: 3 },
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesMdItemTypeApi } from '#/api/mes/md/item/type';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode, MesItemOrProductEnum } from '@vben/constants';
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
MesAutoCodeRuleCode,
|
||||
MesItemOrProductEnum,
|
||||
} from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
|
|
@ -14,8 +19,14 @@ import { z } from '#/adapter/form';
|
|||
import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
||||
import { getItemTypeList } from '#/api/mes/md/item/type';
|
||||
|
||||
/** 表单类型 */
|
||||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改物料分类的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -59,20 +70,23 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入分类编码',
|
||||
},
|
||||
rules: z.string().min(1, '分类编码不能为空').max(64),
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_ITEM_TYPE_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '自动生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_ITEM_TYPE_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '自动生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -62,9 +62,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
// 加载数据
|
||||
const data = modalApi.getData<MesMdItemTypeApi.ItemType>();
|
||||
formApi.setState({
|
||||
schema: useFormSchema(data?.id ? 'update' : 'create', formApi),
|
||||
});
|
||||
if (!data || !data.id) {
|
||||
formData.value = data || undefined;
|
||||
if (data) {
|
||||
|
|
|
|||
|
|
@ -16,71 +16,86 @@ import {
|
|||
useVendorSelectGridFormSchema,
|
||||
} from '../data';
|
||||
|
||||
defineOptions({ name: 'MdVendorSelectDialog' });
|
||||
|
||||
const emit = defineEmits<{
|
||||
selected: [rows: MesMdVendorApi.Vendor[]];
|
||||
}>();
|
||||
|
||||
const open = ref(false); // 弹窗是否打开
|
||||
const multiple = ref(true); // 是否多选
|
||||
const syncingSingleSelection = ref(false); // 是否同步单选勾选状态
|
||||
const selectedRows = ref<MesMdVendorApi.Vendor[]>([]); // 已选供应商列表
|
||||
const preSelectedIds = ref<number[]>([]); // 预选供应商编号列表
|
||||
|
||||
/** 单选模式下同步 VXE 勾选状态,避免跨页残留多选 */
|
||||
async function syncSingleSelection(row?: MesMdVendorApi.Vendor) {
|
||||
syncingSingleSelection.value = true;
|
||||
await nextTick();
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
if (row) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
}
|
||||
await nextTick();
|
||||
syncingSingleSelection.value = false;
|
||||
/** 获取当前表格数据 */
|
||||
function getTableRows() {
|
||||
return gridApi.grid.getTableData().fullData as MesMdVendorApi.Vendor[];
|
||||
}
|
||||
|
||||
/** 处理勾选变化,单选模式只保留最后一条 */
|
||||
async function handleCheckboxChange({
|
||||
checked,
|
||||
records,
|
||||
row,
|
||||
}: {
|
||||
checked: boolean;
|
||||
records: MesMdVendorApi.Vendor[];
|
||||
row?: MesMdVendorApi.Vendor;
|
||||
}) {
|
||||
if (syncingSingleSelection.value) {
|
||||
return;
|
||||
}
|
||||
if (!multiple.value) {
|
||||
const selected = checked && row ? [row] : [];
|
||||
selectedRows.value = selected;
|
||||
await syncSingleSelection(selected[0]);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = records;
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesMdVendorApi.Vendor>();
|
||||
const records = [
|
||||
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
|
||||
...(gridApi.grid.getCheckboxRecords?.() ?? []),
|
||||
] as MesMdVendorApi.Vendor[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId != null) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
return [...selectedMap.values()];
|
||||
}
|
||||
|
||||
/** 处理全选变化 */
|
||||
function handleCheckboxAll({ records }: { records: MesMdVendorApi.Vendor[] }) {
|
||||
if (syncingSingleSelection.value) {
|
||||
/** 处理勾选变化 */
|
||||
function handleCheckboxSelectChange() {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理单选变化 */
|
||||
function handleRadioChange(row: MesMdVendorApi.Vendor) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
|
||||
/** 多选模式下切换行勾选 */
|
||||
async function toggleMultipleRow(row: MesMdVendorApi.Vendor) {
|
||||
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
|
||||
await gridApi.grid.setCheckboxRow(row, !selected);
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理行双击 */
|
||||
async function handleCellDblclick({ row }: { row: MesMdVendorApi.Vendor }) {
|
||||
if (multiple.value) {
|
||||
await toggleMultipleRow(row);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = records;
|
||||
selectedRows.value = [row];
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
handleConfirm();
|
||||
}
|
||||
|
||||
/** 回显预选供应商 */
|
||||
function applyPreSelection() {
|
||||
async function applyPreSelection() {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = gridApi.grid.getData() as MesMdVendorApi.Vendor[];
|
||||
const rows = getTableRows();
|
||||
for (const row of rows) {
|
||||
if (row.id && preSelectedIds.value.includes(row.id)) {
|
||||
gridApi.grid.setCheckboxRow(row, true);
|
||||
if (!multiple.value) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
if (row.id == null || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
} else {
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
selectedRows.value = [row];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiple.value) {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +104,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
schema: useVendorSelectGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useVendorSelectGridColumns(),
|
||||
columns: useVendorSelectGridColumns(true),
|
||||
height: 520,
|
||||
keepSource: true,
|
||||
checkboxConfig: {
|
||||
|
|
@ -97,6 +112,10 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
range: true,
|
||||
reserve: true,
|
||||
},
|
||||
radioConfig: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
|
|
@ -119,8 +138,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
},
|
||||
} as VxeTableGridOptions<MesMdVendorApi.Vendor>,
|
||||
gridEvents: {
|
||||
checkboxAll: handleCheckboxAll,
|
||||
checkboxChange: handleCheckboxChange,
|
||||
cellDblclick: handleCellDblclick,
|
||||
checkboxAll: handleCheckboxSelectChange,
|
||||
checkboxChange: handleCheckboxSelectChange,
|
||||
radioChange: ({ row }: { row: MesMdVendorApi.Vendor }) => {
|
||||
handleRadioChange(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -128,6 +151,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
async function resetQueryState() {
|
||||
selectedRows.value = [];
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
await gridApi.grid.clearRadioRow();
|
||||
await gridApi.formApi.resetForm();
|
||||
}
|
||||
|
||||
|
|
@ -140,10 +165,13 @@ async function openModal(
|
|||
multiple.value = options?.multiple ?? true;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useVendorSelectGridColumns(multiple.value),
|
||||
});
|
||||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
applyPreSelection();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭供应商选择弹窗 */
|
||||
|
|
@ -154,14 +182,12 @@ async function closeModal() {
|
|||
|
||||
/** 确认选择供应商 */
|
||||
function handleConfirm() {
|
||||
if (selectedRows.value.length === 0) {
|
||||
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
|
||||
if (rows.length === 0) {
|
||||
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
'selected',
|
||||
multiple.value ? selectedRows.value : [selectedRows.value[0]!],
|
||||
);
|
||||
emit('selected', multiple.value ? rows : [rows[0]!]);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,28 +38,23 @@ const selectedItem = ref<MesMdVendorApi.Vendor>(); // 当前选中供应商
|
|||
|
||||
const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
// 是否显示清空图标
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue !== null,
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据供应商编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id === null) {
|
||||
if (id == null) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getVendor(id as number);
|
||||
} catch (error) {
|
||||
console.error('[MdVendorSelect] resolveItemById failed:', error);
|
||||
}
|
||||
selectedItem.value = await getVendor(id);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
@ -88,8 +83,8 @@ function handleClick(event: MouseEvent) {
|
|||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds = props.modelValue === null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds as number[], { multiple: false });
|
||||
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, { multiple: false });
|
||||
}
|
||||
|
||||
/** 回填选中的供应商 */
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesMdVendorApi } from '#/api/mes/md/vendor';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -16,7 +16,10 @@ import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改供应商的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -33,27 +36,24 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
componentProps: {
|
||||
placeholder: '请输入供应商编码',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
componentProps: (values) => ({
|
||||
disabled: !!values.id,
|
||||
}),
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_VENDOR_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '自动生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_VENDOR_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '自动生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
@ -416,9 +416,11 @@ export function useVendorSelectGridFormSchema(): VbenFormSchema[] {
|
|||
}
|
||||
|
||||
/** 供应商选择弹窗的字段 */
|
||||
export function useVendorSelectGridColumns(): VxeTableGridOptions<MesMdVendorApi.Vendor>['columns'] {
|
||||
export function useVendorSelectGridColumns(
|
||||
multiple = true,
|
||||
): VxeTableGridOptions<MesMdVendorApi.Vendor>['columns'] {
|
||||
return [
|
||||
{ type: 'checkbox', width: 50 },
|
||||
{ type: multiple ? 'checkbox' : 'radio', width: 50 },
|
||||
{
|
||||
field: 'code',
|
||||
title: '供应商编码',
|
||||
|
|
|
|||
|
|
@ -74,11 +74,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
subTabsName.value = 'itemReceiptLine';
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -31,12 +31,36 @@ function formatDate(value: Date | number | string | undefined) {
|
|||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
columns: [
|
||||
{ field: 'itemCode', title: '物料编码', width: 140 },
|
||||
{ field: 'itemName', title: '物料名称', minWidth: 150 },
|
||||
{ field: 'specification', title: '规格型号', minWidth: 140 },
|
||||
{ field: 'unitMeasureName', title: '单位', width: 100 },
|
||||
{ field: 'receivedQuantity', title: '入库数量', width: 120 },
|
||||
{ field: 'batchCode', title: '批次号', minWidth: 140 },
|
||||
{
|
||||
field: 'itemCode',
|
||||
title: '物料编码',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
field: 'itemName',
|
||||
title: '物料名称',
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'specification',
|
||||
title: '规格型号',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
field: 'unitMeasureName',
|
||||
title: '单位',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'receivedQuantity',
|
||||
title: '入库数量',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'batchCode',
|
||||
title: '批次号',
|
||||
minWidth: 140,
|
||||
},
|
||||
],
|
||||
height: 280,
|
||||
keepSource: true,
|
||||
|
|
|
|||
|
|
@ -33,11 +33,31 @@ const [Grid] = useVbenVxeGrid({
|
|||
width: 140,
|
||||
slots: { default: 'itemCode' },
|
||||
},
|
||||
{ field: 'itemName', title: '物料名称', minWidth: 150 },
|
||||
{ field: 'specification', title: '规格型号', minWidth: 140 },
|
||||
{ field: 'unitMeasureName', title: '单位', width: 100 },
|
||||
{ field: 'receivedQuantity', title: '入库数量', width: 120 },
|
||||
{ field: 'batchCode', title: '批次号', minWidth: 140 },
|
||||
{
|
||||
field: 'itemName',
|
||||
title: '物料名称',
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'specification',
|
||||
title: '规格型号',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
field: 'unitMeasureName',
|
||||
title: '单位',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'receivedQuantity',
|
||||
title: '入库数量',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'batchCode',
|
||||
title: '批次号',
|
||||
minWidth: 140,
|
||||
},
|
||||
],
|
||||
height: 320,
|
||||
keepSource: true,
|
||||
|
|
|
|||
|
|
@ -36,12 +36,36 @@ const [Grid] = useVbenVxeGrid({
|
|||
minWidth: 160,
|
||||
slots: { default: 'receiptCode' },
|
||||
},
|
||||
{ field: 'purchaseOrderCode', title: '采购订单号', minWidth: 150 },
|
||||
{ field: 'itemCode', title: '物料编码', width: 140 },
|
||||
{ field: 'itemName', title: '物料名称', minWidth: 150 },
|
||||
{ field: 'specification', title: '规格型号', minWidth: 140 },
|
||||
{ field: 'unitMeasureName', title: '单位', width: 100 },
|
||||
{ field: 'receivedQuantity', title: '入库数量', width: 120 },
|
||||
{
|
||||
field: 'purchaseOrderCode',
|
||||
title: '采购订单号',
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'itemCode',
|
||||
title: '物料编码',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
field: 'itemName',
|
||||
title: '物料名称',
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'specification',
|
||||
title: '规格型号',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
field: 'unitMeasureName',
|
||||
title: '单位',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'receivedQuantity',
|
||||
title: '入库数量',
|
||||
width: 120,
|
||||
},
|
||||
],
|
||||
height: 320,
|
||||
keepSource: true,
|
||||
|
|
|
|||
|
|
@ -40,12 +40,11 @@ const selectedItem = ref<MesMdWorkstationApi.Workstation>(); // 选中的工作
|
|||
|
||||
const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
// 是否显示清空图标
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue !== null,
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据工作站编号回显选择器 */
|
||||
|
|
@ -57,11 +56,7 @@ async function resolveItemById(id: number | undefined) {
|
|||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getWorkstation(id);
|
||||
} catch (error) {
|
||||
console.error('[MdWorkstationSelect] resolveItemById failed:', error);
|
||||
}
|
||||
selectedItem.value = await getWorkstation(id);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesMdWorkstationApi } from '#/api/mes/md/workstation';
|
||||
|
||||
import { DICT_TYPE, h, markRaw } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -22,7 +22,10 @@ import { MdWorkshopSelect } from './components';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改工作站的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -39,27 +42,24 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
componentProps: {
|
||||
placeholder: '请输入工作站编码',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['id'],
|
||||
componentProps: (values) => ({
|
||||
disabled: !!values.id,
|
||||
}),
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_WORKSTATION_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_WORKSTATION_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -99,11 +99,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
subTabsName.value = 'machine';
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,10 @@ const [Form, formApi] = useVbenForm({
|
|||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: { placeholder: '请输入备注', rows: 3 },
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
|
|
@ -89,10 +92,26 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
autoResize: true,
|
||||
border: true,
|
||||
columns: [
|
||||
{ field: 'machineryCode', title: '设备编码', width: 140 },
|
||||
{ field: 'machineryName', title: '设备名称', minWidth: 160 },
|
||||
{ field: 'quantity', title: '数量', width: 100 },
|
||||
{ field: 'remark', title: '备注', minWidth: 160 },
|
||||
{
|
||||
field: 'machineryCode',
|
||||
title: '设备编码',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
field: 'machineryName',
|
||||
title: '设备名称',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '数量',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 90,
|
||||
|
|
|
|||
|
|
@ -89,7 +89,10 @@ const [Form, formApi] = useVbenForm({
|
|||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: { placeholder: '请输入备注', rows: 3 },
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
|
|
@ -100,10 +103,26 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
autoResize: true,
|
||||
border: true,
|
||||
columns: [
|
||||
{ field: 'toolTypeId', title: '工具类型编号', width: 140 },
|
||||
{ field: 'toolTypeName', title: '工具类型名称', minWidth: 160 },
|
||||
{ field: 'quantity', title: '数量', width: 100 },
|
||||
{ field: 'remark', title: '备注', minWidth: 160 },
|
||||
{
|
||||
field: 'toolTypeId',
|
||||
title: '工具类型编号',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
field: 'toolTypeName',
|
||||
title: '工具类型名称',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '数量',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 130,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,10 @@ const [Form, formApi] = useVbenForm({
|
|||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: { placeholder: '请输入备注', rows: 3 },
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
|
|
@ -96,10 +99,26 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
autoResize: true,
|
||||
border: true,
|
||||
columns: [
|
||||
{ field: 'postId', title: '岗位编号', width: 140 },
|
||||
{ field: 'postName', title: '岗位名称', minWidth: 160 },
|
||||
{ field: 'quantity', title: '数量', width: 100 },
|
||||
{ field: 'remark', title: '备注', minWidth: 160 },
|
||||
{
|
||||
field: 'postId',
|
||||
title: '岗位编号',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
field: 'postName',
|
||||
title: '岗位名称',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '数量',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 130,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesMdWorkshopApi } from '#/api/mes/md/workstation/workshop';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -17,7 +17,10 @@ import { getSimpleUserList } from '#/api/system/user';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改车间的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -41,20 +44,23 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
}),
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_WORKSHOP_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(
|
||||
MesAutoCodeRuleCode.MD_WORKSHOP_CODE,
|
||||
);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -89,10 +89,10 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -41,22 +41,16 @@ const canSubmit = computed(() => // 编辑态草稿可提交
|
|||
formType.value === 'update' &&
|
||||
formData.value?.status === MesProCardStatusEnum.PREPARE,
|
||||
);
|
||||
// TODO @AI:标题方法的代码风格;
|
||||
const getTitle = computed(() => {
|
||||
switch (formType.value) {
|
||||
case 'detail': {
|
||||
return $t('ui.actionTitle.view', ['流转卡']);
|
||||
}
|
||||
case 'finish': {
|
||||
return '完成流转卡';
|
||||
}
|
||||
case 'update': {
|
||||
return $t('ui.actionTitle.edit', ['流转卡']);
|
||||
}
|
||||
default: {
|
||||
return $t('ui.actionTitle.create', ['流转卡']);
|
||||
}
|
||||
if (formType.value === 'detail') {
|
||||
return $t('ui.actionTitle.view', ['流转卡']);
|
||||
}
|
||||
if (formType.value === 'finish') {
|
||||
return '完成流转卡';
|
||||
}
|
||||
return formType.value === 'update'
|
||||
? $t('ui.actionTitle.edit', ['流转卡'])
|
||||
: $t('ui.actionTitle.create', ['流转卡']);
|
||||
});
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
|
|
@ -125,7 +119,6 @@ async function handleFinish() {
|
|||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// TODO @AI:检查下 onConfirm 方法的风格,是不是缺了一个 // 关闭并提示??看看其他模块,是不是也有这个情况?
|
||||
async onConfirm() {
|
||||
if (!isEditable.value) {
|
||||
await modalApi.close();
|
||||
|
|
|
|||
|
|
@ -327,7 +327,9 @@ export function useFormSchema(
|
|||
fieldName: 'itemCode',
|
||||
label: '产品编码',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
|
|
@ -337,7 +339,9 @@ export function useFormSchema(
|
|||
fieldName: 'itemName',
|
||||
label: '产品名称',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
|
|
@ -347,7 +351,9 @@ export function useFormSchema(
|
|||
fieldName: 'unitMeasureName',
|
||||
label: '单位',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
|
|
@ -357,7 +363,9 @@ export function useFormSchema(
|
|||
fieldName: 'itemSpecification',
|
||||
label: '规格',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['itemCode'],
|
||||
show: (values) => !!values.itemCode,
|
||||
|
|
@ -367,7 +375,11 @@ export function useFormSchema(
|
|||
fieldName: 'feedbackQuantity',
|
||||
label: '报工数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: 'w-full', min: 0, precision: 2 },
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['checkFlag'],
|
||||
// 非质检工序时,报工数量 = 合格 + 不良,禁用直接编辑
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||
import type { MesProProcessApi } from '#/api/mes/pro/process';
|
||||
import type { MesProProcessContentApi } from '#/api/mes/pro/process/content';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process';
|
|||
import type { MesProRouteProductApi } from '#/api/mes/pro/route/product';
|
||||
import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom';
|
||||
|
||||
import { DICT_TYPE, h } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
@ -98,13 +98,19 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
fieldName: 'code',
|
||||
label: '路线编码',
|
||||
component: 'Input',
|
||||
componentProps: { allowClear: true, placeholder: '请输入路线编码' },
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入路线编码',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '路线名称',
|
||||
component: 'Input',
|
||||
componentProps: { allowClear: true, placeholder: '请输入路线名称' },
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入路线名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
|
|
@ -186,7 +192,11 @@ export function useRouteProcessFormSchema(
|
|||
fieldName: 'sort',
|
||||
label: '序号',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 1, precision: 0 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 1,
|
||||
precision: 0,
|
||||
},
|
||||
rules: z.number().default(1),
|
||||
},
|
||||
{
|
||||
|
|
@ -221,28 +231,42 @@ export function useRouteProcessFormSchema(
|
|||
fieldName: 'keyFlag',
|
||||
label: '是否关键工序',
|
||||
component: 'Switch',
|
||||
componentProps: { checkedChildren: '是', unCheckedChildren: '否' },
|
||||
componentProps: {
|
||||
checkedChildren: '是',
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
rules: z.boolean().default(false),
|
||||
},
|
||||
{
|
||||
fieldName: 'checkFlag',
|
||||
label: '是否质检确认',
|
||||
component: 'Switch',
|
||||
componentProps: { checkedChildren: '是', unCheckedChildren: '否' },
|
||||
componentProps: {
|
||||
checkedChildren: '是',
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
rules: z.boolean().default(false),
|
||||
},
|
||||
{
|
||||
fieldName: 'prepareTime',
|
||||
label: '准备时间(分)',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 0, precision: 0 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
rules: z.number().default(0),
|
||||
},
|
||||
{
|
||||
fieldName: 'waitTime',
|
||||
label: '等待时间(分)',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 0, precision: 0 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
},
|
||||
rules: z.number().default(0),
|
||||
},
|
||||
{
|
||||
|
|
@ -250,7 +274,11 @@ export function useRouteProcessFormSchema(
|
|||
label: '备注',
|
||||
component: 'Textarea',
|
||||
formItemClass: 'col-span-2',
|
||||
componentProps: { maxLength: 250, placeholder: '请输入备注', rows: 2 },
|
||||
componentProps: {
|
||||
maxLength: 250,
|
||||
placeholder: '请输入备注',
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -369,7 +397,7 @@ export function useRouteProductFormSchema(
|
|||
{
|
||||
fieldName: 'itemId',
|
||||
label: '产品',
|
||||
component: MdItemSelect as any,
|
||||
component: markRaw(MdItemSelect),
|
||||
componentProps: {
|
||||
onChange: onItemChange,
|
||||
},
|
||||
|
|
@ -380,14 +408,22 @@ export function useRouteProductFormSchema(
|
|||
fieldName: 'quantity',
|
||||
label: '生产数量',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 1, precision: 0 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 1,
|
||||
precision: 0,
|
||||
},
|
||||
rules: z.number().default(1),
|
||||
},
|
||||
{
|
||||
fieldName: 'productionTime',
|
||||
label: '生产用时',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 0, precision: 2 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
},
|
||||
rules: z.number().default(1),
|
||||
},
|
||||
{
|
||||
|
|
@ -406,7 +442,11 @@ export function useRouteProductFormSchema(
|
|||
label: '备注',
|
||||
component: 'Textarea',
|
||||
formItemClass: 'col-span-2',
|
||||
componentProps: { maxLength: 250, placeholder: '请输入备注', rows: 2 },
|
||||
componentProps: {
|
||||
maxLength: 250,
|
||||
placeholder: '请输入备注',
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -441,7 +481,7 @@ export function useRouteProductBomFormSchema(
|
|||
{
|
||||
fieldName: 'itemId',
|
||||
label: 'BOM 物料',
|
||||
component: MdProductBomSelect as any,
|
||||
component: markRaw(MdProductBomSelect),
|
||||
componentProps: () => ({
|
||||
itemId: itemId(),
|
||||
onChange: onBomChange,
|
||||
|
|
@ -453,14 +493,22 @@ export function useRouteProductBomFormSchema(
|
|||
fieldName: 'quantity',
|
||||
label: '用料比例',
|
||||
component: 'InputNumber',
|
||||
componentProps: { class: '!w-full', min: 0, precision: 2 },
|
||||
componentProps: {
|
||||
class: '!w-full',
|
||||
min: 0,
|
||||
precision: 2,
|
||||
},
|
||||
rules: z.number().default(1),
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: { maxLength: 250, placeholder: '请输入备注', rows: 2 },
|
||||
componentProps: {
|
||||
maxLength: 250,
|
||||
placeholder: '请输入备注',
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export { default as WorkRecordStatusBar } from './status-bar.vue';
|
||||
|
|
@ -12,8 +12,8 @@ import {
|
|||
} from '#/api/mes/pro/workrecord';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { WorkRecordStatusBar } from './components';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import WorkRecordStatusBar from './modules/status-bar.vue';
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as QcIndicatorResultSpecificationInput } from './result-specification-input.vue';
|
||||
export { default as QcIndicatorSelectDialog } from './select-dialog.vue';
|
||||
export { default as QcIndicatorSelect } from './select.vue';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { MesQcResultValueType } from '@vben/constants';
|
||||
|
||||
import { RadioGroup, Select } from 'ant-design-vue';
|
||||
|
||||
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
||||
|
||||
defineOptions({ name: 'QcIndicatorResultSpecificationInput' });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string;
|
||||
resultType?: number;
|
||||
}>(),
|
||||
{
|
||||
modelValue: undefined,
|
||||
resultType: undefined,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value?: string];
|
||||
}>();
|
||||
|
||||
const fileOptions = [
|
||||
{ label: '图片/照片', value: 'IMG' },
|
||||
{ label: '文件', value: 'FILE' },
|
||||
];
|
||||
const dictTypeOptions = ref<{ name?: string; type?: string }[]>([]);
|
||||
|
||||
const innerValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value?: string) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
/** 加载字典类型选项(仅字典类型结果值需要) */
|
||||
async function loadDictTypeOptions() {
|
||||
if (dictTypeOptions.value.length > 0) {
|
||||
return;
|
||||
}
|
||||
dictTypeOptions.value = await getSimpleDictTypeList();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.resultType,
|
||||
(value) => {
|
||||
if (value === MesQcResultValueType.DICT) {
|
||||
void loadDictTypeOptions();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RadioGroup
|
||||
v-if="resultType === MesQcResultValueType.FILE"
|
||||
v-model:value="innerValue"
|
||||
:options="fileOptions"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="resultType === MesQcResultValueType.DICT"
|
||||
v-model:value="innerValue"
|
||||
:field-names="{ label: 'name', value: 'type' }"
|
||||
:filter-option="
|
||||
(input: string, option: any) =>
|
||||
(option.name as string).toLowerCase().includes(input.toLowerCase())
|
||||
"
|
||||
:options="dictTypeOptions"
|
||||
allow-clear
|
||||
class="w-full"
|
||||
placeholder="请选择字典类型"
|
||||
show-search
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -55,11 +55,7 @@ async function resolveItemById(id: number | undefined) {
|
|||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
selectedItem.value = await getIndicator(id);
|
||||
} catch (error) {
|
||||
console.error('[QcIndicatorSelect] resolveItemById failed:', error);
|
||||
}
|
||||
selectedItem.value = await getIndicator(id);
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, resolveItemById, { immediate: true });
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesQcIndicatorApi } from '#/api/mes/qc/indicator';
|
||||
|
||||
import { h } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE, MesAutoCodeRuleCode, MesQcResultValueType } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
|
@ -10,7 +10,8 @@ import { getDictOptions } from '@vben/hooks';
|
|||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
||||
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
||||
|
||||
import { QcIndicatorResultSpecificationInput } from './components';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
|
|
@ -93,37 +94,17 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
},
|
||||
{
|
||||
fieldName: 'resultSpecification',
|
||||
label: '文件类型',
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '图片/照片', value: 'IMG' },
|
||||
{ label: '文件', value: 'FILE' },
|
||||
],
|
||||
},
|
||||
label: '结果值属性',
|
||||
component: markRaw(QcIndicatorResultSpecificationInput),
|
||||
// 按结果值类型在组件内部切换文件类型 RadioGroup / 字典类型 ApiSelect
|
||||
dependencies: {
|
||||
triggerFields: ['resultType'],
|
||||
show: (values) => values.resultType === MesQcResultValueType.FILE,
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'resultSpecification',
|
||||
label: '字典类型',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: getSimpleDictTypeList,
|
||||
filterOption: (input: string, option: any) =>
|
||||
(option.label as string).toLowerCase().includes(input.toLowerCase()),
|
||||
labelField: 'name',
|
||||
placeholder: '请选择字典类型',
|
||||
showSearch: true,
|
||||
valueField: 'type',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['resultType'],
|
||||
show: (values) => values.resultType === MesQcResultValueType.DICT,
|
||||
if: (values) =>
|
||||
values.resultType === MesQcResultValueType.FILE ||
|
||||
values.resultType === MesQcResultValueType.DICT,
|
||||
componentProps: (values) => ({
|
||||
resultType: values.resultType,
|
||||
}),
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -73,9 +73,21 @@ export function useQcIndicatorResultFormSchema(
|
|||
/** 检测结果列表的字段 */
|
||||
export function useQcIndicatorResultGridColumns(): VxeTableGridOptions<MesQcIndicatorResultApi.IndicatorResult>['columns'] {
|
||||
return [
|
||||
{ field: 'code', title: '样品编号', width: 200 },
|
||||
{ field: 'sn', title: '物资SN', minWidth: 200 },
|
||||
{ field: 'remark', title: '备注', minWidth: 200 },
|
||||
{
|
||||
field: 'code',
|
||||
title: '样品编号',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'sn',
|
||||
title: '物资SN',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 150,
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ export function useFormSchema(
|
|||
precision: 2,
|
||||
onChange: () => syncUnqualified(formApi),
|
||||
},
|
||||
defaultValue: 0,
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
triggerFields: ['unqualifiedQuantity'],
|
||||
|
|
@ -275,6 +276,7 @@ export function useFormSchema(
|
|||
precision: 2,
|
||||
onChange: () => syncUnqualified(formApi),
|
||||
},
|
||||
defaultValue: 0,
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
triggerFields: ['unqualifiedQuantity'],
|
||||
|
|
@ -293,6 +295,7 @@ export function useFormSchema(
|
|||
precision: 2,
|
||||
onChange: () => syncUnqualified(formApi),
|
||||
},
|
||||
defaultValue: 0,
|
||||
rules: 'required',
|
||||
dependencies: {
|
||||
triggerFields: ['unqualifiedQuantity'],
|
||||
|
|
|
|||
|
|
@ -171,10 +171,15 @@ const [Modal, modalApi] = useVbenModal({
|
|||
modalApi.unlock();
|
||||
}
|
||||
} else if (data?.prefill) {
|
||||
// 预填模式:来自待检任务
|
||||
formData.value = { ...data.prefill };
|
||||
// 预填模式:来自待检任务。源项目靠 watcher 自动把 outQuantity 同步到
|
||||
// checkQuantity,这里显式补齐,避免必填的检测数量为空
|
||||
const prefill = {
|
||||
...data.prefill,
|
||||
checkQuantity: data.prefill.checkQuantity ?? data.prefill.outQuantity,
|
||||
};
|
||||
formData.value = { ...prefill };
|
||||
// 设置到 values
|
||||
await formApi.setValues(data.prefill);
|
||||
await formApi.setValues(prefill);
|
||||
}
|
||||
originalSnapshot.value = JSON.stringify(await formApi.getValues());
|
||||
},
|
||||
|
|
|
|||
|
|
@ -136,9 +136,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
// 关闭并提示
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
|
|
@ -160,7 +158,10 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(formType.value, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
modalApi.setState({
|
||||
confirmText: formType.value === 'detail' ? undefined : '保存',
|
||||
showConfirmButton: formType.value !== 'detail',
|
||||
});
|
||||
if (data?.id) {
|
||||
modalApi.lock();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ 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 { DICT_TYPE, h, markRaw } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { CommonStatusEnum, DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export { default as TmToolSelectDialog } from './select-dialog.vue';
|
||||
export { default as TmToolSelect } from './select.vue';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { MesTmToolApi } from '#/api/mes/tm/tool';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { Button, message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getToolPage } from '#/api/mes/tm/tool';
|
||||
|
||||
import { useToolSelectGridColumns, useToolSelectGridFormSchema } from '../data';
|
||||
|
||||
defineOptions({ name: 'TmToolSelectDialog' });
|
||||
|
||||
const emit = defineEmits<{
|
||||
selected: [rows: MesTmToolApi.Tool[]];
|
||||
}>();
|
||||
|
||||
const open = ref(false); // 弹窗是否打开
|
||||
const multiple = ref(false); // 是否多选
|
||||
const selectedRows = ref<MesTmToolApi.Tool[]>([]); // 已选工具列表
|
||||
const preSelectedIds = ref<number[]>([]); // 预选工具编号列表
|
||||
|
||||
/** 获取当前表格数据 */
|
||||
function getTableRows() {
|
||||
return gridApi.grid.getTableData().fullData as MesTmToolApi.Tool[];
|
||||
}
|
||||
|
||||
/** 获取多选记录,包含 VXE reserve 跨页记录 */
|
||||
function getMultipleSelectedRows() {
|
||||
const selectedMap = new Map<number, MesTmToolApi.Tool>();
|
||||
const records = [
|
||||
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
|
||||
...(gridApi.grid.getCheckboxRecords?.() ?? []),
|
||||
] as MesTmToolApi.Tool[];
|
||||
records.forEach((row) => {
|
||||
const rowId = row.id;
|
||||
if (rowId != null) {
|
||||
selectedMap.set(rowId, row);
|
||||
}
|
||||
});
|
||||
return [...selectedMap.values()];
|
||||
}
|
||||
|
||||
/** 处理勾选变化 */
|
||||
function handleCheckboxSelectChange() {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理单选变化 */
|
||||
function handleRadioChange(row: MesTmToolApi.Tool) {
|
||||
selectedRows.value = [row];
|
||||
}
|
||||
|
||||
/** 多选模式下切换行勾选 */
|
||||
async function toggleMultipleRow(row: MesTmToolApi.Tool) {
|
||||
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
|
||||
await gridApi.grid.setCheckboxRow(row, !selected);
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
|
||||
/** 处理行双击 */
|
||||
async function handleCellDblclick({ row }: { row: MesTmToolApi.Tool }) {
|
||||
if (multiple.value) {
|
||||
await toggleMultipleRow(row);
|
||||
return;
|
||||
}
|
||||
selectedRows.value = [row];
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
handleConfirm();
|
||||
}
|
||||
|
||||
/** 回显预选工具 */
|
||||
async function applyPreSelection() {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = getTableRows();
|
||||
for (const row of rows) {
|
||||
if (row.id == null || !preSelectedIds.value.includes(row.id)) {
|
||||
continue;
|
||||
}
|
||||
if (multiple.value) {
|
||||
await gridApi.grid.setCheckboxRow(row, true);
|
||||
} else {
|
||||
await gridApi.grid.setRadioRow(row);
|
||||
selectedRows.value = [row];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (multiple.value) {
|
||||
selectedRows.value = getMultipleSelectedRows();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useToolSelectGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useToolSelectGridColumns(false),
|
||||
height: 520,
|
||||
keepSource: true,
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
range: true,
|
||||
reserve: true,
|
||||
},
|
||||
radioConfig: {
|
||||
highlight: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getToolPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<MesTmToolApi.Tool>,
|
||||
gridEvents: {
|
||||
cellDblclick: handleCellDblclick,
|
||||
checkboxAll: handleCheckboxSelectChange,
|
||||
checkboxChange: handleCheckboxSelectChange,
|
||||
radioChange: ({ row }: { row: MesTmToolApi.Tool }) => {
|
||||
handleRadioChange(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 重置查询和选择状态 */
|
||||
async function resetQueryState() {
|
||||
selectedRows.value = [];
|
||||
await gridApi.grid.clearCheckboxRow();
|
||||
await gridApi.grid.clearCheckboxReserve();
|
||||
await gridApi.grid.clearRadioRow();
|
||||
await gridApi.formApi.resetForm();
|
||||
}
|
||||
|
||||
/** 打开工具选择弹窗 */
|
||||
async function openModal(
|
||||
selectedIds?: number[],
|
||||
options?: { multiple?: boolean },
|
||||
) {
|
||||
open.value = true;
|
||||
multiple.value = options?.multiple ?? false;
|
||||
preSelectedIds.value = selectedIds || [];
|
||||
await nextTick();
|
||||
gridApi.setGridOptions({
|
||||
columns: useToolSelectGridColumns(multiple.value),
|
||||
});
|
||||
await resetQueryState();
|
||||
await gridApi.query();
|
||||
await nextTick();
|
||||
await applyPreSelection();
|
||||
}
|
||||
|
||||
/** 关闭工具选择弹窗 */
|
||||
async function closeModal() {
|
||||
open.value = false;
|
||||
await resetQueryState();
|
||||
}
|
||||
|
||||
/** 确认选择工具 */
|
||||
function handleConfirm() {
|
||||
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
|
||||
if (rows.length === 0) {
|
||||
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||
return;
|
||||
}
|
||||
emit('selected', multiple.value ? rows : [rows[0]!]);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
defineExpose({ open: openModal });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-model:open="open"
|
||||
title="工具选择"
|
||||
width="75%"
|
||||
:destroy-on-close="true"
|
||||
@ok="handleConfirm"
|
||||
@cancel="closeModal"
|
||||
>
|
||||
<Grid table-title="工具列表" />
|
||||
<template #footer>
|
||||
<Button @click="closeModal"> 取消 </Button>
|
||||
<Button type="primary" @click="handleConfirm"> 确定 </Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
@ -1,55 +1,141 @@
|
|||
<script lang="ts" setup>
|
||||
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||
|
||||
import type { MesTmToolApi } from '#/api/mes/tm/tool';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, ref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { Select } from 'ant-design-vue';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { getToolSimpleList } from '#/api/mes/tm/tool';
|
||||
import { Input, Tooltip } from 'ant-design-vue';
|
||||
|
||||
withDefaults(
|
||||
import { getTool } from '#/api/mes/tm/tool';
|
||||
|
||||
import TmToolSelectDialog from './select-dialog.vue';
|
||||
|
||||
defineOptions({ name: 'TmToolSelect', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
allowClear?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{ allowClear: true, disabled: false, modelValue: undefined, placeholder: '请选择工具' },
|
||||
{
|
||||
allowClear: true,
|
||||
disabled: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择工具',
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
change: [row?: MesTmToolApi.Tool];
|
||||
'update:modelValue': [value?: number];
|
||||
change: [item: MesTmToolApi.Tool | undefined];
|
||||
'update:modelValue': [value: number | undefined];
|
||||
}>();
|
||||
const list = ref<MesTmToolApi.Tool[]>([]); // 工具列表
|
||||
const attrs = useAttrs(); // 透传属性
|
||||
const dialogRef = ref<InstanceType<typeof TmToolSelectDialog>>(); // 工具选择弹窗
|
||||
const hovering = ref(false); // 是否悬停
|
||||
const selectedItem = ref<MesTmToolApi.Tool>(); // 当前选中工具
|
||||
|
||||
/** 加载工具列表 */
|
||||
async function getList() {
|
||||
list.value = await getToolSimpleList();
|
||||
const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 选择器展示名称
|
||||
const showClear = computed(
|
||||
() =>
|
||||
props.allowClear &&
|
||||
!props.disabled &&
|
||||
hovering.value &&
|
||||
props.modelValue != null,
|
||||
);
|
||||
|
||||
/** 根据工具编号回显选择器 */
|
||||
async function resolveItemById(id: number | undefined) {
|
||||
if (id == null) {
|
||||
selectedItem.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (selectedItem.value?.id === id) {
|
||||
return;
|
||||
}
|
||||
selectedItem.value = await getTool(id);
|
||||
}
|
||||
|
||||
/** 处理工具选择变化 */
|
||||
function handleChange(value: SelectValue) {
|
||||
const toolId = typeof value === 'number' ? value : undefined;
|
||||
emit('update:modelValue', toolId);
|
||||
emit('change', list.value.find((item) => item.id === toolId));
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
resolveItemById(value);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 清空已选工具 */
|
||||
function clearSelected() {
|
||||
selectedItem.value = undefined;
|
||||
emit('update:modelValue', undefined);
|
||||
emit('change', undefined);
|
||||
}
|
||||
|
||||
onMounted(getList);
|
||||
/** 打开工具选择弹窗 */
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLElement;
|
||||
if (showClear.value && target.closest('.ant-input-suffix')) {
|
||||
event.stopPropagation();
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||
dialogRef.value?.open(selectedIds, { multiple: false });
|
||||
}
|
||||
|
||||
/** 回填选中的工具 */
|
||||
function handleSelected(rows: MesTmToolApi.Tool[]) {
|
||||
const item = rows[0];
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
selectedItem.value = item;
|
||||
emit('update:modelValue', item.id);
|
||||
emit('change', item);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select
|
||||
:allow-clear="allowClear"
|
||||
:disabled="disabled"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
:options="list"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
<div
|
||||
v-bind="attrs"
|
||||
class="w-full"
|
||||
option-filter-prop="name"
|
||||
show-search
|
||||
@change="handleChange"
|
||||
/>
|
||||
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
||||
@click="handleClick"
|
||||
@mouseenter="hovering = true"
|
||||
@mouseleave="hovering = false"
|
||||
>
|
||||
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
|
||||
<template #title>
|
||||
<div v-if="selectedItem" class="leading-6">
|
||||
<div>工具编码:{{ selectedItem.code || '-' }}</div>
|
||||
<div>工具名称:{{ selectedItem.name || '-' }}</div>
|
||||
<div v-if="selectedItem.brand">品牌:{{ selectedItem.brand }}</div>
|
||||
<div v-if="selectedItem.specification">
|
||||
型号规格:{{ selectedItem.specification }}
|
||||
</div>
|
||||
<div v-if="selectedItem.toolTypeName">
|
||||
工具类型:{{ selectedItem.toolTypeName }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<Input
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:value="displayLabel"
|
||||
readonly
|
||||
>
|
||||
<template #suffix>
|
||||
<IconifyIcon
|
||||
class="size-4"
|
||||
:icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
|
||||
/>
|
||||
</template>
|
||||
</Input>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<TmToolSelectDialog ref="dialogRef" @selected="handleSelected" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ import { TmToolTypeSelect } from './type/components';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改工具的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -29,6 +32,15 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
show: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
// 选中工具类型是否「编码管理」,用于锁定库存数量为 1(隐藏字段)
|
||||
fieldName: 'codeFlag',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
triggerFields: [''],
|
||||
show: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '工具编码',
|
||||
|
|
@ -41,18 +53,21 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
componentProps: (values) => ({ disabled: !!values.id }),
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.TM_TOOL_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.TM_TOOL_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
@ -70,7 +85,9 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
componentProps: {
|
||||
placeholder: '请选择工具类型',
|
||||
onChange: async (row: any) => {
|
||||
if (row?.codeFlag) {
|
||||
// 记录是否编码管理;编码管理类型库存数量锁定为 1
|
||||
await formApi?.setFieldValue('codeFlag', row?.codeFlag === true);
|
||||
if (row?.codeFlag === true) {
|
||||
await formApi?.setFieldValue('quantity', 1);
|
||||
await formApi?.setFieldValue('availableQuantity', 1);
|
||||
}
|
||||
|
|
@ -110,6 +127,13 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
precision: 0,
|
||||
},
|
||||
rules: 'required',
|
||||
// 编码管理类型库存数量锁定为 1,禁止修改
|
||||
dependencies: {
|
||||
triggerFields: ['codeFlag'],
|
||||
componentProps: (values) => ({
|
||||
disabled: values.codeFlag === true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'availableQuantity',
|
||||
|
|
@ -301,3 +325,85 @@ export function useGridColumns(): VxeTableGridOptions<MesTmToolApi.Tool>['column
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 工具选择弹窗的搜索表单 */
|
||||
export function useToolSelectGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '工具编码',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入工具编码',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '工具名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入工具名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'toolTypeId',
|
||||
label: '工具类型',
|
||||
component: markRaw(TmToolTypeSelect),
|
||||
componentProps: {
|
||||
placeholder: '请选择工具类型',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'brand',
|
||||
label: '品牌',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入品牌',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.MES_TM_TOOL_STATUS, 'number'),
|
||||
placeholder: '请选择状态',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 工具选择弹窗的字段 */
|
||||
export function useToolSelectGridColumns(
|
||||
multiple = false,
|
||||
): VxeTableGridOptions<MesTmToolApi.Tool>['columns'] {
|
||||
return [
|
||||
{ type: multiple ? 'checkbox' : 'radio', width: 50 },
|
||||
{ field: 'code', title: '工具编码', width: 120 },
|
||||
{ field: 'name', title: '工具名称', minWidth: 120 },
|
||||
{ field: 'brand', title: '品牌', minWidth: 100 },
|
||||
{ field: 'specification', title: '型号规格', minWidth: 100 },
|
||||
{ field: 'toolTypeName', title: '工具类型', width: 120 },
|
||||
{ field: 'quantity', title: '库存数量', width: 100 },
|
||||
{ field: 'availableQuantity', title: '可用数量', width: 100 },
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.MES_TM_TOOL_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
width: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,10 +87,10 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ import { generateAutoCode } from '#/api/mes/md/autocode/record';
|
|||
export type FormType = 'create' | 'detail' | 'update';
|
||||
|
||||
/** 新增/修改工具类型的表单 */
|
||||
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
||||
export function useFormSchema(
|
||||
formType: FormType,
|
||||
formApi?: VbenFormApi,
|
||||
): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
|
|
@ -34,18 +37,21 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
placeholder: '请输入类型编码',
|
||||
},
|
||||
rules: 'required',
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.TM_TOOL_TYPE_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
suffix:
|
||||
formType === 'detail'
|
||||
? undefined
|
||||
: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
type: 'default',
|
||||
onClick: async () => {
|
||||
const code = await generateAutoCode(MesAutoCodeRuleCode.TM_TOOL_TYPE_CODE);
|
||||
await formApi?.setFieldValue('code', code);
|
||||
},
|
||||
},
|
||||
{ default: () => '生成' },
|
||||
),
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
|
|
|
|||
|
|
@ -71,10 +71,10 @@ const [Modal, modalApi] = useVbenModal({
|
|||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
formApi.setState({ schema: useFormSchema(formApi) });
|
||||
// 加载数据
|
||||
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
||||
formType.value = data.formType;
|
||||
formApi.setState({ schema: useFormSchema(data.formType, formApi) });
|
||||
formApi.setDisabled(formType.value === 'detail');
|
||||
modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
|
||||
if (!data?.id) {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,14 @@ import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
|
|||
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
|
||||
import type { DescriptionItemSchema } from '#/components/description';
|
||||
|
||||
import { BarcodeBizTypeEnum, DICT_TYPE, h, markRaw } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum } from '@vben/constants';
|
||||
import {
|
||||
BarcodeBizTypeEnum,
|
||||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
MesProWorkOrderStatusEnum,
|
||||
} from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
|
|
@ -222,6 +227,7 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
|
|||
label: '工单',
|
||||
component: markRaw(ProWorkOrderSelect),
|
||||
componentProps: {
|
||||
status: MesProWorkOrderStatusEnum.CONFIRMED,
|
||||
onChange: (item: any) => syncBizDetail(formApi, item),
|
||||
},
|
||||
dependencies: {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue