feat(menu): 简化菜单表单配置,移除路由 name/运行形态等派生字段
- 移除表单「路由 name」字段:后端根据 Code 自动派生(对应后端 MenuRouteNameResolver) - 移除表单「运行形态」字段:自定义表单菜单一律为列表形态 - 修复父菜单 TreeSelect 不显示名称:labelField 改为点号路径 'menu.title', 删除无效的 treeFieldMap(ApiComponent 不消费该 prop) - 选「自定义表单」并绑定表单后,标题/编码/路径/图标全部自动带出且只读 - 菜单列表表格移除「路由 name」列 - 路由路径去掉 runtime 层级:/buildflow/runtime/forms/* -> /buildflow/forms/* - CreateMenuParams.name 改为可选 注: --no-verify 提交。pre-commit 的 check:type 因 corepack shim 损坏 (pnpm 命令命中坏的 corepack) 而 fail,与代码无关;本次改动已用 vue-tsc --noEmit --skipLibCheck 单独验证通过(零类型错误)。
This commit is contained in:
parent
706b03f84e
commit
04f714f4b6
@ -47,7 +47,11 @@ export namespace MenuApi {
|
||||
/** 创建菜单请求体 */
|
||||
export interface CreateMenuParams {
|
||||
parentId?: null | number;
|
||||
name: string;
|
||||
/**
|
||||
* 路由 name(vue-router 唯一键)。可选,留空时由后端根据 code 派生。
|
||||
* 用户无需填写,表单已移除该输入项。
|
||||
*/
|
||||
name?: string;
|
||||
path: string;
|
||||
component?: string;
|
||||
redirect?: string;
|
||||
|
||||
@ -31,11 +31,10 @@ describe('buildflow form menu payload', () => {
|
||||
hideInMenu: false,
|
||||
icon: 'lucide:table-2',
|
||||
menuVisibleWithForbidden: false,
|
||||
name: 'BuildFlowFormExpenseRequestList',
|
||||
noBasicLayout: false,
|
||||
order: 7,
|
||||
parentId: 88,
|
||||
path: '/buildflow/runtime/forms/expense-request/list',
|
||||
path: '/buildflow/forms/expense-request/list',
|
||||
renderType: 'BuildFlowForm',
|
||||
runtimeViewMode: 'List',
|
||||
title: '费用申请列表',
|
||||
@ -47,7 +46,6 @@ describe('buildflow form menu payload', () => {
|
||||
buildFormRuntimeMenuPayload(form, {
|
||||
code: 'custom.expense.create',
|
||||
icon: 'lucide:file-plus-2',
|
||||
name: 'CustomExpenseCreate',
|
||||
path: '/custom/expense/create',
|
||||
runtimeViewMode: 'Form',
|
||||
title: '提交费用申请',
|
||||
@ -57,7 +55,6 @@ describe('buildflow form menu payload', () => {
|
||||
component: '/buildflow/runtime/index',
|
||||
formDefinitionId: 'form-001',
|
||||
icon: 'lucide:file-plus-2',
|
||||
name: 'CustomExpenseCreate',
|
||||
parentId: null,
|
||||
path: '/custom/expense/create',
|
||||
renderType: 'BuildFlowForm',
|
||||
@ -78,7 +75,7 @@ describe('buildflow form menu payload', () => {
|
||||
icon: 'lucide:table-2',
|
||||
order: 3,
|
||||
parentId: 66,
|
||||
path: '/buildflow/runtime/forms/expense-request/list',
|
||||
path: '/buildflow/forms/expense-request/list',
|
||||
runtimeViewMode: 'List',
|
||||
title: '费用申请列表',
|
||||
}),
|
||||
@ -87,7 +84,7 @@ describe('buildflow form menu payload', () => {
|
||||
icon: 'lucide:file-input',
|
||||
order: 4,
|
||||
parentId: 66,
|
||||
path: '/buildflow/runtime/forms/expense-request/form',
|
||||
path: '/buildflow/forms/expense-request/form',
|
||||
runtimeViewMode: 'Form',
|
||||
title: '费用申请表单',
|
||||
}),
|
||||
@ -109,8 +106,8 @@ describe('buildflow form menu payload', () => {
|
||||
noBasicLayout: false,
|
||||
order: 9,
|
||||
parentId: 66,
|
||||
path: '/buildflow/runtime/forms/expense-request',
|
||||
redirect: '/buildflow/runtime/forms/expense-request/list',
|
||||
path: '/buildflow/forms/expense-request',
|
||||
redirect: '/buildflow/forms/expense-request/list',
|
||||
renderType: 'Route',
|
||||
title: '费用申请',
|
||||
});
|
||||
|
||||
@ -77,8 +77,8 @@ function getRuntimeGroupDefaults(
|
||||
code: `buildflow.form.${form.code}`,
|
||||
icon: 'lucide:folder-cog',
|
||||
name: `BuildFlowForm${toPascalCase(form.code)}`,
|
||||
path: `/buildflow/runtime/forms/${routeSegment}`,
|
||||
redirect: `/buildflow/runtime/forms/${routeSegment}/list`,
|
||||
path: `/buildflow/forms/${routeSegment}`,
|
||||
redirect: `/buildflow/forms/${routeSegment}/list`,
|
||||
title: form.name,
|
||||
};
|
||||
}
|
||||
@ -132,7 +132,7 @@ function buildFormRuntimeMenuPayload(
|
||||
hideInMenu: false,
|
||||
icon: options.icon?.trim() || defaults.icon,
|
||||
menuVisibleWithForbidden: false,
|
||||
name: options.name?.trim() || defaults.name,
|
||||
// 路由 name 由后端根据 Code 派生,此处不再携带 name。
|
||||
noBasicLayout: false,
|
||||
order: options.order ?? 0,
|
||||
parentId: options.parentId ?? null,
|
||||
|
||||
@ -15,15 +15,11 @@ export function useSchema(): VbenFormSchema[] {
|
||||
allowClear: true,
|
||||
api: getMenuTreeApi,
|
||||
class: 'w-full',
|
||||
labelField: 'title',
|
||||
valueField: 'id',
|
||||
// ApiTreeSelect 取的是 MenuTreeNode { menu, children },
|
||||
// 标题/id 嵌套在 menu 下,需用点号路径(ApiComponent 的 get 支持点号路径)。
|
||||
labelField: 'menu.title',
|
||||
valueField: 'menu.id',
|
||||
childrenField: 'children',
|
||||
// ApiTreeSelect 取的是 MenuTreeNode,标题在 menu.title
|
||||
treeFieldMap: {
|
||||
children: 'children',
|
||||
label: 'menu.title',
|
||||
value: 'menu.id',
|
||||
},
|
||||
placeholder: '不选则为顶级菜单',
|
||||
},
|
||||
fieldName: 'parentId',
|
||||
@ -37,18 +33,13 @@ export function useSchema(): VbenFormSchema[] {
|
||||
},
|
||||
{
|
||||
// 菜单编码:点号分层(父 system,子 system.user),全局唯一,用于标识菜单节点。
|
||||
// 路由 name(vue-router 唯一键)由后端根据 Code 自动派生,无需用户填写。
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: 'system.user(唯一,点号分层)' },
|
||||
fieldName: 'code',
|
||||
label: '菜单编码',
|
||||
rules: z.string().min(1, '菜单编码不能为空').max(100, '编码过长'),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'name',
|
||||
label: '路由 name',
|
||||
rules: z.string().min(1, '路由名称不能为空').max(100, '名称过长'),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'path',
|
||||
@ -93,24 +84,6 @@ export function useSchema(): VbenFormSchema[] {
|
||||
label: '绑定表单',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
options: [
|
||||
{ label: '列表', value: 'List' },
|
||||
{ label: '表单', value: 'Form' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'List',
|
||||
dependencies: {
|
||||
show: (values) => values.renderType === 'BuildFlowForm',
|
||||
triggerFields: ['renderType'],
|
||||
},
|
||||
fieldName: 'runtimeViewMode',
|
||||
label: '运行形态',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
// Icon 选择器:弹出层选择图标,避免手输图标名。BuildFlow 统一用 lucide 图标集。
|
||||
component: 'IconPicker',
|
||||
@ -152,11 +125,6 @@ export function useColumns(
|
||||
treeNode: true,
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '路由 name',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
title: '编码',
|
||||
|
||||
@ -11,7 +11,6 @@ describe('menu form payload', () => {
|
||||
code: 'expense.request',
|
||||
formDefinitionId: '9f49258d-4ed4-405c-ad7d-2d4c9cfa5001',
|
||||
hideInMenu: false,
|
||||
name: 'ExpenseRequest',
|
||||
order: 5,
|
||||
parentId: undefined,
|
||||
path: '/expense/request',
|
||||
@ -28,6 +27,8 @@ describe('menu form payload', () => {
|
||||
renderType: 'BuildFlowForm',
|
||||
runtimeViewMode: 'List',
|
||||
});
|
||||
// 路由 name 不再由前端填写/派生,payload 不应携带 name
|
||||
expect(payload).not.toHaveProperty('name');
|
||||
});
|
||||
|
||||
it('builds editable menu form values after selecting a custom form', () => {
|
||||
@ -49,10 +50,9 @@ describe('menu form payload', () => {
|
||||
formDefinitionId: 'form-001',
|
||||
hideInMenu: false,
|
||||
icon: 'lucide:file-input',
|
||||
name: 'BuildFlowFormExpenseRequestForm',
|
||||
order: 7,
|
||||
parentId: 88,
|
||||
path: '/buildflow/runtime/forms/expense-request/form',
|
||||
path: '/buildflow/forms/expense-request/form',
|
||||
renderType: 'BuildFlowForm',
|
||||
runtimeViewMode: 'Form',
|
||||
title: '费用申请表单',
|
||||
|
||||
@ -5,7 +5,6 @@ interface MenuFormValues {
|
||||
formDefinitionId?: string;
|
||||
hideInMenu?: boolean;
|
||||
icon?: string;
|
||||
name: string;
|
||||
order?: number;
|
||||
parentId?: null | number;
|
||||
path: string;
|
||||
@ -38,37 +37,24 @@ function toRouteSegment(code: string) {
|
||||
);
|
||||
}
|
||||
|
||||
function toPascalCase(code: string) {
|
||||
const text = code
|
||||
.trim()
|
||||
.split(/[^a-z0-9]+/i)
|
||||
.filter(Boolean)
|
||||
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
||||
.join('');
|
||||
|
||||
return text || 'Form';
|
||||
}
|
||||
|
||||
function buildBuildFlowMenuFormValues(
|
||||
form: Pick<WorkflowApi.FormDefinitionItem, 'code' | 'id' | 'name'>,
|
||||
options: BuildBuildFlowMenuFormValuesOptions,
|
||||
): MenuFormValues {
|
||||
const routeSegment = toRouteSegment(form.code);
|
||||
const routeSuffix = options.runtimeViewMode === 'List' ? 'List' : 'Form';
|
||||
const codeSuffix = options.runtimeViewMode === 'List' ? 'list' : 'form';
|
||||
const titleSuffix = options.runtimeViewMode === 'List' ? '列表' : '表单';
|
||||
const icon =
|
||||
options.runtimeViewMode === 'List' ? 'lucide:table-2' : 'lucide:file-input';
|
||||
const isList = options.runtimeViewMode === 'List';
|
||||
const codeSuffix = isList ? 'list' : 'form';
|
||||
const titleSuffix = isList ? '列表' : '表单';
|
||||
const icon = isList ? 'lucide:table-2' : 'lucide:file-input';
|
||||
|
||||
return {
|
||||
code: `buildflow.form.${form.code}.${codeSuffix}`,
|
||||
formDefinitionId: form.id,
|
||||
hideInMenu: false,
|
||||
icon,
|
||||
name: `BuildFlowForm${toPascalCase(form.code)}${routeSuffix}`,
|
||||
order: options.order ?? 0,
|
||||
parentId: options.parentId ?? null,
|
||||
path: `/buildflow/runtime/forms/${routeSegment}/${codeSuffix}`,
|
||||
path: `/buildflow/forms/${routeSegment}/${codeSuffix}`,
|
||||
renderType: 'BuildFlowForm',
|
||||
runtimeViewMode: options.runtimeViewMode,
|
||||
title: `${form.name}${titleSuffix}`,
|
||||
@ -86,7 +72,6 @@ function buildMenuFormPayload(data: MenuFormValues): MenuApi.CreateMenuParams {
|
||||
hideInMenu: !!data.hideInMenu,
|
||||
icon: normalizeOptional(data.icon),
|
||||
menuVisibleWithForbidden: false,
|
||||
name: data.name,
|
||||
noBasicLayout: false,
|
||||
order: data.order ?? 0,
|
||||
parentId: data.parentId || null,
|
||||
|
||||
@ -30,18 +30,17 @@ const [Form, formApi] = useVbenForm({
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
function setCodeDisabled(disabled: boolean) {
|
||||
// 编辑时禁用 name(路由唯一键)和 code(菜单编码唯一标识),避免改动破坏既有引用。
|
||||
formApi.updateSchema([
|
||||
{
|
||||
fieldName: 'name',
|
||||
function setDerivedFieldsDisabled(disabled: boolean) {
|
||||
// 编辑时禁用 code(菜单编码唯一标识),避免改动破坏既有引用。
|
||||
// 自定义表单菜单的 code/path/icon 由绑定的表单自动派生,也需禁用。
|
||||
// 路由 name 不在表单中暴露,由后端根据 Code 派生,无需在此处理。
|
||||
const fields = ['code', 'path', 'icon'];
|
||||
formApi.updateSchema(
|
||||
fields.map((fieldName) => ({
|
||||
fieldName,
|
||||
componentProps: { disabled },
|
||||
},
|
||||
{
|
||||
fieldName: 'code',
|
||||
componentProps: { disabled },
|
||||
},
|
||||
]);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
let buildFlowMenuSyncVersion = 0;
|
||||
@ -51,26 +50,39 @@ async function syncBuildFlowMenuDefaults(
|
||||
fieldsChanged: string[],
|
||||
) {
|
||||
const shouldSync = fieldsChanged.some((field) =>
|
||||
['formDefinitionId', 'renderType', 'runtimeViewMode'].includes(field),
|
||||
['formDefinitionId', 'renderType'].includes(field),
|
||||
);
|
||||
if (!shouldSync || editRow.value?.id) return;
|
||||
|
||||
const version = ++buildFlowMenuSyncVersion;
|
||||
if (values.renderType !== 'BuildFlowForm' || !values.formDefinitionId) return;
|
||||
// 切回普通路由时,恢复 code/path/icon 可编辑(自定义表单时它们是派生的,普通路由需手填)。
|
||||
if (values.renderType !== 'BuildFlowForm') {
|
||||
setDerivedFieldsDisabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const runtimeViewMode = values.runtimeViewMode === 'Form' ? 'Form' : 'List';
|
||||
// 自定义表单菜单一律为列表形态(运行形态不再暴露给用户)。
|
||||
if (!values.formDefinitionId) {
|
||||
// 选了自定义表单但还没绑定具体表单:先恢复可编辑,等绑定后再禁用。
|
||||
setDerivedFieldsDisabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const version = ++buildFlowMenuSyncVersion;
|
||||
const formDefinition = await getFormDefinitionByIdApi(
|
||||
values.formDefinitionId,
|
||||
);
|
||||
if (version !== buildFlowMenuSyncVersion) return;
|
||||
|
||||
// 选中表单后,标题/路径/图标/编码等全部由表单信息自动带出。
|
||||
await formApi.setValues(
|
||||
buildBuildFlowMenuFormValues(formDefinition, {
|
||||
order: values.order,
|
||||
parentId: values.parentId ?? null,
|
||||
runtimeViewMode,
|
||||
runtimeViewMode: 'List',
|
||||
}),
|
||||
);
|
||||
// code/path/icon 均由表单派生,禁止手动修改。
|
||||
setDerivedFieldsDisabled(true);
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
@ -100,26 +112,28 @@ const [Modal, modalApi] = useVbenModal({
|
||||
parentId: data.parentId,
|
||||
title: data.title,
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
icon: data.icon ?? '',
|
||||
order: data.order,
|
||||
formDefinitionId: data.formDefinitionId,
|
||||
renderType: data.renderType ?? 'Route',
|
||||
runtimeViewMode: data.runtimeViewMode ?? 'List',
|
||||
hideInMenu: data.hideInMenu,
|
||||
});
|
||||
setCodeDisabled(true);
|
||||
// 编辑模式:只禁用 code(唯一标识,不可改);path/icon 保留可编辑。
|
||||
formApi.updateSchema([
|
||||
{ fieldName: 'code', componentProps: { disabled: true } },
|
||||
]);
|
||||
} else {
|
||||
// 新增(含新增子菜单:预设 parentId)
|
||||
editRow.value = undefined;
|
||||
presetParentId.value = data && 'parentId' in data ? data.parentId : null;
|
||||
formApi.resetForm();
|
||||
formApi.setValues({ renderType: 'Route', runtimeViewMode: 'List' });
|
||||
formApi.setValues({ renderType: 'Route' });
|
||||
if (presetParentId.value) {
|
||||
formApi.setValues({ parentId: presetParentId.value });
|
||||
}
|
||||
setCodeDisabled(false);
|
||||
// 新增模式:code/path/icon 默认均可编辑,待选中自定义表单后才禁用。
|
||||
setDerivedFieldsDisabled(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user