feat(execution): 实现执行模块视角切换和快捷过滤功能

- 添加执行视角切换功能(my/all),支持不同身份维度查看
- 实现逾期/本周到期快捷过滤功能,提升执行管理效率
- 重构执行区域UI布局,优化用户体验和界面结构
- 集成Element Plus表单验证,在用户选择器组件中使用
- 优化执行状态筛选和计数逻辑,提升数据展示准确性
- 实现执行视角切换时的数据同步刷新机制
- 添加执行完成操作的二次确认对话框
- 重构权限码检查逻辑,统一使用query权限码进行控制
- 移除auth store依赖,精简代码结构
- 优化执行状态看板和任务计数的加载机制
- 实现执行创建和编辑流程的状态同步更新
- 统一任务工作区的执行范围传递方式,提高性能
- 添加执行详情面板的操作按钮权限控制
- 优化执行删除后的数据刷新逻辑,确保视图一致性
This commit is contained in:
2026-05-29 16:40:25 +08:00
parent 4ed4b537ad
commit b2da882b31
13 changed files with 822 additions and 278 deletions

View File

@@ -22,6 +22,11 @@ export interface ProductActivityTextPart {
strong?: boolean;
}
interface ActivitySummaryResult {
text: string;
parts: ProductActivityTextPart[];
}
export interface ProductActivityDisplayItem extends Api.Product.ProductActivityTimelineItem {
tagLabel: string;
timeText: string;
@@ -265,34 +270,46 @@ function buildMemberChangeSummary(
item: Api.Product.ProductActivityTimelineItem,
detailsRecord: ActivityDetailRecord | null,
operatorText: string
) {
): ActivitySummaryResult | null {
const memberName = getActivityTargetUserName(item, detailsRecord);
const roleName = getActivityTargetRoleName(item, detailsRecord);
if (!memberName) {
return '';
return null;
}
const memberDetail = roleName ? `${memberName}${roleName}` : memberName;
const prefix =
operatorText === '--' ? `执行了【${item.actionName}】:` : `${operatorText}执行了【${item.actionName}】:`;
const roleSuffix = roleName ? `${roleName}` : '';
const text = `${prefix}${memberName}${roleSuffix}`;
const parts: ProductActivityTextPart[] = [{ text: prefix }, { text: memberName, strong: true }];
return operatorText === '--'
? `执行了【${item.actionName}】:${memberDetail}`
: `${operatorText}执行了【${item.actionName}】:${memberDetail}`;
if (roleSuffix) {
parts.push({ text: roleSuffix });
}
return { text, parts };
}
function buildMemberUpdateSummary(
item: Api.Product.ProductActivityTimelineItem,
detailsRecord: ActivityDetailRecord | null,
operatorText: string
) {
): ActivitySummaryResult {
const memberName = getActivityTargetUserName(item, detailsRecord);
const roleTransitionText = getRoleTransitionText(detailsRecord);
const memberText = memberName || '成员';
const roleText = roleTransitionText ? `,角色:${roleTransitionText}` : '';
const prefix =
operatorText === '--' ? `执行了【${item.actionName}】:` : `${operatorText}执行了【${item.actionName}】:`;
const text = `${prefix}${memberText}${roleText}`;
const parts: ProductActivityTextPart[] = [{ text: prefix }, { text: memberText, strong: Boolean(memberName) }];
return operatorText === '--'
? `执行了【${item.actionName}】:${memberText}${roleText}`
: `${operatorText}执行了【${item.actionName}】:${memberText}${roleText}`;
if (roleText) {
parts.push({ text: roleText });
}
return { text, parts };
}
function buildManagerChangeSummary(detailsRecord: ActivityDetailRecord | null, operatorText: string) {
@@ -319,16 +336,20 @@ function buildManagerChangeSummary(detailsRecord: ActivityDetailRecord | null, o
return operatorText === '--' ? `变更产品经理:${transitionText}` : `${operatorText}变更产品经理:${transitionText}`;
}
function plainSummary(text: string): ActivitySummaryResult {
return { text, parts: [{ text }] };
}
function resolveDetailedSummary(
item: Api.Product.ProductActivityTimelineItem,
detailsRecord: ActivityDetailRecord | null,
texts: { operatorText: string; actionText: string }
) {
): ActivitySummaryResult {
const { operatorText, actionText } = texts;
const summaryText = item.summary?.trim() || '';
if (item.actionType === 'add_member' || item.actionType === 'remove_member') {
return buildMemberChangeSummary(item, detailsRecord, operatorText) || summaryText || actionText;
return buildMemberChangeSummary(item, detailsRecord, operatorText) || plainSummary(summaryText || actionText);
}
if (item.actionType === 'update_member') {
@@ -336,29 +357,16 @@ function resolveDetailedSummary(
}
if (!isGenericActivitySummary(summaryText, actionText)) {
return summaryText;
return plainSummary(summaryText);
}
if (item.actionType === 'change_manager') {
return buildManagerChangeSummary(detailsRecord, operatorText) || summaryText || actionText;
const managerSummary = buildManagerChangeSummary(detailsRecord, operatorText);
return plainSummary(managerSummary || summaryText || actionText);
}
return summaryText || actionText;
}
function buildProductActivityTextParts(text: string, subjectText: string): ProductActivityTextPart[] {
const normalizedSubject = subjectText.trim();
const subjectIndex = normalizedSubject ? text.indexOf(normalizedSubject) : -1;
if (subjectIndex < 0) {
return [{ text }];
}
return [
{ text: text.slice(0, subjectIndex) },
{ text: normalizedSubject, strong: true },
{ text: text.slice(subjectIndex + normalizedSubject.length) }
].filter(part => part.text);
return plainSummary(summaryText || actionText);
}
export function buildProductActivityDisplayItem(
@@ -369,18 +377,19 @@ export function buildProductActivityDisplayItem(
operatorText === '--' ? `执行了【${item.actionName}` : `${operatorText}执行了【${item.actionName}`;
const detailsRecord = parseActivityDetails(item.details);
const subjectText = isMemberActivityAction(item.actionType) ? getActivityTargetUserName(item, detailsRecord) : '';
const displaySummary =
item.type === 'status' ? actionText : resolveDetailedSummary(item, detailsRecord, { operatorText, actionText });
const compactText = displaySummary;
const summary =
item.type === 'status'
? plainSummary(actionText)
: resolveDetailedSummary(item, detailsRecord, { operatorText, actionText });
return {
...item,
tagLabel: activityTypeLabelMap[item.type],
timeText: formatProductActivityTime(item.occurredAt) || '--',
actionText,
displaySummary,
compactText,
compactTextParts: buildProductActivityTextParts(compactText, subjectText),
displaySummary: summary.text,
compactText: summary.text,
compactTextParts: summary.parts.filter(part => part.text),
operatorText,
subjectText,
reasonText: item.reason?.trim() || '',

View File

@@ -17,7 +17,6 @@ import {
fetchUpdateProductMember,
fetchUpdateProductSettingBaseInfo
} from '@/service/api';
import { useAuthStore } from '@/store/modules/auth';
import { useObjectContextStore } from '@/store/modules/object-context';
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
@@ -46,7 +45,6 @@ import {
defineOptions({ name: 'ProductSetting' });
const authStore = useAuthStore();
const objectContextStore = useObjectContextStore();
const themeStore = useThemeStore();
const { routerPush } = useRouterPush();
@@ -97,9 +95,7 @@ const baseInfo = computed(() => settings.value?.baseInfo || null);
const lifecycle = computed(() => settings.value?.lifecycle || null);
const canManageTeam = computed(() =>
canManageProductTeam({
buttonCodes: objectContextStore.buttonCodes,
loginUserId: authStore.userInfo.userId,
currentManagerUserId: currentManager.value?.userId
buttonCodes: objectContextStore.buttonCodes
})
);
const visibleSectionKeys = computed(() =>

View File

@@ -6,8 +6,6 @@ export interface ProductManagerMemberLike {
interface ProductTeamManageContext {
buttonCodes: readonly string[];
loginUserId: string | null | undefined;
currentManagerUserId: string | null | undefined;
}
interface ProductLifecycleStatusSummary {
@@ -203,13 +201,5 @@ export function getProductLifecycleActionCardMeta(actionCode: Api.Product.Produc
}
export function canManageProductTeam(context: ProductTeamManageContext) {
const hasUpdateAuth = context.buttonCodes.includes('project:product:update');
const loginUserId = String(context.loginUserId || '');
const currentManagerUserId = String(context.currentManagerUserId || '');
if (!hasUpdateAuth || !loginUserId || !currentManagerUserId) {
return false;
}
return loginUserId === currentManagerUserId;
return context.buttonCodes.includes('project:product:update');
}