diff --git a/frontend/src/components/OrgPermissionTree.test.js b/frontend/src/components/OrgPermissionTree.test.js index 2620ee9..ad691db 100644 --- a/frontend/src/components/OrgPermissionTree.test.js +++ b/frontend/src/components/OrgPermissionTree.test.js @@ -8,10 +8,13 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const source = readFileSync(join(__dirname, "OrgPermissionTree.vue"), "utf8"); describe("OrgPermissionTree hierarchy lines", () => { - it("draws connector lines so parent-child hierarchy is easier to read", () => { - assert.match(source, /\.org-tree-branch::before/); - assert.match(source, /\.org-tree-node::before/); + it("draws connector lines only between each node and its direct children", () => { + assert.match(source, /\.org-tree-children\s*>\s*\.org-tree-branch\s*>\s*\.org-tree-node::before/); + assert.match(source, /\.org-tree-children\s*>\s*\.org-tree-branch\s*>\s*\.org-tree-node::after/); + assert.doesNotMatch(source, /\.org-tree-children::before/); + assert.doesNotMatch(source, /\.org-tree-branch::before/); assert.match(source, /--tree-connector-x/); assert.match(source, /--tree-connector-color/); + assert.match(source, /--tree-connector-width:\s*2px/); }); }); diff --git a/frontend/src/components/OrgPermissionTree.vue b/frontend/src/components/OrgPermissionTree.vue index 9167ba9..b46801d 100644 --- a/frontend/src/components/OrgPermissionTree.vue +++ b/frontend/src/components/OrgPermissionTree.vue @@ -166,14 +166,14 @@ const OrgTreeBranch = defineComponent({ return () => h("div", { class: ["org-tree-branch", { "org-tree-branch-root": branchProps.level === 0 }], - style: { "--tree-level": branchProps.level } + style: treeLevelStyle(branchProps.level) }, [ h( "button", { type: "button", class: ["org-tree-node", { active: isSelected.value }], - style: { "--tree-level": branchProps.level }, + style: treeLevelStyle(branchProps.level), onClick: selectCurrent, onContextmenu: openMenu }, @@ -266,6 +266,13 @@ function organizationChildren(node) { return (Array.isArray(node?.children) ? node.children : []).filter(isOrganizationNode); } +function treeLevelStyle(level) { + return { + "--tree-level": level, + "--tree-connector-x": `${15 + Number(level || 0) * 22}px` + }; +} + function filterOrgTreeByKeyword(nodes, keyword) { const normalizedKeyword = String(keyword || "").trim().toLowerCase(); if (!normalizedKeyword) { @@ -570,8 +577,9 @@ function removeEmployee(employee) { } .org-tree-list { - --tree-connector-color: rgba(64, 158, 255, 0.34); - --tree-connector-x: calc(15px + var(--tree-level, 0) * 22px); + --tree-connector-color: rgba(37, 99, 235, 0.74); + --tree-connector-width: 2px; + --tree-connector-arm: 14px; display: flex; flex: 1; flex-direction: column; @@ -590,26 +598,6 @@ function removeEmployee(employee) { gap: 2px; } -.org-tree-branch::before { - content: ""; - position: absolute; - top: -2px; - bottom: -2px; - left: var(--tree-connector-x); - width: 1px; - background: linear-gradient(180deg, transparent, var(--tree-connector-color) 12px, var(--tree-connector-color) calc(100% - 12px), transparent); - pointer-events: none; -} - -.org-tree-branch-root::before { - content: none; -} - -.org-tree-branch:not(.org-tree-branch-root):last-child::before { - bottom: calc(100% - 21px); - background: var(--tree-connector-color); -} - .org-tree-node { position: relative; display: grid; @@ -627,14 +615,29 @@ function removeEmployee(employee) { transition: background 0.14s ease, color 0.14s ease, box-shadow 0.14s ease; } -.org-tree-branch:not(.org-tree-branch-root) > .org-tree-node::before { +.org-tree-children > .org-tree-branch > .org-tree-node::before { content: ""; position: absolute; top: 50%; - left: var(--tree-connector-x); - width: 14px; - height: 1px; + left: calc(var(--tree-connector-x) - var(--tree-connector-width) / 2); + width: var(--tree-connector-arm); + height: var(--tree-connector-width); background: var(--tree-connector-color); + border-radius: 999px; + transform: translateY(-50%); + pointer-events: none; +} + +.org-tree-children > .org-tree-branch > .org-tree-node::after { + content: ""; + position: absolute; + top: calc(50% - 10px); + left: var(--tree-connector-x); + width: var(--tree-connector-width); + height: 20px; + background: var(--tree-connector-color); + border-radius: 999px; + transform: translateX(-50%); pointer-events: none; } @@ -708,6 +711,7 @@ function removeEmployee(employee) { } .org-tree-children { + position: relative; display: flex; flex-direction: column; gap: 2px; @@ -794,26 +798,6 @@ function removeEmployee(employee) { gap: 2px; } -:deep(.org-tree-branch::before) { - content: ""; - position: absolute; - top: -2px; - bottom: -2px; - left: var(--tree-connector-x); - width: 1px; - background: linear-gradient(180deg, transparent, var(--tree-connector-color) 12px, var(--tree-connector-color) calc(100% - 12px), transparent); - pointer-events: none; -} - -:deep(.org-tree-branch-root::before) { - content: none; -} - -:deep(.org-tree-branch:not(.org-tree-branch-root):last-child::before) { - bottom: calc(100% - 21px); - background: var(--tree-connector-color); -} - :deep(.org-tree-node) { position: relative; display: grid; @@ -832,14 +816,29 @@ function removeEmployee(employee) { transition: background 0.14s ease, color 0.14s ease, box-shadow 0.14s ease; } -:deep(.org-tree-branch:not(.org-tree-branch-root) > .org-tree-node::before) { +:deep(.org-tree-children > .org-tree-branch > .org-tree-node::before) { content: ""; position: absolute; top: 50%; - left: var(--tree-connector-x); - width: 14px; - height: 1px; + left: calc(var(--tree-connector-x) - var(--tree-connector-width) / 2); + width: var(--tree-connector-arm); + height: var(--tree-connector-width); background: var(--tree-connector-color); + border-radius: 999px; + transform: translateY(-50%); + pointer-events: none; +} + +:deep(.org-tree-children > .org-tree-branch > .org-tree-node::after) { + content: ""; + position: absolute; + top: calc(50% - 10px); + left: var(--tree-connector-x); + width: var(--tree-connector-width); + height: 20px; + background: var(--tree-connector-color); + border-radius: 999px; + transform: translateX(-50%); pointer-events: none; } @@ -913,6 +912,7 @@ function removeEmployee(employee) { } :deep(.org-tree-children) { + position: relative; display: flex; flex-direction: column; gap: 2px;