Files
cn-rdms-web/src/views/workbench/homepage.ts

586 lines
21 KiB
TypeScript
Raw Normal View History

import dayjs from 'dayjs';
export type WorkbenchTodoCategory = 'task' | 'ticket' | 'personal' | 'approval';
refactor(workbench): 重构待办面板功能提升用户体验 - 替换原有时间桶过滤为分类标签页和截止时间筛选器 - 添加优先级排序功能,支持任务类别内按优先级排序 - 重构待办数据结构,新增创建时间和优先级字段 - 移除高优先级标记,统一使用优先级枚举值 - 添加个人事项创建对话框和相关操作功能 - 更新模拟数据以匹配新的数据结构和功能需求 - 优化列表排序逻辑,按创建时间升序排列,无截止时间排最后 - 为各类别待办项添加逾期状态标识和计数统计 - 实现分页加载,每页显示5条待办记录 - 更新样式类名以匹配新的逾期判断逻辑 refactor(project): 优化项目执行模块提升性能和可维护性 - 移除执行项点击切换功能相关的事件和方法 - 删除不再使用的select-execution事件发射器 - 移除执行标签的悬停效果和鼠标指针样式 - 重构任务表格视图,将日期格式化函数名称标准化 - 在跨执行模式下也显示进度列,统一界面布局 - 更新最近更新列宽度并调整日期格式显示 - 将默认页面大小从10增加到20以提高加载效率 feat(list): 统一日期格式化功能简化代码维护 - 将日期时间格式化函数重命名为更准确的date格式化 - 在产品列表和项目列表中统一使用新的日期格式化函数 - 移除秒数显示,仅保留年月日格式提高可读性 refactor(todo): 重构待办事项数据模型和过滤逻辑 - 重新定义待办事项分类类型,移除mention添加personal - 新增主标签、截止时间筛选器和优先级类型定义 - 添加创建时间字段用于排序和显示 - 实现基于分类、截止时间和优先级的过滤函数 - 创建优先级权重映射用于排序算法 - 更新待办项构建函数以支持新的排序逻辑 - 修改逾期判断逻辑以适应新的数据结构 - 移除原有的高优先级字段,统一使用优先级枚举 - 添加优先级排序功能支持升序降序切换 - 重构排序算法,优先按创建时间,其次按截止时间排序 refactor(task): 清理任务模块中已废弃的功能 - 移除通过ID选择执行项的相关函数和事件处理器 - 删除任务卡片和表格中的执行项点击切换功能 - 更新任务工作区组件以移除废弃的事件监听 - 调整任务表格视图中进度条的样式和状态显示 refactor(components): 项目列表中添加进度条可视化组件 - 引入Element Plus进度条组件用于项目进度展示 - 在项目列表中添加进度列并实现进度条渲染 - 配置进度条样式包括内嵌文字、成功状态和边框圆角 - 调整进度列宽度以适应进度条显示需求 refactor(widgets): 整理工作台模块配置和清理冗余组件 - 从工作台模块注册中移除已废弃的myTicket组件 - 更新模块注释说明,明确myTicket已废弃的原因 - 删除不再使用的workbench-my-ticket.vue组件文件 - 更新模块总数注释从16个调整为15个
2026-05-25 14:30:44 +08:00
export type WorkbenchTodoMainTab = 'all' | WorkbenchTodoCategory;
export type WorkbenchTodoDeadlineFilter = 'overdue' | 'today' | 'week' | null;
export type WorkbenchTodoPriority = 'high' | 'mid' | 'low';
export interface WorkbenchTodoItemSource {
id: string;
category: WorkbenchTodoCategory;
title: string;
refactor(workbench): 重构待办面板功能提升用户体验 - 替换原有时间桶过滤为分类标签页和截止时间筛选器 - 添加优先级排序功能,支持任务类别内按优先级排序 - 重构待办数据结构,新增创建时间和优先级字段 - 移除高优先级标记,统一使用优先级枚举值 - 添加个人事项创建对话框和相关操作功能 - 更新模拟数据以匹配新的数据结构和功能需求 - 优化列表排序逻辑,按创建时间升序排列,无截止时间排最后 - 为各类别待办项添加逾期状态标识和计数统计 - 实现分页加载,每页显示5条待办记录 - 更新样式类名以匹配新的逾期判断逻辑 refactor(project): 优化项目执行模块提升性能和可维护性 - 移除执行项点击切换功能相关的事件和方法 - 删除不再使用的select-execution事件发射器 - 移除执行标签的悬停效果和鼠标指针样式 - 重构任务表格视图,将日期格式化函数名称标准化 - 在跨执行模式下也显示进度列,统一界面布局 - 更新最近更新列宽度并调整日期格式显示 - 将默认页面大小从10增加到20以提高加载效率 feat(list): 统一日期格式化功能简化代码维护 - 将日期时间格式化函数重命名为更准确的date格式化 - 在产品列表和项目列表中统一使用新的日期格式化函数 - 移除秒数显示,仅保留年月日格式提高可读性 refactor(todo): 重构待办事项数据模型和过滤逻辑 - 重新定义待办事项分类类型,移除mention添加personal - 新增主标签、截止时间筛选器和优先级类型定义 - 添加创建时间字段用于排序和显示 - 实现基于分类、截止时间和优先级的过滤函数 - 创建优先级权重映射用于排序算法 - 更新待办项构建函数以支持新的排序逻辑 - 修改逾期判断逻辑以适应新的数据结构 - 移除原有的高优先级字段,统一使用优先级枚举 - 添加优先级排序功能支持升序降序切换 - 重构排序算法,优先按创建时间,其次按截止时间排序 refactor(task): 清理任务模块中已废弃的功能 - 移除通过ID选择执行项的相关函数和事件处理器 - 删除任务卡片和表格中的执行项点击切换功能 - 更新任务工作区组件以移除废弃的事件监听 - 调整任务表格视图中进度条的样式和状态显示 refactor(components): 项目列表中添加进度条可视化组件 - 引入Element Plus进度条组件用于项目进度展示 - 在项目列表中添加进度列并实现进度条渲染 - 配置进度条样式包括内嵌文字、成功状态和边框圆角 - 调整进度列宽度以适应进度条显示需求 refactor(widgets): 整理工作台模块配置和清理冗余组件 - 从工作台模块注册中移除已废弃的myTicket组件 - 更新模块注释说明,明确myTicket已废弃的原因 - 删除不再使用的workbench-my-ticket.vue组件文件 - 更新模块总数注释从16个调整为15个
2026-05-25 14:30:44 +08:00
/** 创建时间ISO 字符串。列表默认按这个升序 */
createdTime: string;
/** 截止时间ISO 字符串 */
deadline: string | null;
/** 来源(提交人/项目名/工单号) */
source: string;
/** 进度百分比,仅任务和个人事项使用 */
progressRate?: number | null;
refactor(workbench): 重构待办面板功能提升用户体验 - 替换原有时间桶过滤为分类标签页和截止时间筛选器 - 添加优先级排序功能,支持任务类别内按优先级排序 - 重构待办数据结构,新增创建时间和优先级字段 - 移除高优先级标记,统一使用优先级枚举值 - 添加个人事项创建对话框和相关操作功能 - 更新模拟数据以匹配新的数据结构和功能需求 - 优化列表排序逻辑,按创建时间升序排列,无截止时间排最后 - 为各类别待办项添加逾期状态标识和计数统计 - 实现分页加载,每页显示5条待办记录 - 更新样式类名以匹配新的逾期判断逻辑 refactor(project): 优化项目执行模块提升性能和可维护性 - 移除执行项点击切换功能相关的事件和方法 - 删除不再使用的select-execution事件发射器 - 移除执行标签的悬停效果和鼠标指针样式 - 重构任务表格视图,将日期格式化函数名称标准化 - 在跨执行模式下也显示进度列,统一界面布局 - 更新最近更新列宽度并调整日期格式显示 - 将默认页面大小从10增加到20以提高加载效率 feat(list): 统一日期格式化功能简化代码维护 - 将日期时间格式化函数重命名为更准确的date格式化 - 在产品列表和项目列表中统一使用新的日期格式化函数 - 移除秒数显示,仅保留年月日格式提高可读性 refactor(todo): 重构待办事项数据模型和过滤逻辑 - 重新定义待办事项分类类型,移除mention添加personal - 新增主标签、截止时间筛选器和优先级类型定义 - 添加创建时间字段用于排序和显示 - 实现基于分类、截止时间和优先级的过滤函数 - 创建优先级权重映射用于排序算法 - 更新待办项构建函数以支持新的排序逻辑 - 修改逾期判断逻辑以适应新的数据结构 - 移除原有的高优先级字段,统一使用优先级枚举 - 添加优先级排序功能支持升序降序切换 - 重构排序算法,优先按创建时间,其次按截止时间排序 refactor(task): 清理任务模块中已废弃的功能 - 移除通过ID选择执行项的相关函数和事件处理器 - 删除任务卡片和表格中的执行项点击切换功能 - 更新任务工作区组件以移除废弃的事件监听 - 调整任务表格视图中进度条的样式和状态显示 refactor(components): 项目列表中添加进度条可视化组件 - 引入Element Plus进度条组件用于项目进度展示 - 在项目列表中添加进度列并实现进度条渲染 - 配置进度条样式包括内嵌文字、成功状态和边框圆角 - 调整进度列宽度以适应进度条显示需求 refactor(widgets): 整理工作台模块配置和清理冗余组件 - 从工作台模块注册中移除已废弃的myTicket组件 - 更新模块注释说明,明确myTicket已废弃的原因 - 删除不再使用的workbench-my-ticket.vue组件文件 - 更新模块总数注释从16个调整为15个
2026-05-25 14:30:44 +08:00
/** 优先级,用于前端排序与高亮 */
priority?: WorkbenchTodoPriority;
/** 优先级原始标签(字典 label如 P0~P3有值才渲染角标不做高/中/低翻译 */
priorityLabel?: string | null;
/** 是否逾期 */
overdue?: boolean;
/** 跳转路由 key可选未配则不跳转 */
routeKey?: string;
/** 任务条目携带:所属项目 ID点击带对象上下文跳进该项目的任务导航 */
projectId?: string;
/** 审批业务类型 */
approvalBizType?: 'overtime_application' | string;
/** 审批业务 ID */
approvalBizId?: string;
}
export interface WorkbenchTodoItem extends Omit<WorkbenchTodoItemSource, 'deadline'> {
deadlineLabel: string;
/** 相对截止天数:负数表示已逾期 */
remainingDays: number | null;
categoryLabel: string;
categoryTone: 'sky' | 'emerald' | 'amber' | 'rose' | 'violet';
}
/** 「我参与的项目」展示项(由 Api.Project.MyParticipatedProjectItem 衍生) */
export interface WorkbenchParticipatedProjectView {
id: string;
name: string;
code: string | null;
statusName: string | null;
statusTone: 'sky' | 'emerald' | 'amber';
myRole: string | null;
progress: number;
myTaskCount: number;
myPendingTaskCount: number;
}
const todoCategoryMeta: Record<
WorkbenchTodoCategory,
{ label: string; tone: WorkbenchTodoItem['categoryTone']; icon: string }
> = {
task: { label: '任务', tone: 'emerald', icon: 'mdi:checkbox-marked-circle-outline' },
ticket: { label: '工单', tone: 'amber', icon: 'mdi:ticket-confirmation-outline' },
refactor(workbench): 重构待办面板功能提升用户体验 - 替换原有时间桶过滤为分类标签页和截止时间筛选器 - 添加优先级排序功能,支持任务类别内按优先级排序 - 重构待办数据结构,新增创建时间和优先级字段 - 移除高优先级标记,统一使用优先级枚举值 - 添加个人事项创建对话框和相关操作功能 - 更新模拟数据以匹配新的数据结构和功能需求 - 优化列表排序逻辑,按创建时间升序排列,无截止时间排最后 - 为各类别待办项添加逾期状态标识和计数统计 - 实现分页加载,每页显示5条待办记录 - 更新样式类名以匹配新的逾期判断逻辑 refactor(project): 优化项目执行模块提升性能和可维护性 - 移除执行项点击切换功能相关的事件和方法 - 删除不再使用的select-execution事件发射器 - 移除执行标签的悬停效果和鼠标指针样式 - 重构任务表格视图,将日期格式化函数名称标准化 - 在跨执行模式下也显示进度列,统一界面布局 - 更新最近更新列宽度并调整日期格式显示 - 将默认页面大小从10增加到20以提高加载效率 feat(list): 统一日期格式化功能简化代码维护 - 将日期时间格式化函数重命名为更准确的date格式化 - 在产品列表和项目列表中统一使用新的日期格式化函数 - 移除秒数显示,仅保留年月日格式提高可读性 refactor(todo): 重构待办事项数据模型和过滤逻辑 - 重新定义待办事项分类类型,移除mention添加personal - 新增主标签、截止时间筛选器和优先级类型定义 - 添加创建时间字段用于排序和显示 - 实现基于分类、截止时间和优先级的过滤函数 - 创建优先级权重映射用于排序算法 - 更新待办项构建函数以支持新的排序逻辑 - 修改逾期判断逻辑以适应新的数据结构 - 移除原有的高优先级字段,统一使用优先级枚举 - 添加优先级排序功能支持升序降序切换 - 重构排序算法,优先按创建时间,其次按截止时间排序 refactor(task): 清理任务模块中已废弃的功能 - 移除通过ID选择执行项的相关函数和事件处理器 - 删除任务卡片和表格中的执行项点击切换功能 - 更新任务工作区组件以移除废弃的事件监听 - 调整任务表格视图中进度条的样式和状态显示 refactor(components): 项目列表中添加进度条可视化组件 - 引入Element Plus进度条组件用于项目进度展示 - 在项目列表中添加进度列并实现进度条渲染 - 配置进度条样式包括内嵌文字、成功状态和边框圆角 - 调整进度列宽度以适应进度条显示需求 refactor(widgets): 整理工作台模块配置和清理冗余组件 - 从工作台模块注册中移除已废弃的myTicket组件 - 更新模块注释说明,明确myTicket已废弃的原因 - 删除不再使用的workbench-my-ticket.vue组件文件 - 更新模块总数注释从16个调整为15个
2026-05-25 14:30:44 +08:00
personal: { label: '个人事项', tone: 'violet', icon: 'mdi:notebook-edit-outline' },
approval: { label: '待审批', tone: 'sky', icon: 'mdi:checkbox-multiple-marked-outline' }
refactor(workbench): 重构待办面板功能提升用户体验 - 替换原有时间桶过滤为分类标签页和截止时间筛选器 - 添加优先级排序功能,支持任务类别内按优先级排序 - 重构待办数据结构,新增创建时间和优先级字段 - 移除高优先级标记,统一使用优先级枚举值 - 添加个人事项创建对话框和相关操作功能 - 更新模拟数据以匹配新的数据结构和功能需求 - 优化列表排序逻辑,按创建时间升序排列,无截止时间排最后 - 为各类别待办项添加逾期状态标识和计数统计 - 实现分页加载,每页显示5条待办记录 - 更新样式类名以匹配新的逾期判断逻辑 refactor(project): 优化项目执行模块提升性能和可维护性 - 移除执行项点击切换功能相关的事件和方法 - 删除不再使用的select-execution事件发射器 - 移除执行标签的悬停效果和鼠标指针样式 - 重构任务表格视图,将日期格式化函数名称标准化 - 在跨执行模式下也显示进度列,统一界面布局 - 更新最近更新列宽度并调整日期格式显示 - 将默认页面大小从10增加到20以提高加载效率 feat(list): 统一日期格式化功能简化代码维护 - 将日期时间格式化函数重命名为更准确的date格式化 - 在产品列表和项目列表中统一使用新的日期格式化函数 - 移除秒数显示,仅保留年月日格式提高可读性 refactor(todo): 重构待办事项数据模型和过滤逻辑 - 重新定义待办事项分类类型,移除mention添加personal - 新增主标签、截止时间筛选器和优先级类型定义 - 添加创建时间字段用于排序和显示 - 实现基于分类、截止时间和优先级的过滤函数 - 创建优先级权重映射用于排序算法 - 更新待办项构建函数以支持新的排序逻辑 - 修改逾期判断逻辑以适应新的数据结构 - 移除原有的高优先级字段,统一使用优先级枚举 - 添加优先级排序功能支持升序降序切换 - 重构排序算法,优先按创建时间,其次按截止时间排序 refactor(task): 清理任务模块中已废弃的功能 - 移除通过ID选择执行项的相关函数和事件处理器 - 删除任务卡片和表格中的执行项点击切换功能 - 更新任务工作区组件以移除废弃的事件监听 - 调整任务表格视图中进度条的样式和状态显示 refactor(components): 项目列表中添加进度条可视化组件 - 引入Element Plus进度条组件用于项目进度展示 - 在项目列表中添加进度列并实现进度条渲染 - 配置进度条样式包括内嵌文字、成功状态和边框圆角 - 调整进度列宽度以适应进度条显示需求 refactor(widgets): 整理工作台模块配置和清理冗余组件 - 从工作台模块注册中移除已废弃的myTicket组件 - 更新模块注释说明,明确myTicket已废弃的原因 - 删除不再使用的workbench-my-ticket.vue组件文件 - 更新模块总数注释从16个调整为15个
2026-05-25 14:30:44 +08:00
};
const todoPriorityWeight: Record<WorkbenchTodoPriority, number> = {
high: 0,
mid: 1,
low: 2
};
/** 列表只含进行中项目;按已知状态编码上色,未知回退 sky */
function resolveParticipatedProjectTone(statusCode: string): 'sky' | 'emerald' | 'amber' {
if (statusCode === 'active') return 'emerald';
if (statusCode === 'paused') return 'amber';
return 'sky';
}
function clampPercent(value: number) {
if (!Number.isFinite(value)) return 0;
return Math.min(100, Math.max(0, Math.round(value)));
}
function formatDeadline(value: string | null) {
if (!value) return '不限';
const target = dayjs(value);
if (!target.isValid()) return '不限';
const now = dayjs().startOf('day');
const start = target.startOf('day');
const days = start.diff(now, 'day');
if (days === 0) return `今日 ${target.format('HH:mm')}`;
if (days === 1) return `明日 ${target.format('HH:mm')}`;
if (days === -1) return `昨日 ${target.format('HH:mm')}`;
if (days > 0 && days <= 7) return `${days} 天后 ${target.format('MM-DD')}`;
if (days < 0) return `逾期 ${Math.abs(days)}`;
return target.format('YYYY-MM-DD');
}
function getRemainingDays(value: string | null) {
if (!value) return null;
const target = dayjs(value);
if (!target.isValid()) return null;
return target.startOf('day').diff(dayjs().startOf('day'), 'day');
}
export function buildWorkbenchTodoItems(source: readonly WorkbenchTodoItemSource[]): WorkbenchTodoItem[] {
return [...source]
.sort((left, right) => {
refactor(workbench): 重构待办面板功能提升用户体验 - 替换原有时间桶过滤为分类标签页和截止时间筛选器 - 添加优先级排序功能,支持任务类别内按优先级排序 - 重构待办数据结构,新增创建时间和优先级字段 - 移除高优先级标记,统一使用优先级枚举值 - 添加个人事项创建对话框和相关操作功能 - 更新模拟数据以匹配新的数据结构和功能需求 - 优化列表排序逻辑,按创建时间升序排列,无截止时间排最后 - 为各类别待办项添加逾期状态标识和计数统计 - 实现分页加载,每页显示5条待办记录 - 更新样式类名以匹配新的逾期判断逻辑 refactor(project): 优化项目执行模块提升性能和可维护性 - 移除执行项点击切换功能相关的事件和方法 - 删除不再使用的select-execution事件发射器 - 移除执行标签的悬停效果和鼠标指针样式 - 重构任务表格视图,将日期格式化函数名称标准化 - 在跨执行模式下也显示进度列,统一界面布局 - 更新最近更新列宽度并调整日期格式显示 - 将默认页面大小从10增加到20以提高加载效率 feat(list): 统一日期格式化功能简化代码维护 - 将日期时间格式化函数重命名为更准确的date格式化 - 在产品列表和项目列表中统一使用新的日期格式化函数 - 移除秒数显示,仅保留年月日格式提高可读性 refactor(todo): 重构待办事项数据模型和过滤逻辑 - 重新定义待办事项分类类型,移除mention添加personal - 新增主标签、截止时间筛选器和优先级类型定义 - 添加创建时间字段用于排序和显示 - 实现基于分类、截止时间和优先级的过滤函数 - 创建优先级权重映射用于排序算法 - 更新待办项构建函数以支持新的排序逻辑 - 修改逾期判断逻辑以适应新的数据结构 - 移除原有的高优先级字段,统一使用优先级枚举 - 添加优先级排序功能支持升序降序切换 - 重构排序算法,优先按创建时间,其次按截止时间排序 refactor(task): 清理任务模块中已废弃的功能 - 移除通过ID选择执行项的相关函数和事件处理器 - 删除任务卡片和表格中的执行项点击切换功能 - 更新任务工作区组件以移除废弃的事件监听 - 调整任务表格视图中进度条的样式和状态显示 refactor(components): 项目列表中添加进度条可视化组件 - 引入Element Plus进度条组件用于项目进度展示 - 在项目列表中添加进度列并实现进度条渲染 - 配置进度条样式包括内嵌文字、成功状态和边框圆角 - 调整进度列宽度以适应进度条显示需求 refactor(widgets): 整理工作台模块配置和清理冗余组件 - 从工作台模块注册中移除已废弃的myTicket组件 - 更新模块注释说明,明确myTicket已废弃的原因 - 删除不再使用的workbench-my-ticket.vue组件文件 - 更新模块总数注释从16个调整为15个
2026-05-25 14:30:44 +08:00
const leftCreated = dayjs(left.createdTime).valueOf();
const rightCreated = dayjs(right.createdTime).valueOf();
if (leftCreated !== rightCreated) return leftCreated - rightCreated;
const leftDeadline = left.deadline ? dayjs(left.deadline).valueOf() : Number.POSITIVE_INFINITY;
const rightDeadline = right.deadline ? dayjs(right.deadline).valueOf() : Number.POSITIVE_INFINITY;
return leftDeadline - rightDeadline;
})
.map(item => {
const meta = todoCategoryMeta[item.category];
return {
...item,
deadlineLabel: formatDeadline(item.deadline),
remainingDays: getRemainingDays(item.deadline),
categoryLabel: meta.label,
categoryTone: meta.tone
} satisfies WorkbenchTodoItem;
});
}
refactor(workbench): 重构待办面板功能提升用户体验 - 替换原有时间桶过滤为分类标签页和截止时间筛选器 - 添加优先级排序功能,支持任务类别内按优先级排序 - 重构待办数据结构,新增创建时间和优先级字段 - 移除高优先级标记,统一使用优先级枚举值 - 添加个人事项创建对话框和相关操作功能 - 更新模拟数据以匹配新的数据结构和功能需求 - 优化列表排序逻辑,按创建时间升序排列,无截止时间排最后 - 为各类别待办项添加逾期状态标识和计数统计 - 实现分页加载,每页显示5条待办记录 - 更新样式类名以匹配新的逾期判断逻辑 refactor(project): 优化项目执行模块提升性能和可维护性 - 移除执行项点击切换功能相关的事件和方法 - 删除不再使用的select-execution事件发射器 - 移除执行标签的悬停效果和鼠标指针样式 - 重构任务表格视图,将日期格式化函数名称标准化 - 在跨执行模式下也显示进度列,统一界面布局 - 更新最近更新列宽度并调整日期格式显示 - 将默认页面大小从10增加到20以提高加载效率 feat(list): 统一日期格式化功能简化代码维护 - 将日期时间格式化函数重命名为更准确的date格式化 - 在产品列表和项目列表中统一使用新的日期格式化函数 - 移除秒数显示,仅保留年月日格式提高可读性 refactor(todo): 重构待办事项数据模型和过滤逻辑 - 重新定义待办事项分类类型,移除mention添加personal - 新增主标签、截止时间筛选器和优先级类型定义 - 添加创建时间字段用于排序和显示 - 实现基于分类、截止时间和优先级的过滤函数 - 创建优先级权重映射用于排序算法 - 更新待办项构建函数以支持新的排序逻辑 - 修改逾期判断逻辑以适应新的数据结构 - 移除原有的高优先级字段,统一使用优先级枚举 - 添加优先级排序功能支持升序降序切换 - 重构排序算法,优先按创建时间,其次按截止时间排序 refactor(task): 清理任务模块中已废弃的功能 - 移除通过ID选择执行项的相关函数和事件处理器 - 删除任务卡片和表格中的执行项点击切换功能 - 更新任务工作区组件以移除废弃的事件监听 - 调整任务表格视图中进度条的样式和状态显示 refactor(components): 项目列表中添加进度条可视化组件 - 引入Element Plus进度条组件用于项目进度展示 - 在项目列表中添加进度列并实现进度条渲染 - 配置进度条样式包括内嵌文字、成功状态和边框圆角 - 调整进度列宽度以适应进度条显示需求 refactor(widgets): 整理工作台模块配置和清理冗余组件 - 从工作台模块注册中移除已废弃的myTicket组件 - 更新模块注释说明,明确myTicket已废弃的原因 - 删除不再使用的workbench-my-ticket.vue组件文件 - 更新模块总数注释从16个调整为15个
2026-05-25 14:30:44 +08:00
export function filterWorkbenchTodoItemsByCategory(items: readonly WorkbenchTodoItem[], tab: WorkbenchTodoMainTab) {
if (tab === 'all') return [...items];
return items.filter(item => item.category === tab);
}
export function filterWorkbenchTodoItemsByDeadline(
items: readonly WorkbenchTodoItem[],
filter: WorkbenchTodoDeadlineFilter
) {
if (!filter) return [...items];
if (filter === 'overdue') return items.filter(item => isOverdue(item));
if (filter === 'today') return items.filter(item => item.remainingDays === 0);
// filter === 'week':含今日,含逾期一起暴露给用户处理
return items.filter(item => item.remainingDays !== null && item.remainingDays <= 7);
}
export function isWorkbenchTodoOverdue(item: WorkbenchTodoItem) {
return isOverdue(item);
}
function isOverdue(item: WorkbenchTodoItem) {
if (item.overdue) return true;
return item.remainingDays !== null && item.remainingDays < 0;
}
export function sortWorkbenchTodoItemsByPriority(
items: readonly WorkbenchTodoItem[],
direction: 'asc' | 'desc' = 'desc'
) {
const factor = direction === 'desc' ? 1 : -1;
return [...items].sort((left, right) => {
const leftWeight = todoPriorityWeight[left.priority ?? 'low'];
const rightWeight = todoPriorityWeight[right.priority ?? 'low'];
if (leftWeight !== rightWeight) return (leftWeight - rightWeight) * factor;
return dayjs(left.createdTime).valueOf() - dayjs(right.createdTime).valueOf();
});
}
export function buildWorkbenchParticipatedProjects(
source: readonly Api.Project.MyParticipatedProjectItem[]
): WorkbenchParticipatedProjectView[] {
return source.map(item => ({
id: item.id,
name: item.name,
code: item.code,
statusName: item.statusName,
statusTone: resolveParticipatedProjectTone(item.statusCode),
myRole: item.myRole,
progress: clampPercent(item.progress),
myTaskCount: item.myTaskCount,
myPendingTaskCount: item.myPendingTaskCount
}));
}
/** 「我负责的项目」成员负载展示项 */
export interface WorkbenchOwnedProjectMemberView {
userId: string;
userName: string | null;
/** 该成员在本项目下进行中任务数 */
activeTaskCount: number;
}
/** 「我负责的项目」展示项(由 Api.Project.MyOwnedProjectItem 衍生) */
export interface WorkbenchOwnedProjectView {
id: string;
name: string;
code: string | null;
progress: number;
myRole: string | null;
executionCount: number;
taskCount: number;
overdueCount: number;
memberCount: number;
/** 计划结束日期 YYYY-MM-DD可空 */
plannedEndDate: string | null;
/** 距计划结束剩余天数(负=已逾期plannedEndDate 为空时 null */
remainingDays: number | null;
members: WorkbenchOwnedProjectMemberView[];
}
export function buildWorkbenchOwnedProjects(
source: readonly Api.Project.MyOwnedProjectItem[]
): WorkbenchOwnedProjectView[] {
return source.map(item => ({
id: item.id,
name: item.name,
code: item.code,
progress: clampPercent(item.progress),
myRole: item.myRole,
executionCount: item.executionCount,
taskCount: item.taskCount,
overdueCount: item.overdueCount,
memberCount: item.memberCount,
plannedEndDate: item.plannedEndDate,
remainingDays: getRemainingDays(item.plannedEndDate),
members: item.members.map(member => ({
userId: member.userId,
userName: member.userName,
activeTaskCount: member.activeTaskCount
}))
}));
}
/** 工时分布行项:项目 / 个人事项 / 其他(杂项) */
export interface WorkbenchWorklogDistributionItem {
/** 唯一 keyproject 用 projectIdpersonal/other 用固定字面量 */
key: string;
label: string;
hours: number;
kind: 'project' | 'personal' | 'other';
/** kind === 'project' 时携带,用于跳转项目对象上下文 */
projectId?: string;
}
/** 周标准工时:系统无配置项,后端不返回,产品确认前端落常量 35不是 40 */
export const WORKBENCH_WEEK_TARGET_HOURS = 35;
/** 单周工时视图builder 衍生;逐日工时为后端按填报日期段均摊到工作日的推算值) */
export interface WorkbenchWeekWorklogView {
weekStart: string;
weekLabel: string;
/** 周一~周五逐日工时5 长度,均摊推算值,周末份额归周五) */
dailyHours: number[];
/** 本周累计 */
totalHours: number;
target: number;
/** 累计 - 目标;正=领先、负=落后 */
delta: number;
/** 达成率0-100 整数 */
completionRate: number;
distribution: WorkbenchWorklogDistributionItem[];
}
const WEEKDAY_COUNT = 5;
function roundHours(value: number) {
return Math.round(value * 10) / 10;
}
/** 契约分布项 → 展示行项key/label 归一kind != project 时用固定字面量) */
function toWorklogDistributionItem(item: Api.Project.WorklogDistributionItem): WorkbenchWorklogDistributionItem {
const isProject = item.kind === 'project' && Boolean(item.projectId);
return {
key: isProject ? (item.projectId as string) : item.kind,
label: item.projectName ?? (item.kind === 'personal' ? '个人事项' : '其他'),
hours: roundHours(item.hours),
kind: item.kind,
projectId: isProject ? (item.projectId as string) : undefined
};
}
export function buildWorkbenchWeekWorklogView(source: Api.Project.MyWorklogWeekResult): WorkbenchWeekWorklogView {
const start = dayjs(source.weekStart);
const weekLabel = start.isValid() ? `${start.isoWeekYear()}年第${start.isoWeek()}` : source.weekStart;
const dailyHours = Array.from({ length: WEEKDAY_COUNT }, (_, i) => roundHours(source.dailyHours[i] ?? 0));
const totalHours = roundHours(dailyHours.reduce((s, h) => s + h, 0));
const target = WORKBENCH_WEEK_TARGET_HOURS;
const delta = roundHours(totalHours - target);
const completionRate = target > 0 ? clampPercent((totalHours / target) * 100) : 0;
return {
weekStart: source.weekStart,
weekLabel,
dailyHours,
totalHours,
target,
delta,
completionRate,
distribution: source.distribution.map(toWorklogDistributionItem)
};
}
export function getGreeting(hour: number = dayjs().hour()) {
if (hour < 6) return '凌晨好';
if (hour < 11) return '早上好';
if (hour < 13) return '中午好';
if (hour < 18) return '下午好';
if (hour < 22) return '晚上好';
return '夜深了';
}
// === 团队工时分布D16 团队 tab原 C12 teamWorklog ===
export type WorkbenchTeamWorklogItemKind = 'project' | 'personal' | 'other';
/** 团队工时分布视图builder 衍生) */
export interface WorkbenchTeamWorklogView {
weekStart: string;
weekLabel: string;
members: Array<{
memberId: string;
memberName: string;
totalHours: number;
}>;
/** 项目/个人/其他 列(保序,去重) */
categories: Array<{ key: string; label: string; kind: WorkbenchTeamWorklogItemKind }>;
/** 每个 category 一个 seriesdata[i] = 第 i 个成员该 category 的工时(缺则 0 */
seriesMatrix: Array<{ key: string; label: string; kind: WorkbenchTeamWorklogItemKind; data: number[] }>;
/** 团队总工时 */
totalHours: number;
/** 团队人均工时 */
averageHours: number;
/** 应填工时合计(人数 × WORKBENCH_WEEK_TARGET_HOURS */
expectedTotalHours: number;
/** 填报率 0-100 整数(= 总工时 / 应填工时) */
fillRate: number;
/** 偏低人数:< 团队均值 × 0.8 */
lowCount: number;
/** 加班人数:> 周标准 × 1.125 */
highCount: number;
/** 加班判定阈值小时KPI 文案展示用 */
overtimeThreshold: number;
/** 工时最低成员(用于底部小结) */
lowest: { memberName: string; hours: number } | null;
/** 工时最高成员(用于底部小结) */
highest: { memberName: string; hours: number } | null;
}
export function buildWorkbenchTeamWorklogView(source: Api.Project.TeamWorklogWeekResult): WorkbenchTeamWorklogView {
const start = dayjs(source.weekStart);
const weekLabel = start.isValid() ? `${start.isoWeekYear()}年第${start.isoWeek()}` : source.weekStart;
// 契约members[0] 恒为当前用户,展示加「(我)」后缀
const members = source.members.map((member, index) => ({
memberId: member.userId,
memberName: index === 0 ? `${member.userNickname}(我)` : member.userNickname,
items: member.items.map(toWorklogDistributionItem)
}));
// 列保序去重:按成员遍历顺序首次出现即入列;项目优先、个人/其他按出现顺序追加
const categoryMap = new Map<string, { key: string; label: string; kind: WorkbenchTeamWorklogItemKind }>();
for (const m of members) {
for (const it of m.items) {
if (!categoryMap.has(it.key)) {
categoryMap.set(it.key, { key: it.key, label: it.label, kind: it.kind });
}
}
}
const categories = Array.from(categoryMap.values());
const memberView = members.map(m => ({
memberId: m.memberId,
memberName: m.memberName,
totalHours: roundHours(m.items.reduce((s, it) => s + it.hours, 0))
}));
const seriesMatrix = categories.map(cat => ({
...cat,
data: members.map(m => {
const hit = m.items.find(it => it.key === cat.key);
return hit ? roundHours(hit.hours) : 0;
})
}));
const totalHours = roundHours(memberView.reduce((s, m) => s + m.totalHours, 0));
const memberCount = memberView.length;
const averageHours = memberCount > 0 ? roundHours(totalHours / memberCount) : 0;
const expectedTotalHours = memberCount * WORKBENCH_WEEK_TARGET_HOURS;
const fillRate = expectedTotalHours > 0 ? clampPercent((totalHours / expectedTotalHours) * 100) : 0;
const lowThreshold = averageHours * 0.8;
const highThreshold = WORKBENCH_WEEK_TARGET_HOURS * 1.125;
const lowCount = memberView.filter(m => m.totalHours < lowThreshold).length;
const highCount = memberView.filter(m => m.totalHours > highThreshold).length;
let lowest: { memberName: string; hours: number } | null = null;
let highest: { memberName: string; hours: number } | null = null;
for (const m of memberView) {
if (!lowest || m.totalHours < lowest.hours) lowest = { memberName: m.memberName, hours: m.totalHours };
if (!highest || m.totalHours > highest.hours) highest = { memberName: m.memberName, hours: m.totalHours };
}
return {
weekStart: source.weekStart,
weekLabel,
members: memberView,
categories,
seriesMatrix,
totalHours,
averageHours,
expectedTotalHours,
fillRate,
lowCount,
highCount,
overtimeThreshold: roundHours(highThreshold),
lowest,
highest
};
}
// === 团队负载C13 · teamLoad ===
export type WorkbenchTeamLoadLevel = 'high' | 'mid' | 'normal';
export type WorkbenchTeamLoadItemKind = 'project' | 'personal' | 'other';
export interface WorkbenchTeamLoadItemSource {
/** projectId 或 'personal' / 'other' */
key: string;
label: string;
kind: WorkbenchTeamLoadItemKind;
/** 该项目/个人事项下,该成员未完成的任务/事项数(含待开始/已暂停;任务按"负责人/协办人"口径,单任务多人各算 1 条) */
count: number;
}
export interface WorkbenchTeamLoadMemberSource {
memberId: string;
memberName: string;
/** 未完成数按项目 + 个人事项的拆分(合计即"未完成总数" */
items: WorkbenchTeamLoadItemSource[];
/** 今天 ≤ 计划结束 ≤ 今天+3 天 且未完成(与逾期互斥) */
dueSoon: number;
/** 计划结束 < 今天 且未完成 */
overdue: number;
}
export interface WorkbenchTeamLoadSource {
members: WorkbenchTeamLoadMemberSource[];
}
export interface WorkbenchTeamLoadSegment extends WorkbenchTeamLoadItemSource {
/** 段宽占"柱子内部"的百分比(段总和 = 100% */
widthPercent: number;
}
export interface WorkbenchTeamLoadMember extends Omit<WorkbenchTeamLoadMemberSource, 'items'> {
/** items 合计(未完成总数) */
inProgress: number;
/** dueSoon + overdue */
urgent: number;
level: WorkbenchTeamLoadLevel;
/** 项目段(去掉 count = 0 */
segments: WorkbenchTeamLoadSegment[];
/** 柱子总长占容器百分比0-100溢出时 = 100 */
barWidthPercent: number;
/** 溢出数量inProgress - scaleMax不溢出 = 0柱子末端显示 "+N" */
overflowExtra: number;
}
export interface WorkbenchTeamLoadView {
/** 已按 level → inProgress desc 排序,高负载置顶 */
members: WorkbenchTeamLoadMember[];
/** 柱子量程上限(固定值,避免某个成员极端值把全员压扁) */
scaleMax: number;
highCount: number;
midCount: number;
/** 临期+逾期总条数(团队聚合) */
urgentTotal: number;
}
const TEAM_LOAD_INPROGRESS_HIGH = 6;
const TEAM_LOAD_INPROGRESS_MID = 4;
const TEAM_LOAD_URGENT_HIGH = 2;
const TEAM_LOAD_URGENT_MID = 1;
/** 柱子量程上限:固定 10确保高负载阈值 6 在 60% 位置可辨;超出走 "+N" 文字 */
const TEAM_LOAD_SCALE_MAX = 10;
function resolveTeamLoadLevel(inProgress: number, urgent: number): WorkbenchTeamLoadLevel {
if (inProgress >= TEAM_LOAD_INPROGRESS_HIGH || urgent >= TEAM_LOAD_URGENT_HIGH) return 'high';
if (inProgress >= TEAM_LOAD_INPROGRESS_MID || urgent >= TEAM_LOAD_URGENT_MID) return 'mid';
return 'normal';
}
const LEVEL_RANK: Record<WorkbenchTeamLoadLevel, number> = { high: 0, mid: 1, normal: 2 };
export function buildWorkbenchTeamLoadView(source: WorkbenchTeamLoadSource): WorkbenchTeamLoadView {
const scaleMax = TEAM_LOAD_SCALE_MAX;
const enriched: WorkbenchTeamLoadMember[] = source.members.map(m => {
const inProgress = m.items.reduce((s, it) => s + it.count, 0);
const urgent = m.dueSoon + m.overdue;
const barWidthPercent = scaleMax > 0 ? Math.min(100, (inProgress / scaleMax) * 100) : 0;
const overflowExtra = Math.max(0, inProgress - scaleMax);
// 段宽永远相对柱子内部按 count/inProgress 算(段总和 = 100%
// 柱子总长靠 barWidthPercent 控制;不溢出时柱子未顶满,溢出时柱子顶满 + 末端 +N
const segments: WorkbenchTeamLoadSegment[] = m.items
.filter(it => it.count > 0)
.map(it => ({
...it,
widthPercent: inProgress > 0 ? (it.count / inProgress) * 100 : 0
}));
return {
memberId: m.memberId,
memberName: m.memberName,
dueSoon: m.dueSoon,
overdue: m.overdue,
inProgress,
urgent,
level: resolveTeamLoadLevel(inProgress, urgent),
segments,
barWidthPercent,
overflowExtra
};
});
enriched.sort((a, b) => {
if (LEVEL_RANK[a.level] !== LEVEL_RANK[b.level]) return LEVEL_RANK[a.level] - LEVEL_RANK[b.level];
if (a.inProgress !== b.inProgress) return b.inProgress - a.inProgress;
return b.urgent - a.urgent;
});
const highCount = enriched.filter(m => m.level === 'high').length;
const midCount = enriched.filter(m => m.level === 'mid').length;
const urgentTotal = enriched.reduce((s, m) => s + m.urgent, 0);
return {
members: enriched,
scaleMax,
highCount,
midCount,
urgentTotal
};
}
export type WorkbenchHealthLevel = 'green' | 'yellow' | 'red';
export interface WorkbenchProjectHealthCardSource {
projectId: string;
projectName: string;
code: string;
health: WorkbenchHealthLevel;
riskCount: number;
overdueTasks: number;
backlogRequirements: number;
}
export interface WorkbenchProjectHealthCard extends WorkbenchProjectHealthCardSource {
healthLabel: string;
}
export function buildWorkbenchProjectHealthCards(
source: readonly WorkbenchProjectHealthCardSource[]
): WorkbenchProjectHealthCard[] {
const labelMap: Record<WorkbenchHealthLevel, string> = { green: '健康', yellow: '关注', red: '风险' };
return source.map(s => ({ ...s, healthLabel: labelMap[s.health] }));
}
/**
* / / 100
*
*/
export function buildWorkbenchMyExecutionItems<T extends { statusCode: string; progressRate: number }>(
source: readonly T[]
): T[] {
return source.filter(item => {
if (item.statusCode === 'completed' || item.statusCode === 'cancelled') return false;
if (item.progressRate >= 100) return false;
return true;
});
}