From 628a914e19976221504b465a9fb2a3d1baa3cd64 Mon Sep 17 00:00:00 2001 From: chenxing Date: Wed, 16 Sep 2026 19:14:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(authz):=20=E5=BA=94=E7=94=A8=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=8D=A1=E7=89=87=E6=96=B0=E5=A2=9E=E3=80=8C=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E5=B1=95=E5=BC=80=E3=80=8D=E5=BC=80=E5=85=B3=20+=20?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E5=88=97=E5=AE=BD=E8=AE=A9?= =?UTF-8?q?=E4=BD=8D=E9=83=A8=E9=97=A8=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MyTree 新增受控 prop expandAll(默认 false):在 zTree 初始化之后套用并监听变化, 数据重载(切组/搜索过滤)后自动重新展开,规避调用方与 initTree 抢 nextTick 时序 - 组授权 / 角色授权的应用类型卡片头部加「全部展开 + small Switch」,默认关闭, 控制该类型下全部应用与模块的展开收起;每个卡片独立,状态按 category id 保存 - 开关外层 @click.stop:卡片头部本身绑定 toggleCollapse,不阻断冒泡会连带折叠卡片 - 角色授权「用户管理」表格:账号/姓名改定宽(110/90)、操作 80,部门 minWidth 吃剩余 宽度,把空间留给组路径列 --- src/components/MyTree.vue | 16 ++++++++++++++ src/views/GroupAuth.vue | 23 ++++++++++++++++++++- src/views/role-authz/AppModuleCheckTree.vue | 23 ++++++++++++++++++++- src/views/role-authz/RoleUserManage.vue | 7 ++++--- 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/components/MyTree.vue b/src/components/MyTree.vue index 9ec4355..cddaad7 100644 --- a/src/components/MyTree.vue +++ b/src/components/MyTree.vue @@ -43,6 +43,12 @@ export default { type: Boolean, default: true, }, + /** 受控的「全部展开」:true 时展开全部节点。由本组件在树初始化之后套用, + * 避免调用方在数据重载(切组/搜索过滤)时与 initTree 抢时序而丢失展开状态 */ + expandAll: { + type: Boolean, + default: false, + }, isCheck: { typeof: Boolean, default: false, @@ -64,6 +70,12 @@ export default { }); } }); + // 受控「全部展开」:开关切换即时生效(树未初始化时忽略,initTree 会按当前值套用) + this.$watch("expandAll", val => { + if (this.treeObj) { + this.treeObj.expandAll(!!val); + } + }); }, mounted() { this.domId = this.uuid(); @@ -128,6 +140,10 @@ export default { }; _this.treeObj = $.fn.zTree.init($("#" + _domId), _this.treeSetting, _data); + // 数据重载后重新套用「全部展开」(节点 open 会被新数据重置) + if (_this.expandAll) { + _this.treeObj.expandAll(true); + } if (_this.autoClick) { if (Array.isArray(this.treeData)) { let id = this.treeData[0].id; diff --git a/src/views/GroupAuth.vue b/src/views/GroupAuth.vue index 3cedc41..f099010 100644 --- a/src/views/GroupAuth.vue +++ b/src/views/GroupAuth.vue @@ -40,10 +40,15 @@ {{ item.cat.name }} {{ item.treeData.length }}个应用 子组继承 + + + 全部展开 + +
+ :expandAll="!!expandAllMap[String(item.cat.id)]" @on-tree-check="onTreeCheck" />
@@ -134,6 +139,9 @@ const searchKeyword = ref(""); const appliedKeyword = ref(""); const collapsedMap = reactive>({}); +/** 各应用类型卡片的「全部展开」开关(默认关闭):状态随卡片独立保存,数据重载后仍生效 */ +const expandAllMap = reactive>({}); + const fallbackModeItems: DictItem[] = [ { k: 0, v: "追加" }, { k: 1, v: "覆盖" }, @@ -674,6 +682,19 @@ onMounted(async () => { margin-right: 6px; } +.expand-all { + display: flex; + align-items: center; + flex-shrink: 0; + gap: 4px; +} + +.expand-all-label { + font-size: 12px; + color: #999; + white-space: nowrap; +} + .cat-card-body { padding: 6px 8px; /* 子组继承复选框的绝对定位锚点:保证所有层级节点的复选框右对齐成一列 */ diff --git a/src/views/role-authz/AppModuleCheckTree.vue b/src/views/role-authz/AppModuleCheckTree.vue index 885be3b..63e2528 100644 --- a/src/views/role-authz/AppModuleCheckTree.vue +++ b/src/views/role-authz/AppModuleCheckTree.vue @@ -26,10 +26,15 @@ :type="collapsedMap[String(item.cat.id)] ? 'ios-arrow-forward' : 'ios-arrow-down'" /> {{ item.cat.name }} {{ item.treeData.length }}个应用 + + + 全部展开 + +
+ :expandAll="!!expandAllMap[String(item.cat.id)]" @on-tree-check="onTreeCheck" />
@@ -82,6 +87,9 @@ const searchKeyword = ref(""); const appliedKeyword = ref(""); const collapsedMap = reactive>({}); +/** 各应用类型卡片的「全部展开」开关(默认关闭):状态随卡片独立保存,数据重载后仍生效 */ +const expandAllMap = reactive>({}); + const treeSetting = { view: { dblClickExpand: false, showIcon: false }, check: { @@ -402,6 +410,19 @@ watch( padding: 6px 8px; } +.expand-all { + display: flex; + align-items: center; + flex-shrink: 0; + gap: 4px; +} + +.expand-all-label { + font-size: 12px; + color: #999; + white-space: nowrap; +} + .empty-tip { color: #999; padding: 48px 0; diff --git a/src/views/role-authz/RoleUserManage.vue b/src/views/role-authz/RoleUserManage.vue index 5cea475..bfd0a98 100644 --- a/src/views/role-authz/RoleUserManage.vue +++ b/src/views/role-authz/RoleUserManage.vue @@ -39,12 +39,13 @@ const existingBindings = computed(() => ); // 部门列为查询时按 groupId 拼接的组路径(非超管按数据范围裁剪,学校管理员只看到本校路径) +// 账号/姓名用定宽、部门用 minWidth 吃剩余宽度,把空间留给组路径 const columns = [ - { title: "账号", key: "account", minWidth: 140 }, - { title: "姓名", key: "realName", minWidth: 110 }, + { title: "账号", key: "account", width: 110 }, + { title: "姓名", key: "realName", width: 90 }, { title: "部门", key: "groupPath", minWidth: 200 }, { - title: "操作", key: "action", width: 90, align: "center", + title: "操作", key: "action", width: 80, align: "center", render: (h: any, { row }: any) => $utils.operatingTextHandle(h, "删除", "text-error", () => removeUser(row)), }, ];