From 59b73f3daee867b33e79a327f6d2b3514aff185b Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Thu, 14 May 2026 14:11:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor(projects):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E9=A1=B9=E7=9B=AE=E6=96=B0=E5=A2=9E=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/api/product-shared.ts | 3 + src/service/api/project-shared.ts | 3 + src/typings/api/product.d.ts | 11 +- src/typings/api/project.d.ts | 11 +- src/typings/components.d.ts | 1 + .../list/modules/product-create-team-step.vue | 73 +- .../list/modules/product-operate-dialog.vue | 6 +- .../setting/modules/setting-team-panel.vue | 29 +- .../list/modules/project-create-team-step.vue | 73 +- .../list/modules/project-operate-dialog.vue | 6 +- .../setting/modules/setting-team-panel.vue | 29 +- 关心人功能-API接口文档.html | 1144 +++++++++++++++++ 成员列表接口变更-前端对接说明.html | 754 +++++++++++ 13 files changed, 2133 insertions(+), 10 deletions(-) create mode 100644 关心人功能-API接口文档.html create mode 100644 成员列表接口变更-前端对接说明.html diff --git a/src/service/api/product-shared.ts b/src/service/api/product-shared.ts index 6a02a8d..ca8b373 100644 --- a/src/service/api/product-shared.ts +++ b/src/service/api/product-shared.ts @@ -33,6 +33,8 @@ interface ProductMemberResponse { roleId: string | number; roleName: string; roleCode: string; + /** 多角色合并展示的非主角色名列表 */ + additionalRoleNames?: string[] | null; managerFlag: boolean; status: 0 | 1; joinedTime: string; @@ -74,6 +76,7 @@ export function normalizeProductMember(response: ProductMemberResponse): Api.Pro roleId: normalizeStringId(response.roleId), roleName: response.roleName || '', roleCode: response.roleCode || '', + additionalRoleNames: response.additionalRoleNames ?? [], managerFlag: Boolean(response.managerFlag), status: response.status, joinedTime: response.joinedTime, diff --git a/src/service/api/project-shared.ts b/src/service/api/project-shared.ts index b709815..a0dd2a5 100644 --- a/src/service/api/project-shared.ts +++ b/src/service/api/project-shared.ts @@ -147,6 +147,8 @@ export interface ProjectMemberResponse { roleId: string | number; roleName: string; roleCode: string; + /** 多角色合并展示的非主角色名列表 */ + additionalRoleNames?: string[] | null; managerFlag: boolean; status: 0 | 1; joinedTime: string; @@ -225,6 +227,7 @@ export function normalizeProjectMember(response: ProjectMemberResponse): Api.Pro roleId: normalizeStringId(response.roleId), roleName: response.roleName || '', roleCode: response.roleCode || '', + additionalRoleNames: response.additionalRoleNames ?? [], managerFlag: Boolean(response.managerFlag), status: response.status, joinedTime: response.joinedTime, diff --git a/src/typings/api/product.d.ts b/src/typings/api/product.d.ts index bc7fef8..7ddfe5f 100644 --- a/src/typings/api/product.d.ts +++ b/src/typings/api/product.d.ts @@ -99,10 +99,15 @@ declare namespace Api { userNickname: string; /** 角色 ID */ roleId: string; - /** 角色名称 */ + /** 角色名称(主角色) */ roleName: string; - /** 角色编码 */ + /** 角色编码(主角色) */ roleCode: string; + /** + * 非主角色的中文名列表(多角色合并展示用,按字典序升序) + * 单角色时为空数组 [];典型场景:创建者 + 经理重合时,主行 manager,creator 名进此列表 + */ + additionalRoleNames: string[]; /** 是否当前产品经理 */ managerFlag: boolean; /** 成员状态 */ @@ -218,6 +223,8 @@ declare namespace Api { interface CreateProductWithTeamParams { product: SaveProductParams; members: CreateProductMemberParams[]; + /** 关心人 user_id 数组(选填);后端按 (user, object, role) 三元组幂等写入 product_watcher 角色 */ + watcherUserIds?: string[]; } interface UpdateProductMemberParams { diff --git a/src/typings/api/project.d.ts b/src/typings/api/project.d.ts index 7b4bff1..baf17a3 100644 --- a/src/typings/api/project.d.ts +++ b/src/typings/api/project.d.ts @@ -519,10 +519,15 @@ declare namespace Api { userNickname: string; /** 角色 ID */ roleId: string; - /** 角色名称 */ + /** 角色名称(主角色) */ roleName: string; - /** 角色编码 */ + /** 角色编码(主角色) */ roleCode: string; + /** + * 非主角色的中文名列表(多角色合并展示用,按字典序升序) + * 单角色时为空数组 [];典型场景:创建者 + 负责人重合时,主行 manager,creator 名进此列表 + */ + additionalRoleNames: string[]; /** 是否项目负责人 */ managerFlag: boolean; /** 成员状态 */ @@ -628,6 +633,8 @@ declare namespace Api { interface CreateProjectWithTeamParams { project: SaveProjectParams; members: CreateProjectMemberParams[]; + /** 关心人 user_id 数组(选填);后端按 (user, object, role) 三元组幂等写入 project_watcher 角色 */ + watcherUserIds?: string[]; } // ========== 项目需求相关类型定义 ========== diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index 407316c..90fe525 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -104,6 +104,7 @@ declare module 'vue' { IconEpSuccessFilled: typeof import('~icons/ep/success-filled')['default'] 'IconF7:circleFill': typeof import('~icons/f7/circle-fill')['default'] 'IconF7:flagCircleFill': typeof import('~icons/f7/flag-circle-fill')['default'] + 'IconFe:eye': typeof import('~icons/fe/eye')['default'] 'IconFe:question': typeof import('~icons/fe/question')['default'] 'IconFileIcons:microsoftExcel': typeof import('~icons/file-icons/microsoft-excel')['default'] 'IconGg:ratio': typeof import('~icons/gg/ratio')['default'] diff --git a/src/views/product/list/modules/product-create-team-step.vue b/src/views/product/list/modules/product-create-team-step.vue index f734ffc..144cb9a 100644 --- a/src/views/product/list/modules/product-create-team-step.vue +++ b/src/views/product/list/modules/product-create-team-step.vue @@ -27,6 +27,7 @@ const props = defineProps(); const emit = defineEmits<{ (e: 'update:members', members: Api.Product.CreateProductMemberParams[]): void; + (e: 'update:watcherUserIds', watcherUserIds: string[]): void; }>(); const roleOptions = ref([]); @@ -38,7 +39,15 @@ const memberDialogVisible = ref(false); const memberDialogMode = ref<'create' | 'edit'>('create'); const editingKey = ref(null); -const teamTableHeight = getProductTeamTableHeight(5); +const watcherUserIds = ref([]); + +// 关心人候选用户:排除已在团队成员列表中的用户(包含产品经理本人) +const watcherUserOptions = computed(() => { + const memberUserIds = new Set(members.value.map(item => item.userId).filter(Boolean)); + return props.userOptions.filter(user => !memberUserIds.has(user.id)); +}); + +const teamTableHeight = getProductTeamTableHeight(4); const userLabelMap = computed(() => new Map(props.userOptions.map(item => [String(item.id), item.nickname]))); @@ -205,6 +214,24 @@ async function runValidate(): Promise { return true; } +function handleWatcherChange(ids: string[]) { + watcherUserIds.value = ids; + emit('update:watcherUserIds', ids); +} + +// 团队成员变化时,剔除已被加入团队的关心人,避免重叠 +watch( + () => members.value.map(item => item.userId).join(','), + () => { + const memberUserIds = new Set(members.value.map(item => item.userId).filter(Boolean)); + const filtered = watcherUserIds.value.filter(id => !memberUserIds.has(id)); + + if (filtered.length !== watcherUserIds.value.length) { + handleWatcherChange(filtered); + } + } +); + onMounted(loadRoles); watch( @@ -261,6 +288,27 @@ defineExpose({ validate: runValidate }); +
+ + 关心人 + (选填) + + + + +
+ diff --git a/src/views/product/list/modules/product-operate-dialog.vue b/src/views/product/list/modules/product-operate-dialog.vue index 2ced26e..64b796a 100644 --- a/src/views/product/list/modules/product-operate-dialog.vue +++ b/src/views/product/list/modules/product-operate-dialog.vue @@ -114,6 +114,7 @@ const currentStep = ref<1 | 2>(1); const createBaseModel = ref(createBaseInfo()); const draftMembers = ref([]); +const draftWatcherUserIds = ref([]); function createBaseInfo(): ProductCreateBaseFormModel { return { code: '', name: '', directionCode: '', managerUserId: null, description: '' }; @@ -157,7 +158,8 @@ async function handleCreateSubmit() { managerUserId: createBaseModel.value.managerUserId as string, description: getNullableText(createBaseModel.value.description) }, - members: draftMembers.value + members: draftMembers.value, + watcherUserIds: draftWatcherUserIds.value.length > 0 ? draftWatcherUserIds.value : undefined }; const { error, data } = await fetchCreateProductWithTeam(payload); @@ -186,6 +188,7 @@ watch(visible, async value => { editModel.value = createEditModel(); createBaseModel.value = createBaseInfo(); draftMembers.value = []; + draftWatcherUserIds.value = []; await nextTick(); editFormRef.value?.clearValidate(); return; @@ -330,6 +333,7 @@ watch(visible, async value => { :base-info="createBaseModel" :user-options="managerUserOptions" @update:members="draftMembers = $event" + @update:watcher-user-ids="draftWatcherUserIds = $event" /> diff --git a/src/views/product/setting/modules/setting-team-panel.vue b/src/views/product/setting/modules/setting-team-panel.vue index 684d726..23fa53d 100644 --- a/src/views/product/setting/modules/setting-team-panel.vue +++ b/src/views/product/setting/modules/setting-team-panel.vue @@ -103,7 +103,23 @@ function getMemberStatusTagType(status: Api.Product.ProductMemberStatus) { > - + + +