align(菜单管理): 同级拖拽排序接线(手柄列+类型/父级分组+reorder带menu_type)、列表拉全量类型、类型文案后台/软件、弹窗补菜单类型下拉、父菜单候选按类型过滤(对齐 admin.js loadColumns/bindColumnDragSort/persistColumnOrder)

This commit is contained in:
2026-09-05 23:04:05 +08:00
parent 1692fa4a69
commit 32ef9a2ebb
6 changed files with 271 additions and 25 deletions
@@ -18,11 +18,17 @@ export interface MenuManageNode {
children?: MenuManageNode[]
}
/** 菜单类型展示文案(对齐 admin 菜单类型语义)。 */
/** 菜单类型展示文案(对齐 admin.js:6824admin → 后台,其余一律 → 软件)。 */
export function menuTypeLabel(type: string | null | undefined): string {
return (type || '').toLowerCase() === 'app' ? '应用菜单' : '后台菜单'
return (type || '').toLowerCase() === 'admin' ? '后台' : '软件'
}
/** 菜单类型下拉选项(对齐 admin.html:5931-5934 后台/软件)。 */
export const MENU_TYPE_OPTIONS: Array<{ value: 'admin' | 'app'; label: string }> = [
{ value: 'admin', label: '后台' },
{ value: 'app', label: '软件' },
]
export interface MenuManageForm {
name: string
columnKey: string
@@ -198,6 +204,55 @@ export function siblingOrderAfterMove(group: number[], targetId: number, dir: nu
return next
}
/** 拖拽分组键:同"菜单类型 + 父级"才算同级(对齐 admin.js columnSiblingGroupKey)。 */
export function menuSiblingGroupKey(node: MenuManageNode): string {
return `${node.menuType || 'app'}|${node.parentId ?? ''}`
}
/** 拖放排序:把 draggedId 插到 targetId 前/后,返回新的同级 id 顺序;任一 id 缺失或同 id 则原序。 */
export function siblingOrderAfterDrop(
group: number[],
draggedId: number,
targetId: number,
placeAfter: boolean,
): number[] {
if (!Array.isArray(group) || group.length === 0) return []
if (draggedId === targetId) return [...group]
const next = [...group]
const from = next.indexOf(draggedId)
if (from < 0) return next
const target = next.indexOf(targetId)
if (target < 0) return next
next.splice(from, 1)
const insertAt = next.indexOf(targetId) + (placeAfter ? 1 : 0)
next.splice(insertAt, 0, draggedId)
return next
}
/** 父菜单候选:仅同菜单类型的节点(对齐 admin 的类型联动父级下拉)。 */
export function menuParentCandidates(nodes: MenuManageNode[], menuType: string): MenuManageNode[] {
const type = (menuType || '').trim()
return flattenMenuNodes(nodes).filter((node) => (node.menuType || '') === type)
}
/** 同级重排请求体(对齐 admin.js persistColumnOrder{ menu_type, ordered_ids })。 */
export function toMenuReorderRequest(
menuType: string,
orderedIds: number[],
): { menu_type: string; ordered_ids: number[] } {
return { menu_type: text(menuType) || 'app', ordered_ids: dedupeOrderIds(orderedIds) }
}
function dedupeOrderIds(ids: unknown[]): number[] {
if (!Array.isArray(ids)) return []
const set = new Set<number>()
for (const v of ids) {
const n = numberOrNull(v)
if (n !== null && n > 0) set.add(n)
}
return [...set]
}
/** 新建/编辑表单空值工厂:默认菜单类型为 admin、排序 0、根父节点。 */
export function emptyMenuManageForm(): MenuManageForm {
return { name: '', columnKey: '', parentId: null, menuType: ADMIN_MENU_TYPE, routePath: '', sortOrder: 0 }