From 8bbe3c654be6fc8b53d2042188962e41fcbc05ca Mon Sep 17 00:00:00 2001 From: chenxing Date: Wed, 21 Oct 2026 10:31:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(dept):=20=E9=83=A8=E9=97=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=94=B9=E5=8D=A1=E7=89=87=E8=A7=86=E5=9B=BE=EF=BC=8C?= =?UTF-8?q?=E6=A0=91=E5=8F=AA=E5=88=B0=E5=AD=A6=E6=A0=A1=E5=B1=82+?= =?UTF-8?q?=E5=90=AB=E5=AD=90=E7=BB=84=E5=BC=80=E5=85=B3=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=8F=AA=E6=98=BE=E7=A4=BA=E6=95=99=E5=B8=88=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 右侧由表格改为部门卡片网格;有下级部门的部门渲染为跨整行的分组卡(办公室 → 校办/宣传/财务/党建),叶子部门为单卡 - 左侧沿用 zTree,剪枝到「学校」层;学校行尾挂「包含子组」开关(MyTree 新增 attachNodeControl,不污染 node.name、不触发节点选中) - 按访问级别分视角:超管/局端显示树,学校管理员无树直接列本校部门(多校走顶部下拉) - 修复「只显示教师组一行」:右侧数据改为从全量组树按 id 回查 children,不再使用 treeLink 中已清空 children 的节点 --- src/components/GroupTreeSearchPanel.vue | 6 + src/components/MyTree.vue | 27 ++ src/views/dept-manage/DeptCard.vue | 195 +++++++++++ src/views/dept-manage/index.vue | 434 ++++++++++++++++++------ 4 files changed, 566 insertions(+), 96 deletions(-) create mode 100644 src/views/dept-manage/DeptCard.vue diff --git a/src/components/GroupTreeSearchPanel.vue b/src/components/GroupTreeSearchPanel.vue index b5e6458..0a2b755 100644 --- a/src/components/GroupTreeSearchPanel.vue +++ b/src/components/GroupTreeSearchPanel.vue @@ -215,6 +215,11 @@ function getNodeById(id: number) { return refMyTree.value?.getNodeById(id); } +/** 在指定节点后追加自定义控件(见 MyTree.attachNodeControl) */ +function attachNodeControl(id: number, className: string, html: string) { + return refMyTree.value?.attachNodeControl(id, className, html); +} + function getCheckedNodes() { return refMyTree.value?.getCheckedNodes() || []; } @@ -257,6 +262,7 @@ defineExpose({ removeNode, renameNode, getNodeById, + attachNodeControl, getCheckedNodes, checkNodeById, addNote, diff --git a/src/components/MyTree.vue b/src/components/MyTree.vue index cddaad7..4d37d97 100644 --- a/src/components/MyTree.vue +++ b/src/components/MyTree.vue @@ -222,6 +222,33 @@ export default { if (on) a.addClass('ztree-search-highlight'); else a.removeClass('ztree-search-highlight'); }, + /** + * 给指定节点追加自定义 DOM 控件(渲染在节点文字之后)。 + * 用途:在 zTree 节点行尾挂业务控件(如开关),不污染 node.name、 + * 也不触发节点选中(zTree 的 onClick 绑定在节点 a 标签上,控件在其外部)。 + * 树数据重载(initTree)后需重新挂载。 + * @param {*} id 节点 id + * @param {string} className 控件容器类名(同时作为去重标识) + * @param {string} html 控件 HTML + * @returns {HTMLElement|null} 控件元素,失败返回 null + */ + attachNodeControl(id, className, html) { + const _domId = this.domId; + const treeObj = $.fn.zTree.getZTreeObj(_domId); + if (!treeObj || !id || !className) return null; + const node = treeObj.getNodesByFilter(n => n.id == id, true); + if (!node) return null; + const li = document.getElementById(node.tId); + if (!li) return null; + li.querySelectorAll("." + className).forEach(el => el.remove()); + const box = document.createElement("span"); + box.className = className; + box.innerHTML = html; + const a = document.getElementById(node.tId + "_a"); + if (a && a.parentNode) a.parentNode.insertBefore(box, a.nextSibling); + else li.appendChild(box); + return box; + }, clearHighlight() { (this.highlightNodes || []).forEach(n => this.setHighlight(n, false)); this.highlightNodes = []; diff --git a/src/views/dept-manage/DeptCard.vue b/src/views/dept-manage/DeptCard.vue new file mode 100644 index 0000000..2d4c9fa --- /dev/null +++ b/src/views/dept-manage/DeptCard.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/views/dept-manage/index.vue b/src/views/dept-manage/index.vue index a43dcd3..46b844c 100644 --- a/src/views/dept-manage/index.vue +++ b/src/views/dept-manage/index.vue @@ -1,147 +1,284 @@ @@ -155,6 +292,15 @@ function findNodeById(list: any[], id: number): any { flex: 0 0 320px; min-height: 100%; overflow: auto; + + .tree-hint { + font-size: 11.5px; + line-height: 1.6; + color: var(--text-3, #98a1b2); + padding: 0 2px 8px; + border-bottom: 1px dashed var(--line-2, #f0f3f9); + margin-bottom: 6px; + } } .dept-manage-main { @@ -166,4 +312,100 @@ function findNodeById(list: any[], id: number): any { } } } + +.dept-loading { + flex: 1; + text-align: center; + padding-top: 80px; + color: var(--text-3, #98a1b2); +} + +.panel-title { + display: flex; + align-items: center; + gap: 4px; + width: 100%; +} + +.school-select { + width: 180px; +} + +/* 面板头的「包含子组」开关 */ +.inc-ctrl { + display: inline-flex; + align-items: center; + gap: 6px; + cursor: pointer; + user-select: none; + font-size: 12.5px; + color: var(--text-2, #5a6478); +} + +/* 「包含子组」开关:面板头与 zTree 树上共用同一套样式。 + 树上的控件是运行时创建的 DOM,没有 scoped 标记,只能用 :deep 命中 */ +:deep(.sw-mini) { + position: relative; + display: inline-block; + width: 28px; + height: 16px; + border-radius: 8px; + background: #d3d9e6; + transition: background .15s; + + &::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 12px; + height: 12px; + border-radius: 50%; + background: #fff; + box-shadow: 0 1px 2px rgba(30, 38, 53, .2); + transition: left .15s; + } + + &.is-on { + background: var(--brand, #577afb); + + &::after { + left: 14px; + } + } +} + +/* 部门卡片网格 */ +.dept-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(238px, 1fr)); + gap: 12px; + align-items: start; +} + +/* 树上(zTree)学校节点行尾的开关控件: + 校名长时会把开关挤出可视区,故绝对定位固定在行尾,同时给节点文字留出余量 */ +:deep(.ztree li) { + position: relative; +} + +:deep(.ztree li a) { + display: inline-block; + max-width: calc(100% - 46px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; +} + +:deep(.dept-inc-ctrl) { + position: absolute; + right: 6px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + cursor: pointer; + z-index: 1; +}