|
@@ -0,0 +1,145 @@
|
|
|
|
|
+-- 菜单合并:「代付」目录收纳六个顶级菜单(2026-10-05)
|
|
|
|
|
+--
|
|
|
|
|
+-- 【怎么跑】Navicat 整段执行(纯 SQL,无 psql 元命令)。三段:
|
|
|
|
|
+-- 0 预检(只读)→ 1 合并(写,单条语句包在 BEGIN/COMMIT 里)→ 2 复核(只读)。
|
|
|
|
|
+-- ★ 先看第 0 段;再看第 1 段末尾那行计数(期望 1 | 6 | 2);再对第 2 段。
|
|
|
|
|
+--
|
|
|
|
|
+-- 【合并对象】六个当前都是**顶级**(parent_id IS NULL)。以下为 2026-10-05 盘点实测值:
|
|
|
|
|
+-- 190 企业管理 type=2 order=1 /payment/enterprise module_payment/enterprise/index
|
|
|
|
|
+-- 193 员工管理 type=2 order=1 /payment/employee module_payment/employee/index
|
|
|
|
|
+-- 200 资金专户转账页 type=2 order=1 /account module_payment/account/index
|
|
|
|
|
+-- 222 操作指南 type=4 order=1 外链 https://doc.qcsj88888.com/quick-guide.html
|
|
|
|
|
+-- 249 批量付款 type=2 order=3 /payment/batch module_payment/batch/index
|
|
|
|
|
+-- 194 员工授信额度 type=2 order=3 /payment/institution module_payment/institution/index
|
|
|
|
|
+--
|
|
|
|
|
+-- 【为什么必须同时把「代付」授给两个角色】UserService#buildMenuTree 对「父节点不在授权
|
|
|
|
|
+-- 集合里」的子节点**静默丢弃**(不进树、不报错、日志不打):
|
|
|
|
|
+-- · 角色 2(租户角色)持有这六个的**全部** ⇒ 只改 parent_id 不补授 ⇒ 租户侧六个页面
|
|
|
|
|
+-- **一起消失**,且页面上没有任何异常可查。
|
|
|
|
|
+-- · 角色 1(管理员角色)持有一项(249 批量付款)⇒ 不补授 ⇒ 非超管用户看不到批量付款。
|
|
|
|
|
+--
|
|
|
|
|
+-- 【六个子菜单的 route_path 一个都不改,也**不需要**改】它们本来就是绝对路径;前端
|
|
|
|
|
+-- permission.store.ts 的 generator() 直接拿 route_path 当路由 path(**不拼父路径**),
|
|
|
|
|
+-- 侧栏 MenuItem.resolvePath 对绝对路径同样原样取胜 ⇒ 挂到新父目录下路径不变。
|
|
|
|
|
+-- 同构的现成例子就在线上跑着:银联收款(268, type=1, component_path=NULL) 下面挂着
|
|
|
|
|
+-- 支付订单(269, /payment/collection/order) —— 「无组件的目录 + 子页」这套形状已验证。
|
|
|
|
|
+--
|
|
|
|
|
+-- 【没测量过的两项,本轮不受影响】六个的 route_name / hidden 未在盘点里取到。
|
|
|
|
|
+-- · route_name:侧栏叶子优先按 name 跳转、无 name 时按 path 跳转,两条分支都成立;本轮不改它。
|
|
|
|
|
+-- · hidden:第 0 段顺带量出来(六个今天都是可见的顶级项,预期全 false)。
|
|
|
|
|
+--
|
|
|
|
|
+-- 【幂等】三处守卫,重跑第二遍全部 no-op、不报错、不产生第二行:
|
|
|
|
|
+-- ① 建目录:库里已有 name/title 为「代付」的行(**不分类型、不分状态**),或 route_name
|
|
|
|
|
+-- ('PaymentPayout')、route_path ('/payment/payout') 已被占用 ⇒ 不建。
|
|
|
|
|
+-- ② 归位:UPDATE ... FROM dir —— dir 为空则一行都不动;另加 parent_id IS NULL 条件,
|
|
|
|
|
+-- 不覆盖别人已经改过的行。
|
|
|
|
|
+-- ③ 授权:授的是本语句刚建出来的**新 id** ⇒ 重跑时插不进重复行(表上另有 PK 兜底)。
|
|
|
|
|
+-- ★ 失败是**可见**的:第 1 段计数读到 0|0|0 就是守卫命中(多半是库里已有「代付」行),
|
|
|
|
|
+-- 第 2 段会把现状打出来 —— 别猜,把 0 段和 2 段的结果发回来。
|
|
|
|
|
+--
|
|
|
|
|
+-- 【顺序】「代付」自身 order=1:六个搬走后顶级 order=1 只剩 仪表盘(1) ⇒ 代付落回六个原来
|
|
|
|
|
+-- 的位置(仪表盘之后、系统管理(order=2)之前)。与仪表盘并列同值,谁在前由数据库返回顺序
|
|
|
|
|
+-- 决定(顶栏现状本来就有多个并列 order)。要 100% 确定,见第 2 段后面的可选一句。
|
|
|
|
|
+-- 六个子菜单在代付内的顺序 = 你原话里的列举顺序(1..6),改 moved 里的 VALUES 即可调。
|
|
|
|
|
+
|
|
|
|
|
+-- ========== 0 预检(只读) ==========
|
|
|
|
|
+-- 0a) 名字 / route_name / route_path 占用检查 —— 期望:**无数据**
|
|
|
|
|
+SELECT '同名代付' AS what, id, type, status, route_name, route_path
|
|
|
|
|
+ FROM sys_menu WHERE name = '代付' OR title = '代付'
|
|
|
|
|
+UNION ALL
|
|
|
|
|
+SELECT 'route_name 被占', id, type, status, route_name, route_path
|
|
|
|
|
+ FROM sys_menu WHERE route_name = 'PaymentPayout'
|
|
|
|
|
+UNION ALL
|
|
|
|
|
+SELECT 'route_path 被占', id, type, status, route_name, route_path
|
|
|
|
|
+ FROM sys_menu WHERE route_path = '/payment/payout';
|
|
|
|
|
+
|
|
|
|
|
+-- 0b) 六行现状 —— 期望:6 行,parent_id 全空、status 全 '0'
|
|
|
|
|
+SELECT id, parent_id, type, "order", title, route_path, hidden, status
|
|
|
|
|
+ FROM sys_menu WHERE id IN (190, 193, 200, 222, 249, 194)
|
|
|
|
|
+ ORDER BY id;
|
|
|
|
|
+
|
|
|
|
|
+-- ========== 1 合并(写) ==========
|
|
|
|
|
+BEGIN;
|
|
|
|
|
+
|
|
|
|
|
+WITH dir AS (
|
|
|
|
|
+ INSERT INTO sys_menu (name, type, "order", permission, icon, route_name, route_path, component_path,
|
|
|
|
|
+ redirect, hidden, keep_alive, always_show, affix, title, params,
|
|
|
|
|
+ uuid, parent_id, status, created_time, updated_time)
|
|
|
|
|
+ SELECT '代付', 1, 1, NULL, NULL, 'PaymentPayout', '/payment/payout', NULL,
|
|
|
|
|
+ NULL, false, true, false, false, '代付', NULL,
|
|
|
|
|
+ gen_random_uuid(), NULL, '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
|
|
|
|
+ WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE name = '代付' OR title = '代付')
|
|
|
|
|
+ AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE route_name = 'PaymentPayout')
|
|
|
|
|
+ AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE route_path = '/payment/payout')
|
|
|
|
|
+ RETURNING id
|
|
|
|
|
+),
|
|
|
|
|
+moved AS (
|
|
|
|
|
+ UPDATE sys_menu m
|
|
|
|
|
+ SET parent_id = dir.id,
|
|
|
|
|
+ "order" = v.ord
|
|
|
|
|
+ FROM dir,
|
|
|
|
|
+ (VALUES (190, 1), (193, 2), (200, 3), (222, 4), (249, 5), (194, 6)) AS v(mid, ord)
|
|
|
|
|
+ WHERE m.id = v.mid
|
|
|
|
|
+ AND m.parent_id IS NULL
|
|
|
|
|
+ RETURNING m.id
|
|
|
|
|
+),
|
|
|
|
|
+granted_rows AS (
|
|
|
|
|
+ INSERT INTO sys_role_menus (role_id, menu_id)
|
|
|
|
|
+ SELECT r.role_id, dir.id
|
|
|
|
|
+ FROM dir, (VALUES (1), (2)) AS r(role_id)
|
|
|
|
|
+ RETURNING role_id
|
|
|
|
|
+)
|
|
|
|
|
+SELECT (SELECT count(*) FROM dir) AS dir_created,
|
|
|
|
|
+ (SELECT count(*) FROM moved) AS menus_moved,
|
|
|
|
|
+ (SELECT count(*) FROM granted_rows) AS grants_inserted;
|
|
|
|
|
+-- 期望:1 | 6 | 2
|
|
|
|
|
+
|
|
|
|
|
+COMMIT;
|
|
|
|
|
+
|
|
|
|
|
+-- ========== 2 复核(只读) ==========
|
|
|
|
|
+-- 2a) 目录 + 六个成员 —— 期望:7 行,代付在首(parent_title 空),其余 parent_title = 代付
|
|
|
|
|
+SELECT m.id, m.parent_id, m.type, m."order", m.title, m.route_path, m.hidden, m.status,
|
|
|
|
|
+ p.title AS parent_title
|
|
|
|
|
+ FROM sys_menu m
|
|
|
|
|
+ LEFT JOIN sys_menu p ON p.id = m.parent_id
|
|
|
|
|
+ WHERE m.name = '代付'
|
|
|
|
|
+ OR m.parent_id IN (SELECT id FROM sys_menu WHERE name = '代付' AND type = 1)
|
|
|
|
|
+ ORDER BY (m.parent_id IS NULL) DESC, m."order", m.id;
|
|
|
|
|
+
|
|
|
|
|
+-- 2b) 授权 —— 期望:代付 granted_roles='1,2';190/193/200/222/194 = '2';249 = '1,2'
|
|
|
|
|
+SELECT m.id, m.title, m.type,
|
|
|
|
|
+ (SELECT string_agg(rm.role_id::text, ',' ORDER BY rm.role_id)
|
|
|
|
|
+ FROM sys_role_menus rm WHERE rm.menu_id = m.id) AS granted_roles
|
|
|
|
|
+ FROM sys_menu m
|
|
|
|
|
+ WHERE m.name = '代付'
|
|
|
|
|
+ OR m.parent_id IN (SELECT id FROM sys_menu WHERE name = '代付' AND type = 1)
|
|
|
|
|
+ ORDER BY m.id;
|
|
|
|
|
+
|
|
|
|
|
+-- 2c) 静默丢弃体检(与 sys-menu-inventory-readonly.sql 第 ③ 段同式)—— 期望:**无数据**
|
|
|
|
|
+SELECT rm.role_id, r.name AS role_name, m.id AS menu_id, m.title, m.parent_id,
|
|
|
|
|
+ p.title AS parent_title
|
|
|
|
|
+ FROM sys_role_menus rm
|
|
|
|
|
+ JOIN sys_menu m ON m.id = rm.menu_id AND m.status = '0'
|
|
|
|
|
+ JOIN sys_role r ON r.id = rm.role_id
|
|
|
|
|
+ JOIN sys_menu p ON p.id = m.parent_id
|
|
|
|
|
+ WHERE NOT EXISTS (SELECT 1 FROM sys_role_menus x
|
|
|
|
|
+ WHERE x.role_id = rm.role_id AND x.menu_id = m.parent_id)
|
|
|
|
|
+ ORDER BY rm.role_id, m.id;
|
|
|
|
|
+
|
|
|
|
|
+-- ── 可选:让「代付」与「仪表盘」的前后 100% 确定(现状是并列 order=1)──────────
|
|
|
|
|
+-- UPDATE sys_menu SET "order" = 0 WHERE id = 1 AND type = 1; -- 仪表盘提前到唯一 0
|
|
|
|
|
+
|
|
|
|
|
+-- ── ★ 执行后必须**重新登录**才生效:菜单树与权限集合在登录时取,不重登看不到变化。
|
|
|
|
|
+
|
|
|
|
|
+-- ========== 回滚(默认整段注释;要回滚时逐句去掉注释执行) ==========
|
|
|
|
|
+-- 前置:确认「代付」是本次新建的(即第 0 段 0a 当时读到「无数据」)——否则最后一句会删掉
|
|
|
|
|
+-- 你原有的目录行。下面两行的 order 值还原为盘点实测的旧值。
|
|
|
|
|
+-- BEGIN;
|
|
|
|
|
+-- UPDATE sys_menu SET parent_id = NULL, "order" = 1 WHERE id IN (190, 193, 200, 222);
|
|
|
|
|
+-- UPDATE sys_menu SET parent_id = NULL, "order" = 3 WHERE id IN (249, 194);
|
|
|
|
|
+-- DELETE FROM sys_role_menus
|
|
|
|
|
+-- WHERE role_id IN (1, 2)
|
|
|
|
|
+-- AND menu_id IN (SELECT id FROM sys_menu WHERE name = '代付' AND type = 1);
|
|
|
|
|
+-- DELETE FROM sys_menu
|
|
|
|
|
+-- WHERE name = '代付' AND type = 1 AND parent_id IS NULL;
|
|
|
|
|
+-- COMMIT;
|