修复组织树上级节点人员汇总

This commit is contained in:
汤学会 2026-06-15 02:38:54 +08:00
parent b74a4ad6d4
commit bad87029d3
3 changed files with 141 additions and 22 deletions

View File

@ -95,6 +95,7 @@ import { computed, defineComponent, h, ref, watch } from "vue";
import PaginationBar from "./PaginationBar.vue";
import StatusBadge from "./StatusBadge.vue";
import { collectOrganizationEmployees, countOrganizationEmployees } from "../utils/orgTreeEmployees";
import { usePagination } from "../utils/pagination";
const props = defineProps({
@ -228,7 +229,7 @@ const requestedNode = computed(() => findNodeById(organizationNodes.value, props
const selectedNode = computed(() => requestedNode.value || firstRootNode.value);
const effectiveSelectedNodeId = computed(() => getNodeId(selectedNode.value) ?? "");
const selectedNodeLabel = computed(() => selectedNode.value ? nodeLabel(selectedNode.value) : "未选择组织");
const selectedEmployees = computed(() => Array.isArray(selectedNode.value?.employees) ? selectedNode.value.employees : []);
const selectedEmployees = computed(() => collectOrganizationEmployees(selectedNode.value));
const canAddEmployee = computed(() => ["DEPARTMENT", "GROUP"].includes(selectedNode.value?.node_type));
const filteredEmployees = computed(() => {
const keyword = searchKeyword.value.trim().toLowerCase();
@ -358,25 +359,7 @@ function nodeTypeShortLabel(type) {
}
function employeeCount(node) {
const employeeKeys = new Set();
collectEmployeeKeys(node, employeeKeys);
return employeeKeys.size;
}
function collectEmployeeKeys(node, employeeKeys) {
if (!node) {
return;
}
for (const employee of Array.isArray(node.employees) ? node.employees : []) {
employeeKeys.add(String(employeeKey(employee)));
}
for (const child of Array.isArray(node.children) ? node.children : []) {
if (isOrganizationNode(child)) {
collectEmployeeKeys(child, employeeKeys);
} else if (child?.employee_id) {
employeeKeys.add(String(employeeKey(child)));
}
}
return countOrganizationEmployees(node);
}
function toggleNode(node) {
@ -437,8 +420,8 @@ function employeeKey(employee) {
function employeeWithParent(employee) {
return {
...employee,
parent_node_id: getNodeId(selectedNode.value),
parent_node_label: nodeLabel(selectedNode.value)
parent_node_id: employee?.parent_node_id ?? getNodeId(selectedNode.value),
parent_node_label: employee?.parent_node_label ?? nodeLabel(selectedNode.value)
};
}

View File

@ -0,0 +1,68 @@
export function collectOrganizationEmployees(node) {
const employees = [];
const seenKeys = new Set();
collectFromNode(node, node, employees, seenKeys);
return employees;
}
export function countOrganizationEmployees(node) {
return collectOrganizationEmployees(node).length;
}
function collectFromNode(node, parentNode, employees, seenKeys) {
if (!node) {
return;
}
if (isEmployeeNode(node)) {
appendEmployee(node, parentNode, employees, seenKeys);
return;
}
for (const employee of Array.isArray(node.employees) ? node.employees : []) {
appendEmployee(employee, node, employees, seenKeys);
}
for (const child of Array.isArray(node.children) ? node.children : []) {
if (isOrganizationNode(child)) {
collectFromNode(child, child, employees, seenKeys);
} else if (isEmployeeNode(child)) {
appendEmployee(child, node, employees, seenKeys);
}
}
}
function appendEmployee(employee, parentNode, employees, seenKeys) {
const key = employeeKey(employee);
if (!key || seenKeys.has(key)) {
return;
}
seenKeys.add(key);
employees.push({
...employee,
parent_node_id: employee.parent_node_id ?? nodeId(parentNode),
parent_node_label: employee.parent_node_label ?? nodeLabel(parentNode)
});
}
function isOrganizationNode(node) {
return Boolean(node) && node.node_type !== "EMPLOYEE" && !node.employee_id;
}
function isEmployeeNode(node) {
return Boolean(node) && (node.node_type === "EMPLOYEE" || Boolean(node.employee_id));
}
function employeeKey(employee) {
return String(employee?.employee_id ?? employee?.user_id ?? employee?.username ?? employee?.mobile ?? employee?.phone ?? "");
}
function nodeId(node) {
return node?.node_id ?? node?.id ?? node?.value ?? "";
}
function nodeLabel(node) {
return node?.node_label || node?.label || node?.name || "";
}

View File

@ -0,0 +1,68 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { collectOrganizationEmployees, countOrganizationEmployees } from "./orgTreeEmployees.js";
describe("organization tree employee aggregation", () => {
it("collects employees from the selected organization and all descendant organizations", () => {
const branch = {
node_id: 10,
node_type: "BRANCH",
node_label: "旭升车间",
employees: [
{ employee_id: 1, employee_name: "直属主管", mobile: "13000000001" }
],
children: [
{
node_id: 11,
node_type: "DEPARTMENT",
node_label: "旭升加工部",
employees: [
{ employee_id: 2, employee_name: "张三", mobile: "13000000002" },
{ employee_id: 3, employee_name: "李四", mobile: "13000000003" }
],
children: [
{
node_id: 12,
node_type: "GROUP",
node_label: "冲压小组",
employees: [
{ employee_id: 4, employee_name: "王五", mobile: "13000000004" }
]
}
]
}
]
};
const employees = collectOrganizationEmployees(branch);
assert.deepEqual(employees.map((employee) => employee.employee_name), ["直属主管", "张三", "李四", "王五"]);
assert.equal(employees[1].parent_node_id, 11);
assert.equal(employees[1].parent_node_label, "旭升加工部");
assert.equal(countOrganizationEmployees(branch), 4);
});
it("deduplicates employees that appear in multiple descendant branches", () => {
const employee = { employee_id: 9, employee_name: "重复人员", mobile: "13000000009" };
const branch = {
node_id: 20,
node_type: "BRANCH",
node_label: "嘉恒车间",
employees: [employee],
children: [
{
node_id: 21,
node_type: "DEPARTMENT",
node_label: "嘉恒生产部",
employees: [employee]
}
]
};
const employees = collectOrganizationEmployees(branch);
assert.equal(employees.length, 1);
assert.equal(countOrganizationEmployees(branch), 1);
});
});