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 @@ + + + + + {{ dept.name }} + + + + {{ subText }} + + + + + + + + + + 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 @@ - - - - + 加载中… - - - - - 部门管理 - {{ deptContainerName }} - - 请在左侧选择组织节点 - - - 当前节点【{{ selectedNode.name }}】不是部门容器(教师组),部门 = 教师组的直接子节点。 - 请选择学校下的「教师组」节点。 + + + + 树只到「学校」层;行尾开关控制该校是否含下级部门。 + + + + + + + + + 部门管理 + {{ currentSchoolName }} + + {{ s.name }} + + + 包含子组 + + + + + + + {{ showTree ? '请在左侧选择学校节点' : '当前账号没有可管理的学校' }} - - - - 该教师组暂无子节点:按口径「空容器自身算部门」,容器本身即可作为部门使用。 + + 该校教师组下暂无部门。部门 = 教师组的直接子节点,可前往「组,用户管理」建组。 - - - {{ isDisabled(row) ? '已停用' : '启用' }} - - - 停用 - 启用 - - - - - - 说明:停用只影响「选」(新建事项/流程绑定时选不到),不影响「看」(已有事项展示)与「办」(在途流程照常解析)。 - 新建部门请前往「组,用户管理」建组;本页不提供删除。 - - + + + + + + + 说明:停用只影响「选」(新建事项 / 流程绑定时选不到),不影响「看」(已有事项展示)与「办」(在途流程照常解析)。 + 关闭「包含子组」后只展示一级部门;打开后连同下级部门(如办公室下的校办 / 宣传 / 财务 / 党建)一并列出。 + + + @@ -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; +}