feat(projects): 工作台接口切换为真实数据
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export type WorkbenchTrend = 'up' | 'down' | 'flat';
|
||||
|
||||
export type WorkbenchTodoCategory = 'task' | 'ticket' | 'personal' | 'approval';
|
||||
|
||||
export type WorkbenchTodoMainTab = 'all' | WorkbenchTodoCategory;
|
||||
@@ -10,40 +8,6 @@ export type WorkbenchTodoDeadlineFilter = 'overdue' | 'today' | 'week' | null;
|
||||
|
||||
export type WorkbenchTodoPriority = 'high' | 'mid' | 'low';
|
||||
|
||||
export interface WorkbenchKpiSource {
|
||||
/** 待办 */
|
||||
todo: {
|
||||
count: number;
|
||||
diffFromYesterday: number;
|
||||
};
|
||||
/** 我负责的任务 */
|
||||
task: {
|
||||
count: number;
|
||||
diffFromYesterday: number;
|
||||
};
|
||||
/** 我参与的项目 */
|
||||
project: {
|
||||
count: number;
|
||||
activeCount: number;
|
||||
};
|
||||
/** 我负责的需求 */
|
||||
requirement: {
|
||||
count: number;
|
||||
pendingReview: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WorkbenchKpiCard {
|
||||
key: 'todo' | 'task' | 'project' | 'requirement';
|
||||
label: string;
|
||||
value: number;
|
||||
trend: WorkbenchTrend;
|
||||
trendText: string;
|
||||
hint: string;
|
||||
icon: string;
|
||||
tone: 'sky' | 'emerald' | 'amber' | 'rose';
|
||||
}
|
||||
|
||||
export interface WorkbenchTodoItemSource {
|
||||
id: string;
|
||||
category: WorkbenchTodoCategory;
|
||||
@@ -54,12 +18,18 @@ export interface WorkbenchTodoItemSource {
|
||||
deadline: string | null;
|
||||
/** 来源(提交人/项目名/工单号) */
|
||||
source: string;
|
||||
/** 进度百分比,仅任务和个人事项使用 */
|
||||
progressRate?: number | null;
|
||||
/** 优先级,用于前端排序与高亮 */
|
||||
priority?: WorkbenchTodoPriority;
|
||||
/** 优先级原始标签(字典 label,如 P0~P3),有值才渲染角标;不做高/中/低翻译 */
|
||||
priorityLabel?: string | null;
|
||||
/** 是否逾期 */
|
||||
overdue?: boolean;
|
||||
/** 跳转路由 key(可选,未配则不跳转) */
|
||||
routeKey?: string;
|
||||
/** 任务条目携带:所属项目 ID,点击带对象上下文跳进该项目的任务导航 */
|
||||
projectId?: string;
|
||||
/** 审批业务类型 */
|
||||
approvalBizType?: 'overtime_application' | string;
|
||||
/** 审批业务 ID */
|
||||
@@ -74,26 +44,6 @@ export interface WorkbenchTodoItem extends Omit<WorkbenchTodoItemSource, 'deadli
|
||||
categoryTone: 'sky' | 'emerald' | 'amber' | 'rose' | 'violet';
|
||||
}
|
||||
|
||||
export interface WorkbenchActivityItemSource {
|
||||
id: string;
|
||||
actor: string;
|
||||
action: string;
|
||||
/** 目标对象(需求/任务/工单/项目名等) */
|
||||
target: string;
|
||||
/** 目标对象的种类,用于颜色 */
|
||||
targetKind: 'requirement' | 'task' | 'ticket' | 'project' | 'product';
|
||||
/** 时间,ISO */
|
||||
time: string;
|
||||
/** 是否 @ 了当前用户 */
|
||||
mentioned?: boolean;
|
||||
}
|
||||
|
||||
export interface WorkbenchActivityItem extends Omit<WorkbenchActivityItemSource, 'time'> {
|
||||
timeLabel: string;
|
||||
relativeLabel: string;
|
||||
tone: 'sky' | 'emerald' | 'amber' | 'rose' | 'violet';
|
||||
}
|
||||
|
||||
/** 「我参与的项目」展示项(由 Api.Project.MyParticipatedProjectItem 衍生) */
|
||||
export interface WorkbenchParticipatedProjectView {
|
||||
id: string;
|
||||
@@ -123,14 +73,6 @@ const todoPriorityWeight: Record<WorkbenchTodoPriority, number> = {
|
||||
low: 2
|
||||
};
|
||||
|
||||
const activityToneMap: Record<WorkbenchActivityItemSource['targetKind'], WorkbenchActivityItem['tone']> = {
|
||||
requirement: 'sky',
|
||||
task: 'emerald',
|
||||
ticket: 'amber',
|
||||
project: 'violet',
|
||||
product: 'rose'
|
||||
};
|
||||
|
||||
/** 列表只含进行中项目;按已知状态编码上色,未知回退 sky */
|
||||
function resolveParticipatedProjectTone(statusCode: string): 'sky' | 'emerald' | 'amber' {
|
||||
if (statusCode === 'active') return 'emerald';
|
||||
@@ -143,25 +85,6 @@ function clampPercent(value: number) {
|
||||
return Math.min(100, Math.max(0, Math.round(value)));
|
||||
}
|
||||
|
||||
function formatRelative(value: string) {
|
||||
const target = dayjs(value);
|
||||
if (!target.isValid()) return '--';
|
||||
|
||||
const now = dayjs();
|
||||
const diffMinutes = now.diff(target, 'minute');
|
||||
|
||||
if (diffMinutes < 1) return '刚刚';
|
||||
if (diffMinutes < 60) return `${diffMinutes} 分钟前`;
|
||||
|
||||
const diffHours = now.diff(target, 'hour');
|
||||
if (diffHours < 24) return `${diffHours} 小时前`;
|
||||
|
||||
const diffDays = now.diff(target, 'day');
|
||||
if (diffDays < 7) return `${diffDays} 天前`;
|
||||
|
||||
return target.format('MM-DD HH:mm');
|
||||
}
|
||||
|
||||
function formatDeadline(value: string | null) {
|
||||
if (!value) return '不限';
|
||||
const target = dayjs(value);
|
||||
@@ -187,60 +110,6 @@ function getRemainingDays(value: string | null) {
|
||||
return target.startOf('day').diff(dayjs().startOf('day'), 'day');
|
||||
}
|
||||
|
||||
export function buildWorkbenchKpiCards(source: WorkbenchKpiSource): WorkbenchKpiCard[] {
|
||||
function diffTrend(diff: number): { trend: WorkbenchTrend; text: string } {
|
||||
if (diff > 0) return { trend: 'up', text: `较昨日 +${diff}` };
|
||||
if (diff < 0) return { trend: 'down', text: `较昨日 ${diff}` };
|
||||
return { trend: 'flat', text: '与昨日持平' };
|
||||
}
|
||||
|
||||
const todoTrend = diffTrend(source.todo.diffFromYesterday);
|
||||
const taskTrend = diffTrend(source.task.diffFromYesterday);
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'todo',
|
||||
label: '我的待办',
|
||||
value: source.todo.count,
|
||||
trend: todoTrend.trend,
|
||||
trendText: todoTrend.text,
|
||||
hint: '需要我处理的评审、任务、工单与 @ 提醒合计',
|
||||
icon: 'mdi:clipboard-text-clock-outline',
|
||||
tone: 'sky'
|
||||
},
|
||||
{
|
||||
key: 'task',
|
||||
label: '我负责的任务',
|
||||
value: source.task.count,
|
||||
trend: taskTrend.trend,
|
||||
trendText: taskTrend.text,
|
||||
hint: '当前由我负责的进行中任务',
|
||||
icon: 'mdi:checkbox-marked-circle-outline',
|
||||
tone: 'emerald'
|
||||
},
|
||||
{
|
||||
key: 'project',
|
||||
label: '我参与的项目',
|
||||
value: source.project.count,
|
||||
trend: 'flat',
|
||||
trendText: `进行中 ${source.project.activeCount} 个`,
|
||||
hint: '我作为成员参与的项目总数',
|
||||
icon: 'mdi:briefcase-outline',
|
||||
tone: 'amber'
|
||||
},
|
||||
{
|
||||
key: 'requirement',
|
||||
label: '我负责的需求',
|
||||
value: source.requirement.count,
|
||||
trend: 'flat',
|
||||
trendText: `待评审 ${source.requirement.pendingReview} 项`,
|
||||
hint: '由我担任产品经理或负责人的需求',
|
||||
icon: 'mdi:file-document-multiple-outline',
|
||||
tone: 'rose'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export function buildWorkbenchTodoItems(source: readonly WorkbenchTodoItemSource[]): WorkbenchTodoItem[] {
|
||||
return [...source]
|
||||
.sort((left, right) => {
|
||||
@@ -301,18 +170,6 @@ export function sortWorkbenchTodoItemsByPriority(
|
||||
});
|
||||
}
|
||||
|
||||
export function buildWorkbenchActivityItems(source: readonly WorkbenchActivityItemSource[]): WorkbenchActivityItem[] {
|
||||
return [...source]
|
||||
.filter(item => dayjs(item.time).isValid())
|
||||
.sort((left, right) => dayjs(right.time).valueOf() - dayjs(left.time).valueOf())
|
||||
.map(item => ({
|
||||
...item,
|
||||
timeLabel: dayjs(item.time).format('YYYY-MM-DD HH:mm'),
|
||||
relativeLabel: formatRelative(item.time),
|
||||
tone: activityToneMap[item.targetKind]
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildWorkbenchParticipatedProjects(
|
||||
source: readonly Api.Project.MyParticipatedProjectItem[]
|
||||
): WorkbenchParticipatedProjectView[] {
|
||||
@@ -389,37 +246,16 @@ export interface WorkbenchWorklogDistributionItem {
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
/** 单周工时数据源(按天填 / 按周填两类共存) */
|
||||
export interface WorkbenchWeekWorklogSource {
|
||||
/** ISO 周首日(周一),YYYY-MM-DD */
|
||||
weekStart: string;
|
||||
/** 整周一笔填的工时总和(将按工作日 5 等分均摊到周一~周五) */
|
||||
weeklyFilledHours: number;
|
||||
/** 周一~周五按天填的工时,长度恒为 5 */
|
||||
dailyHours: [number, number, number, number, number];
|
||||
/** 工时分布;前端不再做截断,超出 5 行靠容器滚动 */
|
||||
distribution: WorkbenchWorklogDistributionItem[];
|
||||
/** 周目标小时数(默认 40) */
|
||||
target: number;
|
||||
}
|
||||
/** 周标准工时:系统无配置项,后端不返回,产品确认前端落常量 35(不是 40) */
|
||||
export const WORKBENCH_WEEK_TARGET_HOURS = 35;
|
||||
|
||||
/** 双周工时 mock 容器:本周 + 上周 */
|
||||
export interface WorkbenchMyWeekWorklogSource {
|
||||
current: WorkbenchWeekWorklogSource;
|
||||
previous: WorkbenchWeekWorklogSource;
|
||||
}
|
||||
|
||||
/** 单周工时视图(builder 衍生) */
|
||||
/** 单周工时视图(builder 衍生;逐日工时为后端按填报日期段均摊到工作日的推算值) */
|
||||
export interface WorkbenchWeekWorklogView {
|
||||
weekStart: string;
|
||||
weekLabel: string;
|
||||
/** 周一~周五按天填部分(5 长度) */
|
||||
dailyByDay: number[];
|
||||
/** 周一~周五按周均分部分(5 长度,每项 = weeklyFilledHours / 5) */
|
||||
dailyByWeekAvg: number[];
|
||||
/** 周一~周五合计(5 长度,dailyByDay + dailyByWeekAvg) */
|
||||
dailyTotal: number[];
|
||||
/** 累计(含按周) */
|
||||
/** 周一~周五逐日工时(5 长度,均摊推算值,周末份额归周五) */
|
||||
dailyHours: number[];
|
||||
/** 本周累计 */
|
||||
totalHours: number;
|
||||
target: number;
|
||||
/** 累计 - 目标;正=领先、负=落后 */
|
||||
@@ -435,29 +271,37 @@ function roundHours(value: number) {
|
||||
return Math.round(value * 10) / 10;
|
||||
}
|
||||
|
||||
export function buildWorkbenchWeekWorklogView(source: WorkbenchWeekWorklogSource): WorkbenchWeekWorklogView {
|
||||
/** 契约分布项 → 展示行项(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 weekAvg = roundHours(source.weeklyFilledHours / WEEKDAY_COUNT);
|
||||
const dailyByWeekAvg = Array.from({ length: WEEKDAY_COUNT }, () => weekAvg);
|
||||
const dailyByDay = source.dailyHours.map(roundHours);
|
||||
const dailyTotal = dailyByDay.map((h, i) => roundHours(h + dailyByWeekAvg[i]));
|
||||
const totalHours = roundHours(dailyTotal.reduce((s, h) => s + h, 0));
|
||||
const delta = roundHours(totalHours - source.target);
|
||||
const completionRate = source.target > 0 ? clampPercent((totalHours / source.target) * 100) : 0;
|
||||
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,
|
||||
dailyByDay,
|
||||
dailyByWeekAvg,
|
||||
dailyTotal,
|
||||
dailyHours,
|
||||
totalHours,
|
||||
target: source.target,
|
||||
target,
|
||||
delta,
|
||||
completionRate,
|
||||
distribution: source.distribution
|
||||
distribution: source.distribution.map(toWorklogDistributionItem)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -470,31 +314,10 @@ export function getGreeting(hour: number = dayjs().hour()) {
|
||||
return '夜深了';
|
||||
}
|
||||
|
||||
// === 团队工时分布(C12 · teamWorklog) ===
|
||||
// === 团队工时分布(D16 团队 tab,原 C12 teamWorklog) ===
|
||||
|
||||
export type WorkbenchTeamWorklogItemKind = 'project' | 'personal' | 'other';
|
||||
|
||||
export interface WorkbenchTeamWorklogItem {
|
||||
/** 项目 id 或 'personal' / 'other' */
|
||||
key: string;
|
||||
label: string;
|
||||
hours: number;
|
||||
kind: WorkbenchTeamWorklogItemKind;
|
||||
}
|
||||
|
||||
export interface WorkbenchTeamWorklogMemberSource {
|
||||
memberId: string;
|
||||
memberName: string;
|
||||
items: WorkbenchTeamWorklogItem[];
|
||||
}
|
||||
|
||||
export interface WorkbenchTeamWorklogSource {
|
||||
weekStart: string;
|
||||
/** 单人周应填工时(用于填报率口径) */
|
||||
expectedHoursPerMember: number;
|
||||
members: WorkbenchTeamWorklogMemberSource[];
|
||||
}
|
||||
|
||||
/** 团队工时分布视图(builder 衍生) */
|
||||
export interface WorkbenchTeamWorklogView {
|
||||
weekStart: string;
|
||||
@@ -512,27 +335,36 @@ export interface WorkbenchTeamWorklogView {
|
||||
totalHours: number;
|
||||
/** 团队人均工时 */
|
||||
averageHours: number;
|
||||
/** 应填工时合计(人数 × expectedHoursPerMember) */
|
||||
/** 应填工时合计(人数 × WORKBENCH_WEEK_TARGET_HOURS) */
|
||||
expectedTotalHours: number;
|
||||
/** 填报率 0-100 整数(= 总工时 / 应填工时) */
|
||||
fillRate: number;
|
||||
/** 偏低人数:< 团队均值 × 0.8 */
|
||||
lowCount: number;
|
||||
/** 加班人数:> 45h(应填 × 1.125) */
|
||||
/** 加班人数:> 周标准 × 1.125 */
|
||||
highCount: number;
|
||||
/** 加班判定阈值(小时),KPI 文案展示用 */
|
||||
overtimeThreshold: number;
|
||||
/** 工时最低成员(用于底部小结) */
|
||||
lowest: { memberName: string; hours: number } | null;
|
||||
/** 工时最高成员(用于底部小结) */
|
||||
highest: { memberName: string; hours: number } | null;
|
||||
}
|
||||
|
||||
export function buildWorkbenchTeamWorklogView(source: WorkbenchTeamWorklogSource): WorkbenchTeamWorklogView {
|
||||
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 source.members) {
|
||||
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 });
|
||||
@@ -541,7 +373,7 @@ export function buildWorkbenchTeamWorklogView(source: WorkbenchTeamWorklogSource
|
||||
}
|
||||
const categories = Array.from(categoryMap.values());
|
||||
|
||||
const memberView = source.members.map(m => ({
|
||||
const memberView = members.map(m => ({
|
||||
memberId: m.memberId,
|
||||
memberName: m.memberName,
|
||||
totalHours: roundHours(m.items.reduce((s, it) => s + it.hours, 0))
|
||||
@@ -549,7 +381,7 @@ export function buildWorkbenchTeamWorklogView(source: WorkbenchTeamWorklogSource
|
||||
|
||||
const seriesMatrix = categories.map(cat => ({
|
||||
...cat,
|
||||
data: source.members.map(m => {
|
||||
data: members.map(m => {
|
||||
const hit = m.items.find(it => it.key === cat.key);
|
||||
return hit ? roundHours(hit.hours) : 0;
|
||||
})
|
||||
@@ -558,11 +390,11 @@ export function buildWorkbenchTeamWorklogView(source: WorkbenchTeamWorklogSource
|
||||
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 * source.expectedHoursPerMember;
|
||||
const expectedTotalHours = memberCount * WORKBENCH_WEEK_TARGET_HOURS;
|
||||
const fillRate = expectedTotalHours > 0 ? clampPercent((totalHours / expectedTotalHours) * 100) : 0;
|
||||
|
||||
const lowThreshold = averageHours * 0.8;
|
||||
const highThreshold = source.expectedHoursPerMember * 1.125;
|
||||
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;
|
||||
|
||||
@@ -585,6 +417,7 @@ export function buildWorkbenchTeamWorklogView(source: WorkbenchTeamWorklogSource
|
||||
fillRate,
|
||||
lowCount,
|
||||
highCount,
|
||||
overtimeThreshold: roundHours(highThreshold),
|
||||
lowest,
|
||||
highest
|
||||
};
|
||||
@@ -594,31 +427,29 @@ export function buildWorkbenchTeamWorklogView(source: WorkbenchTeamWorklogSource
|
||||
|
||||
export type WorkbenchTeamLoadLevel = 'high' | 'mid' | 'normal';
|
||||
|
||||
export type WorkbenchTeamLoadItemKind = 'project' | 'personal';
|
||||
export type WorkbenchTeamLoadItemKind = 'project' | 'personal' | 'other';
|
||||
|
||||
export interface WorkbenchTeamLoadItemSource {
|
||||
/** projectId 或 'personal' */
|
||||
/** projectId 或 'personal' / 'other' */
|
||||
key: string;
|
||||
label: string;
|
||||
kind: WorkbenchTeamLoadItemKind;
|
||||
/** 该项目/个人事项下,该成员进行中的任务/事项数(任务按"负责人/协办人"口径,单任务多人各算 1 条) */
|
||||
/** 该项目/个人事项下,该成员未完成的任务/事项数(含待开始/已暂停;任务按"负责人/协办人"口径,单任务多人各算 1 条) */
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface WorkbenchTeamLoadMemberSource {
|
||||
memberId: string;
|
||||
memberName: string;
|
||||
/** 进行中按项目 + 个人事项的拆分(合计即"进行中总数") */
|
||||
/** 未完成数按项目 + 个人事项的拆分(合计即"未完成总数") */
|
||||
items: WorkbenchTeamLoadItemSource[];
|
||||
/** 今天 ≤ 计划结束 ≤ 今天+3 天 且未完成 */
|
||||
/** 今天 ≤ 计划结束 ≤ 今天+3 天 且未完成(与逾期互斥) */
|
||||
dueSoon: number;
|
||||
/** 计划结束 < 今天 且未完成 */
|
||||
overdue: number;
|
||||
}
|
||||
|
||||
export interface WorkbenchTeamLoadSource {
|
||||
/** 数据快照所属周(与 C12 对齐口径) */
|
||||
weekStart: string;
|
||||
members: WorkbenchTeamLoadMemberSource[];
|
||||
}
|
||||
|
||||
@@ -628,7 +459,7 @@ export interface WorkbenchTeamLoadSegment extends WorkbenchTeamLoadItemSource {
|
||||
}
|
||||
|
||||
export interface WorkbenchTeamLoadMember extends Omit<WorkbenchTeamLoadMemberSource, 'items'> {
|
||||
/** items 合计 */
|
||||
/** items 合计(未完成总数) */
|
||||
inProgress: number;
|
||||
/** dueSoon + overdue */
|
||||
urgent: number;
|
||||
@@ -642,7 +473,6 @@ export interface WorkbenchTeamLoadMember extends Omit<WorkbenchTeamLoadMemberSou
|
||||
}
|
||||
|
||||
export interface WorkbenchTeamLoadView {
|
||||
weekStart: string;
|
||||
/** 已按 level → inProgress desc 排序,高负载置顶 */
|
||||
members: WorkbenchTeamLoadMember[];
|
||||
/** 柱子量程上限(固定值,避免某个成员极端值把全员压扁) */
|
||||
@@ -709,7 +539,6 @@ export function buildWorkbenchTeamLoadView(source: WorkbenchTeamLoadSource): Wor
|
||||
const urgentTotal = enriched.reduce((s, m) => s + m.urgent, 0);
|
||||
|
||||
return {
|
||||
weekStart: source.weekStart,
|
||||
members: enriched,
|
||||
scaleMax,
|
||||
highCount,
|
||||
@@ -741,19 +570,6 @@ export function buildWorkbenchProjectHealthCards(
|
||||
return source.map(s => ({ ...s, healthLabel: labelMap[s.health] }));
|
||||
}
|
||||
|
||||
export interface WorkbenchProgressBarSource {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
/** 完成率 0-100 */
|
||||
weekCompletionRate: number;
|
||||
}
|
||||
|
||||
export type WorkbenchProgressBar = WorkbenchProgressBarSource;
|
||||
|
||||
export function buildWorkbenchProgressBars(source: readonly WorkbenchProgressBarSource[]): WorkbenchProgressBar[] {
|
||||
return source.map(s => ({ ...s, weekCompletionRate: Math.min(100, Math.max(0, Math.round(s.weekCompletionRate))) }));
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端兜底过滤:剔除已完成 / 已取消 / 进度满 100 的执行(默认不在工作台呈现)。
|
||||
* 后端接口已按此口径过滤,此处为双保险;泛型保证接口返回类型可复用。
|
||||
|
||||
Reference in New Issue
Block a user