From c1f710fbd80373aa29a97c01a8667fb7e619ec1a Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Mon, 6 Jul 2026 16:51:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(requirement):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E6=A0=87=E9=A2=98=E5=88=97=E7=9A=84=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E4=BD=93=E9=AA=8C=20feat(project):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=89=A7=E8=A1=8C=E9=98=B6=E6=AE=B5=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E8=8E=B7=E5=8F=96=E6=8E=A5=E5=8F=A3=20feat(m?= =?UTF-8?q?embership):=20=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE=E8=B4=9F?= =?UTF-8?q?=E8=B4=A3=E4=BA=BA=E9=80=89=E9=A1=B9=E5=8A=A0=E8=BD=BD=E9=80=BB?= =?UTF-8?q?=E8=BE=91=20fix(task):=20=E4=BC=98=E5=8C=96=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=8C=89=E9=92=AE=E7=9A=84=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20feat(todo):=20=E4=BC=98=E5=8C=96=E5=BE=85?= =?UTF-8?q?=E5=8A=9E=E4=BB=BB=E5=8A=A1=E6=88=AA=E6=AD=A2=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/api/project.ts | 15 ++++++++ src/views/product/requirement/index.vue | 35 ++++++++++++++++--- .../use-project-requirement-options.ts | 7 ++-- .../execution/composables/use-task-actions.ts | 21 +++++------ .../execution/modules/task-workspace.vue | 32 +++++++++++++---- .../project/project/requirement/index.vue | 35 ++++++++++++++++--- .../modules/workbench-todo-panel.vue | 13 ++++++- 7 files changed, 129 insertions(+), 29 deletions(-) diff --git a/src/service/api/project.ts b/src/service/api/project.ts index 38d6615..3674c77 100644 --- a/src/service/api/project.ts +++ b/src/service/api/project.ts @@ -1150,6 +1150,21 @@ export async function fetchGetProjectRequirementPage(params?: Api.Project.Projec })); } +/** 获取项目需求分页列表(排除了终止态的需求) */ +export async function fetchGetExecutionRequirementOptions(params?: Api.Project.ProjectRequirementSearchParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${PROJECT_REQUIREMENT_PREFIX}/execution-options`, + method: 'get', + params + }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + ...data, + list: data.list.map(normalizeProjectRequirement) + })); +} + /** 获取项目需求树形列表 */ export async function fetchGetProjectRequirementTree(params?: Api.Project.ProjectRequirementSearchParams) { const result = await request({ diff --git a/src/views/product/requirement/index.vue b/src/views/product/requirement/index.vue index 0b129ac..b379fa4 100644 --- a/src/views/product/requirement/index.vue +++ b/src/views/product/requirement/index.vue @@ -350,12 +350,24 @@ const columns = computed(() => [ prop: 'title', label: '需求名称', minWidth: 200, + className: 'requirement-title-column', formatter: (row: Api.Product.Requirement) => { return ( - openView(row)}> + openView(row)} + onKeydown={event => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + openView(row); + } + }} + > {row.title} - + ); } @@ -1035,14 +1047,29 @@ onMounted(async () => { } :deep(.requirement-title) { + display: block; + flex: 1 1 auto; + min-width: 0; padding: 0; font-weight: 500; - max-width: 180px; + color: var(--el-color-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; - height: auto; + vertical-align: middle; + cursor: pointer; +} + +:deep(.requirement-title-column .cell) { + display: flex; + align-items: center; + justify-content: flex-start; + min-width: 0; +} + +:deep(.requirement-title:hover) { + text-decoration: underline; } :deep(.requirement-title--terminal) { diff --git a/src/views/project/project/execution/composables/use-project-requirement-options.ts b/src/views/project/project/execution/composables/use-project-requirement-options.ts index 6c09d20..c007c02 100644 --- a/src/views/project/project/execution/composables/use-project-requirement-options.ts +++ b/src/views/project/project/execution/composables/use-project-requirement-options.ts @@ -1,5 +1,5 @@ import { ref, watch } from 'vue'; -import { fetchGetProjectRequirementPage } from '@/service/api/project'; +import { fetchGetExecutionRequirementOptions } from '@/service/api/project'; export interface ProjectRequirementTreeNode { id: string; @@ -32,10 +32,10 @@ export function useProjectRequirementOptions(projectId: () => string | null) { loading.value = true; try { - const { data, error } = await fetchGetProjectRequirementPage({ + const { data, error } = await fetchGetExecutionRequirementOptions({ projectId: id, pageNo: 1, - pageSize: 100 + pageSize: -1 }); if (error || !data?.list) { @@ -86,6 +86,7 @@ export function useProjectRequirementOptions(projectId: () => string | null) { } }); } + pruneEmptyChildren(roots); return roots; diff --git a/src/views/project/project/execution/composables/use-task-actions.ts b/src/views/project/project/execution/composables/use-task-actions.ts index b4f70aa..cda680a 100644 --- a/src/views/project/project/execution/composables/use-task-actions.ts +++ b/src/views/project/project/execution/composables/use-task-actions.ts @@ -25,7 +25,7 @@ export interface TaskActionEmits { /** * 任务行操作按钮的集中装配。 * - * 只在这里收口任务行按钮的最终显示口径,不改动底层子任务 / 父任务负责人 / 执行负责人那套权限体系: + * 这里只收口任务行按钮的最终显示口径,不改动底层子任务 / 父任务负责人 / 执行负责人那套权限体系: * - 当前用户是任务负责人(task.ownerId): 显示 填报 / 编辑 / 删除 * - 当前用户是协办人(task.assignees[].userId): 只显示 填报 * - 都不是: 也显示 填报 @@ -37,6 +37,7 @@ export function useTaskActions(emits: TaskActionEmits) { function createActions(row: Api.Project.ProjectTask): TaskAction[] { const actions: TaskAction[] = []; const isOwner = Boolean(currentUserId.value) && row.ownerId === currentUserId.value; + const isCompleted = row.statusCode === 'completed'; actions.push({ key: 'report', @@ -50,7 +51,7 @@ export function useTaskActions(emits: TaskActionEmits) { return actions; } - if (row.statusCode !== 'completed') { + if (!isCompleted) { actions.push({ key: 'edit', tooltip: '编辑', @@ -58,15 +59,15 @@ export function useTaskActions(emits: TaskActionEmits) { type: 'primary', onClick: () => emits.edit(row) }); - } - actions.push({ - key: 'delete', - tooltip: '删除', - icon: markRaw(IconMdiDeleteOutline), - type: 'danger', - onClick: () => emits.remove(row) - }); + actions.push({ + key: 'delete', + tooltip: '删除', + icon: markRaw(IconMdiDeleteOutline), + type: 'danger', + onClick: () => emits.remove(row) + }); + } return actions; } diff --git a/src/views/project/project/execution/modules/task-workspace.vue b/src/views/project/project/execution/modules/task-workspace.vue index f99d70f..6ca6525 100644 --- a/src/views/project/project/execution/modules/task-workspace.vue +++ b/src/views/project/project/execution/modules/task-workspace.vue @@ -7,6 +7,7 @@ import { fetchCreateProjectTaskAssignee, fetchDeleteProjectTask, fetchGetProjectExecutionAssignees, + fetchGetProjectMembers, fetchGetProjectTask, fetchGetProjectTaskAssignees, fetchGetProjectTaskBoardPageCross, @@ -667,11 +668,31 @@ async function refreshAssigneesAfterMutation() { async function loadExecutionAssigneeOptions(specificExecutionId?: string) { const targetId = specificExecutionId || executionId.value; - if (!props.projectId || !targetId) { + + if (!props.projectId) { executionAssigneeOptions.value = []; return; } + if (!targetId) { + const { error, data: members } = await fetchGetProjectMembers(props.projectId); + + if (error || !members) { + executionAssigneeOptions.value = []; + return; + } + + executionAssigneeOptions.value = members + .filter(item => item.status === 0) + .map(item => ({ + id: item.userId, + nickname: item.userNickname || item.userId, + username: null, + deptName: item.roleName || item.roleCode || null + })); + return; + } + const { error, data: assignees } = await fetchGetProjectExecutionAssignees(props.projectId, targetId); if (error || !assignees) { @@ -802,11 +823,7 @@ watch( // execution 锚定变化 → 拉/清"执行协办人选项"(操作弹层用),任务列表的重拉由 scopedExecutionIds watch 统一处理 watch( () => props.execution?.id, - async value => { - if (!value) { - executionAssigneeOptions.value = []; - return; - } + async () => { await loadExecutionAssigneeOptions(); } ); @@ -832,9 +849,10 @@ watch( if (!canLoadAnyTasks.value) { data.value = []; taskStatusBoard.value = null; + executionAssigneeOptions.value = []; return; } - await Promise.all([getDataByPage(1), loadTaskStatusBoard()]); + await Promise.all([getDataByPage(1), loadTaskStatusBoard(), loadExecutionAssigneeOptions()]); } ); diff --git a/src/views/project/project/requirement/index.vue b/src/views/project/project/requirement/index.vue index e89b56e..691d205 100644 --- a/src/views/project/project/requirement/index.vue +++ b/src/views/project/project/requirement/index.vue @@ -352,11 +352,23 @@ const columns = computed(() => [ prop: 'title', label: '需求名称', minWidth: 220, + className: 'requirement-title-column', formatter: (row: Api.Project.ProjectRequirement) => ( - openView(row)}> + openView(row)} + onKeydown={event => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + openView(row); + } + }} + > {row.title} - + ) }, @@ -1020,14 +1032,29 @@ Promise.all([loadStatusOptions()]); } :deep(.requirement-title) { + display: block; + flex: 1 1 auto; + min-width: 0; padding: 0; font-weight: 500; - max-width: 200px; + color: var(--el-color-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; - height: auto; + vertical-align: middle; + cursor: pointer; +} + +:deep(.requirement-title-column .cell) { + display: flex; + align-items: center; + justify-content: flex-start; + min-width: 0; +} + +:deep(.requirement-title:hover) { + text-decoration: underline; } :deep(.requirement-table-card-body) { diff --git a/src/views/workbench/modules/workbench-todo-panel.vue b/src/views/workbench/modules/workbench-todo-panel.vue index 242e52e..2e72bd2 100644 --- a/src/views/workbench/modules/workbench-todo-panel.vue +++ b/src/views/workbench/modules/workbench-todo-panel.vue @@ -1,6 +1,7 @@