From 9b60aa3e0d6babb78a972488da307422fae7a60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A4=E5=AD=A6=E4=BC=9A?= Date: Mon, 15 Jun 2026 03:27:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=84=E7=BB=87=E4=BA=BA?= =?UTF-8?q?=E5=91=98=E8=B7=AF=E5=BE=84=E4=B8=8E=E6=8E=88=E6=9D=83=E5=BC=B9?= =?UTF-8?q?=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/OrgPermissionTree.test.js | 18 + frontend/src/components/OrgPermissionTree.vue | 112 ++++- frontend/src/styles/main.css | 27 +- frontend/src/utils/orgTreeEmployees.js | 24 +- frontend/src/utils/orgTreeEmployees.test.js | 3 + .../src/views/SystemPermissionView.test.js | 25 + frontend/src/views/SystemPermissionView.vue | 445 ++++++++++++++++-- 7 files changed, 605 insertions(+), 49 deletions(-) diff --git a/frontend/src/components/OrgPermissionTree.test.js b/frontend/src/components/OrgPermissionTree.test.js index 08689a5..93a3a90 100644 --- a/frontend/src/components/OrgPermissionTree.test.js +++ b/frontend/src/components/OrgPermissionTree.test.js @@ -24,3 +24,21 @@ describe("OrgPermissionTree hierarchy lines", () => { assert.doesNotMatch(source, /background:\s*var\(--tree-connector-color\)/); }); }); + +describe("OrgPermissionTree employee list", () => { + it("shows each employee organization path with full-value hover text", () => { + assert.match(source, /组织路径<\/th>/); + assert.match(source, /employeeOrganizationPath\(employee\)/); + assert.match(source, /class="org-path-value"\s+:title="employeeOrganizationPath\(employee\)"/); + assert.match(source, /class="org-path-tail"\s+:title="employeeOrganizationPath\(employee\)"/); + assert.match(source, /function employeeOrganizationTail\(employee\)/); + assert.doesNotMatch(source, /org-path-pill/); + assert.match(source, / { + assert.match(source, /\s*
/); + assert.match(source, /\.employee-actions\s*\{[\s\S]*?display:\s*inline-flex;/); + assert.doesNotMatch(source, /\.action-row\s*\{[^}]*display:\s*flex;/); + }); +}); diff --git a/frontend/src/components/OrgPermissionTree.vue b/frontend/src/components/OrgPermissionTree.vue index c883a96..a0615de 100644 --- a/frontend/src/components/OrgPermissionTree.vue +++ b/frontend/src/components/OrgPermissionTree.vue @@ -54,9 +54,19 @@
+ + + + + + + + + + @@ -67,6 +77,10 @@ + @@ -74,12 +88,14 @@ - +
姓名组织路径 电话号码 / 账号 岗位 角色
{{ employeeName(employee) }} + {{ employeeOrganizationPath(employee) }} + {{ employeeOrganizationTail(employee) }} + {{ employeePhone(employee) }} {{ employeePosition(employee) }} {{ employeeRoles(employee) }} - - +
+ + +
{{ selectedEmployees.length ? "没有符合条件的人员" : "当前节点暂无人员" }}{{ selectedEmployees.length ? "没有符合条件的人员" : "当前节点暂无人员" }}
@@ -237,7 +253,7 @@ const filteredEmployees = computed(() => { return selectedEmployees.value; } return selectedEmployees.value.filter((employee) => - [employeeName(employee), employeePhone(employee), employee.employee_code, employee.username] + [employeeName(employee), employeeOrganizationPath(employee), employeePhone(employee), employee.employee_code, employee.username] .filter(Boolean) .join(" ") .toLowerCase() @@ -393,6 +409,15 @@ function employeePosition(employee) { return employee?.job_title || employee?.position || employee?.post_name || employee?.title || "未设置岗位"; } +function employeeOrganizationPath(employee) { + return employee?.organization_path || employee?.parent_node_label || "未设置组织"; +} + +function employeeOrganizationTail(employee) { + const pathParts = Array.isArray(employee?.organization_path_parts) ? employee.organization_path_parts.filter(Boolean) : []; + return pathParts.length ? pathParts[pathParts.length - 1] : employeeOrganizationPath(employee); +} + function employeeRoles(employee) { if (Array.isArray(employee?.role_names) && employee.role_names.length) { return employee.role_names.join("、"); @@ -764,9 +789,23 @@ function removeEmployee(employee) { } .action-row { - display: flex; - flex-wrap: wrap; + overflow: visible; + white-space: nowrap; +} + +.employee-actions { + display: inline-flex; + align-items: center; + justify-content: flex-start; gap: 8px; + min-width: max-content; +} + +.employee-actions .ghost-button { + flex: 0 0 auto; + min-height: 30px; + padding: 0 10px; + font-size: 12px; } .empty-row { @@ -779,6 +818,67 @@ function removeEmployee(employee) { table-layout: fixed; } +.org-employee-table .employee-col-name { + width: 10%; +} + +.org-employee-table .employee-col-path { + width: 30%; +} + +.org-employee-table .employee-col-phone { + width: 13%; +} + +.org-employee-table .employee-col-position { + width: 10%; +} + +.org-employee-table .employee-col-role { + width: 11%; +} + +.org-employee-table .employee-col-status { + width: 7%; +} + +.org-employee-table .employee-col-action { + width: 19%; +} + +.org-path-cell { + min-width: 0; +} + +.org-path-value, +.org-path-tail { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.org-path-value { + color: #334155; + font-size: 13px; + font-weight: 700; + line-height: 1.35; +} + +.org-path-tail { + width: fit-content; + max-width: 100%; + margin-top: 3px; + border-radius: 999px; + padding: 2px 7px; + background: #eef6ff; + color: #2563eb; + font-size: 11px; + font-weight: 750; + line-height: 1.2; +} + :deep(.org-tree-branch) { position: relative; display: flex; diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css index e1a9c91..300ecd6 100644 --- a/frontend/src/styles/main.css +++ b/frontend/src/styles/main.css @@ -12062,32 +12062,37 @@ body .system-permission-page .tree-layout-card .org-employee-table td { body .system-permission-page .tree-layout-card .org-employee-table th:nth-child(1), body .system-permission-page .tree-layout-card .org-employee-table td:nth-child(1) { - width: 13% !important; + width: 10% !important; } body .system-permission-page .tree-layout-card .org-employee-table th:nth-child(2), body .system-permission-page .tree-layout-card .org-employee-table td:nth-child(2) { - width: 20% !important; + width: 30% !important; } body .system-permission-page .tree-layout-card .org-employee-table th:nth-child(3), body .system-permission-page .tree-layout-card .org-employee-table td:nth-child(3) { - width: 14% !important; + width: 13% !important; } body .system-permission-page .tree-layout-card .org-employee-table th:nth-child(4), body .system-permission-page .tree-layout-card .org-employee-table td:nth-child(4) { - width: 17% !important; + width: 10% !important; } body .system-permission-page .tree-layout-card .org-employee-table th:nth-child(5), body .system-permission-page .tree-layout-card .org-employee-table td:nth-child(5) { - width: 12% !important; + width: 11% !important; } body .system-permission-page .tree-layout-card .org-employee-table th:nth-child(6), body .system-permission-page .tree-layout-card .org-employee-table td:nth-child(6) { - width: 24% !important; + width: 7% !important; +} + +body .system-permission-page .tree-layout-card .org-employee-table th:nth-child(7), +body .system-permission-page .tree-layout-card .org-employee-table td:nth-child(7) { + width: 19% !important; } body .system-permission-page .tree-layout-card .org-employee-table .action-row { @@ -12096,9 +12101,17 @@ body .system-permission-page .tree-layout-card .org-employee-table .action-row { white-space: nowrap !important; } +body .system-permission-page .tree-layout-card .org-employee-table .employee-actions { + display: inline-flex !important; + align-items: center !important; + justify-content: flex-start !important; + gap: 8px !important; + min-width: max-content !important; +} + body .system-permission-page .tree-layout-card .org-employee-table .action-row button { min-height: 30px !important; - padding: 0 8px !important; + padding: 0 10px !important; font-size: 12px !important; } diff --git a/frontend/src/utils/orgTreeEmployees.js b/frontend/src/utils/orgTreeEmployees.js index 1e49faa..cde0274 100644 --- a/frontend/src/utils/orgTreeEmployees.js +++ b/frontend/src/utils/orgTreeEmployees.js @@ -2,7 +2,7 @@ export function collectOrganizationEmployees(node) { const employees = []; const seenKeys = new Set(); - collectFromNode(node, node, employees, seenKeys); + collectFromNode(node, node, employees, seenKeys, []); return employees; } @@ -10,40 +10,48 @@ export function countOrganizationEmployees(node) { return collectOrganizationEmployees(node).length; } -function collectFromNode(node, parentNode, employees, seenKeys) { +function collectFromNode(node, parentNode, employees, seenKeys, pathParts) { if (!node) { return; } if (isEmployeeNode(node)) { - appendEmployee(node, parentNode, employees, seenKeys); + appendEmployee(node, parentNode, employees, seenKeys, pathParts); return; } + const currentPathParts = [...pathParts, nodeLabel(node)].filter(Boolean); + for (const employee of Array.isArray(node.employees) ? node.employees : []) { - appendEmployee(employee, node, employees, seenKeys); + appendEmployee(employee, node, employees, seenKeys, currentPathParts); } for (const child of Array.isArray(node.children) ? node.children : []) { if (isOrganizationNode(child)) { - collectFromNode(child, child, employees, seenKeys); + collectFromNode(child, child, employees, seenKeys, currentPathParts); } else if (isEmployeeNode(child)) { - appendEmployee(child, node, employees, seenKeys); + appendEmployee(child, node, employees, seenKeys, currentPathParts); } } } -function appendEmployee(employee, parentNode, employees, seenKeys) { +function appendEmployee(employee, parentNode, employees, seenKeys, pathParts) { const key = employeeKey(employee); if (!key || seenKeys.has(key)) { return; } + const organizationPathParts = Array.isArray(employee.organization_path_parts) && employee.organization_path_parts.length + ? employee.organization_path_parts + : pathParts; + seenKeys.add(key); employees.push({ ...employee, parent_node_id: employee.parent_node_id ?? nodeId(parentNode), - parent_node_label: employee.parent_node_label ?? nodeLabel(parentNode) + parent_node_label: employee.parent_node_label ?? nodeLabel(parentNode), + organization_path_parts: organizationPathParts, + organization_path: employee.organization_path || organizationPathParts.join(" / ") }); } diff --git a/frontend/src/utils/orgTreeEmployees.test.js b/frontend/src/utils/orgTreeEmployees.test.js index 258cc85..a51cea1 100644 --- a/frontend/src/utils/orgTreeEmployees.test.js +++ b/frontend/src/utils/orgTreeEmployees.test.js @@ -40,6 +40,9 @@ describe("organization tree employee aggregation", () => { assert.deepEqual(employees.map((employee) => employee.employee_name), ["直属主管", "张三", "李四", "王五"]); assert.equal(employees[1].parent_node_id, 11); assert.equal(employees[1].parent_node_label, "旭升加工部"); + assert.deepEqual(employees[1].organization_path_parts, ["旭升车间", "旭升加工部"]); + assert.equal(employees[1].organization_path, "旭升车间 / 旭升加工部"); + assert.equal(employees[3].organization_path, "旭升车间 / 旭升加工部 / 冲压小组"); assert.equal(countOrganizationEmployees(branch), 4); }); diff --git a/frontend/src/views/SystemPermissionView.test.js b/frontend/src/views/SystemPermissionView.test.js index f5d9a0a..f893e06 100644 --- a/frontend/src/views/SystemPermissionView.test.js +++ b/frontend/src/views/SystemPermissionView.test.js @@ -46,3 +46,28 @@ describe("SystemPermissionView maintenance scrolling", () => { assert.match(mainStyles, /\.system-permission-page\.system-permission-page--maintenance\s+\.system-permission-maintenance-card\s*\{[\s\S]*?overflow:\s*visible;/); }); }); + +describe("SystemPermissionView organization employee table columns", () => { + it("keeps the organization employee table aligned after adding the organization path column", () => { + assert.match(mainStyles, /\.tree-layout-card\s+\.org-employee-table th:nth-child\(7\),[\s\S]*?td:nth-child\(7\)\s*\{[\s\S]*?width:\s*19%\s*!important;/); + assert.match(mainStyles, /\.tree-layout-card\s+\.org-employee-table \.action-row\s*\{[\s\S]*?display:\s*table-cell\s*!important;/); + assert.match(mainStyles, /\.tree-layout-card\s+\.org-employee-table \.employee-actions\s*\{[\s\S]*?display:\s*inline-flex\s*!important;[\s\S]*?min-width:\s*max-content\s*!important;/); + }); +}); + +describe("SystemPermissionView authorize drawer", () => { + it("uses card-style role selection instead of a native multi-select", () => { + const authorizeStart = source.indexOf('v-if="showAuthorizeDrawer"'); + const authorizeEnd = source.indexOf('v-if="showRoleDrawer"', authorizeStart); + const authorizeSource = source.slice(authorizeStart, authorizeEnd); + + assert.doesNotMatch(authorizeSource, //); + assert.match(authorizeSource, /class="authorize-role-grid"/); + assert.match(authorizeSource, /v-for="role in roles"/); + assert.match(authorizeSource, /@click="toggleAuthorizeRole\(role\.role_id\)"/); + assert.match(authorizeSource, /isAuthorizeRoleSelected\(role\.role_id\)/); + assert.match(authorizeSource, /selectedAuthorizeRoleNames/); + assert.match(authorizeSource, /roleRiskText\(role\)/); + assert.match(authorizeSource, /:disabled="!authorizeForm\.role_ids\.length"\s+@click="submitAuthorizeUser"/); + }); +}); diff --git a/frontend/src/views/SystemPermissionView.vue b/frontend/src/views/SystemPermissionView.vue index e75127e..652e1e3 100644 --- a/frontend/src/views/SystemPermissionView.vue +++ b/frontend/src/views/SystemPermissionView.vue @@ -156,42 +156,87 @@
-