20260922224136

This commit is contained in:
oneao committed 2026-09-22 22:41:36 +08:00
1 parent c8dad8d4a6
commit 0abe3706c8
113 files changed
+5057 -2771

No files matched your search

@@ -4,20 +4,21 @@
* 数据加载与保存由父级 UserPowerDrawer 负责,面板自身只做这几件事,测试只锁这几件:
* 1. 打开时的初始状态:只展开「全部分类」虚拟根,业务目录收起、不选中任何菜单;
* 2. 菜单选中语义:重复点击已选中的菜单不取消选中,只有点「全部分类」才清空;
* 3. 右侧结构:页头(菜单名 + 入口勾选框 + 右端 x/y 进度)+ 一个模块一块的授权块列表。
* 3. 右侧结构:左右两栏用 Splitter 分栏(左树栏可拖拽调宽),
* 右栏是页头(菜单名 + 入口勾选框 + 右端 x/y 进度)+ 一个模块一块的授权块列表。
* 块头是模块(模块级勾选框 + 「已选 x/y」):勾选框表示"这个数据给没给"——有任意
* 一项权限即勾选、一项都没有才未勾选(不做半选),点一下即给模块使用 / 整块收回;
* 块内是它的动作,层级靠块头的上下关系;
* 没配权限点的模块不渲染;目录不渲染入口勾选框,说明放内容区中间而不是页头;
* 本页没有可配置模块时内容区中间给一句说明(目录与外链 / 自定义页文案不同);
* 4. 面板顶部工具栏的「全授权 / 全禁用」作用于该账号的全部功能权限(s_power 全量),
* 与左侧选中哪个菜单无关;
* 5. 勾选结果经 v-model 回传,三级是「逐级给、连带收」:
* 4. 勾选结果经 v-model 回传,三级是「逐级给、连带收」:
* 勾下级自动补上级(操作 → 模块使用 → 入口);勾上级不替下级勾选;
* 取消上级连带收回下级(关入口收回本页全部,取消模块收回该模块全部权限点)。
*
* 勾选框是 ui/Checkbox(label 包原生 input),测试直接驱动 input 的 change ——
* jsdom 不保证 label 点击会转发给内部控件,走 change 才是组件真实监听的入口。
*
* 账号级的批量操作(全授权 / 全禁用 / 清空权限)不在本面板,由 user-power-drawer.spec.js 覆盖。
*/
import { describe, it, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
@@ -72,21 +73,6 @@ const POWER_BY_MODULE = {
v_b_user: [{ b_id: 'module.v_b_user', b_name: '模块使用', b_action: 'access' }],
}
/** s_power 全量启用编码(含 menu.*),父级加载后传给面板,供「全授权」使用 */
const ALL_POWER_IDS = [
'menu.partner',
'menu.portal',
'menu.user',
'module.v_b_othercompany',
'action.v_b_othercompany.create',
'action.v_b_othercompany.update',
'action.v_b_othercompany.audit',
'module.v_b_contact',
'action.v_b_contact.create',
'action.v_b_contact.delete',
'module.v_b_user',
]
/** 合作伙伴模块(v_b_othercompany)的权限点全量:模块级勾选框勾上时就该是这些 */
const OTHERCOMPANY_ALL = [
'module.v_b_othercompany',
@@ -109,7 +95,6 @@ function mountPanel(props = {}) {
menuModules: MENU_MODULES,
moduleById: MODULE_BY_ID,
powerByModule: POWER_BY_MODULE,
allPowerIds: ALL_POWER_IDS,
...props,
},
})
@@ -183,11 +168,6 @@ function setCheck(checkbox, checked = true) {
return checkbox.find('.checkbox-input').setValue(checked)
}
/** 面板顶部工具栏按钮:0 = 全授权,1 = 全禁用 */
function toolbarButtons(wrapper) {
return wrapper.findAll('.power-toolbar__actions button')
}
/** 回传的勾选结果(取最后一次 emit) */
function lastEmitted(wrapper) {
const events = wrapper.emitted('update:modelValue')
@@ -265,6 +245,19 @@ describe('功能权限面板:菜单选中语义', () => {
})
describe('功能权限面板:右侧结构', () => {
it('左右两栏用 Splitter 分栏:左栏菜单树可拖拽调宽,右栏吃满剩余宽度', async () => {
const wrapper = await mountPanel()
await nextTick()
expect(wrapper.find('.power-panel__body.fms-splitter').exists()).toBe(true)
expect(wrapper.find('.power-panel__body .fms-splitter-bar').exists()).toBe(true)
const panels = wrapper.find('.power-panel__body').findAll('.fms-splitter-panel')
expect(panels).toHaveLength(2)
expect(panels[0].find('.fms-tree-panel').exists()).toBe(true)
expect(panels[1].find('.power-panel__main').exists()).toBe(true)
})
it('页头:菜单名做标题 + 入口勾选框 + 右端的授权进度', async () => {
const wrapper = await mountWithMenu('partner')
@@ -426,45 +419,6 @@ describe('功能权限面板:右侧结构', () => {
})
})
describe('功能权限面板:全授权 / 全禁用', () => {
it('工具栏在面板顶部:说明 + 两个按钮', async () => {
const wrapper = mountPanel()
await nextTick()
expect(wrapper.find('.power-toolbar__label').text()).toBe('该账号的全部功能权限')
expect(toolbarButtons(wrapper).map((el) => el.text())).toEqual(['全授权', '全禁用'])
})
it('全授权:一次勾满 s_power 的全部权限点', async () => {
const wrapper = await mountWithMenu('partner')
await toolbarButtons(wrapper)[0].trigger('click')
await nextTick()
expect(lastEmitted(wrapper)).toEqual(ALL_POWER_IDS)
})
it('全禁用:清空该账号的全部功能权限', async () => {
const wrapper = await mountWithMenu('partner', { modelValue: ALL_POWER_IDS })
await toolbarButtons(wrapper)[1].trigger('click')
await nextTick()
expect(lastEmitted(wrapper)).toEqual([])
})
it('工具栏不依赖选中菜单:未选中菜单时同样可用', async () => {
const wrapper = mountPanel()
await nextTick()
expect(wrapper.find('.power-panel__main').text()).toContain('请选择左侧菜单')
await toolbarButtons(wrapper)[0].trigger('click')
await nextTick()
expect(lastEmitted(wrapper)).toEqual(ALL_POWER_IDS)
})
})
describe('功能权限面板:三层联动', () => {
it('勾模块级勾选框:只给该模块的模块使用权限,并补上菜单入口', async () => {
const wrapper = await mountWithMenu('partner')