feat(version): 优化参与人员添加体验

This commit is contained in:
Script Generator
2026-06-26 15:33:07 +08:00
parent 2d802318fa
commit b5ac0a2d47
3 changed files with 275 additions and 45 deletions

View File

@@ -0,0 +1,65 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { addVersionMembers, filterVersionMemberCandidates, inferVersionMemberRole } from './version-members';
test('filterVersionMemberCandidates searches people and excludes already added members', () => {
const result = filterVersionMemberCandidates(
[
{ id: 'm-1', name: 'Alice' },
{ id: 'm-2', name: 'Bob' },
{ id: 'm-3', name: 'Alicia' },
],
[{ name: 'Alice', role: 'frontend' }],
'ali',
);
assert.deepEqual(result.map((member) => member.name), ['Alicia']);
});
test('addVersionMembers appends selected people once with the default version role', () => {
const result = addVersionMembers(
[{ name: 'Alice', role: 'product' }],
['Bob', 'Alicia', 'Alice'],
[
{ id: 'm-1', name: 'Alice' },
{ id: 'm-2', name: 'Bob' },
{ id: 'm-3', name: 'Alicia' },
],
);
assert.deepEqual(result, [
{ name: 'Alice', role: 'product' },
{ name: 'Bob', role: 'frontend' },
{ name: 'Alicia', role: 'frontend' },
]);
});
test('inferVersionMemberRole maps department names to project roles', () => {
assert.equal(inferVersionMemberRole({ id: 'm-1', name: 'Alice', departmentName: '产品部' }), 'product');
assert.equal(inferVersionMemberRole({ id: 'm-2', name: 'Bob', departmentName: '设计部' }), 'ui');
assert.equal(inferVersionMemberRole({ id: 'm-3', name: 'Cathy', departmentName: '前端组' }), 'frontend');
assert.equal(inferVersionMemberRole({ id: 'm-4', name: 'David', departmentName: '后端组' }), 'backend');
assert.equal(inferVersionMemberRole({ id: 'm-5', name: 'Eve', departmentName: '测试组' }), 'testing');
assert.equal(inferVersionMemberRole({ id: 'm-6', name: 'Frank', departmentName: '运营部' }), 'frontend');
});
test('addVersionMembers infers each selected person role from department', () => {
const result = addVersionMembers(
[],
['Product', 'Designer', 'Backend', 'QA'],
[
{ id: 'm-1', name: 'Product', departmentName: '产品部' },
{ id: 'm-2', name: 'Designer', departmentName: 'UI设计部' },
{ id: 'm-3', name: 'Backend', departmentName: '后端组' },
{ id: 'm-4', name: 'QA', departmentName: '质量测试部' },
],
);
assert.deepEqual(result, [
{ name: 'Product', role: 'product' },
{ name: 'Designer', role: 'ui' },
{ name: 'Backend', role: 'backend' },
{ name: 'QA', role: 'testing' },
]);
});

View File

@@ -0,0 +1,58 @@
import type { Role } from './stage';
export interface VersionMember {
name: string;
role: Role;
}
export interface VersionMemberCandidate {
id: string;
name: string;
departmentName?: string;
}
export const DEFAULT_VERSION_MEMBER_ROLE: Role = 'frontend';
export function inferVersionMemberRole(
member: Partial<VersionMemberCandidate>,
defaultRole: Role = DEFAULT_VERSION_MEMBER_ROLE,
): Role {
const departmentName = member.departmentName?.toLowerCase() ?? '';
if (!departmentName) return defaultRole;
if (departmentName.includes('产品') || departmentName.includes('product')) return 'product';
if (departmentName.includes('设计') || departmentName.includes('ui') || departmentName.includes('ux')) return 'ui';
if (departmentName.includes('测试') || departmentName.includes('质量') || departmentName.includes('qa')) return 'testing';
if (departmentName.includes('后端') || departmentName.includes('backend') || departmentName.includes('server')) return 'backend';
if (departmentName.includes('前端') || departmentName.includes('frontend') || departmentName.includes('web') || departmentName.includes('client')) return 'frontend';
return defaultRole;
}
export function filterVersionMemberCandidates(
allMembers: VersionMemberCandidate[],
currentMembers: VersionMember[],
query: string,
): VersionMemberCandidate[] {
const existingNames = new Set(currentMembers.map((member) => member.name));
const normalizedQuery = query.trim().toLowerCase();
return allMembers.filter((member) => {
if (existingNames.has(member.name)) return false;
if (!normalizedQuery) return true;
return member.name.toLowerCase().includes(normalizedQuery);
});
}
export function addVersionMembers(
currentMembers: VersionMember[],
selectedNames: Iterable<string>,
allMembers: VersionMemberCandidate[],
defaultRole: Role = DEFAULT_VERSION_MEMBER_ROLE,
): VersionMember[] {
const existingNames = new Set(currentMembers.map((member) => member.name));
const selectedNameSet = new Set(selectedNames);
const additions = allMembers
.filter((member) => selectedNameSet.has(member.name) && !existingNames.has(member.name))
.map((member) => ({ name: member.name, role: inferVersionMemberRole(member, defaultRole) }));
return [...currentMembers, ...additions];
}