fix(工作台-工时、周报、日志管理): 详细如下。
- 移除历史工作日分隔符兼容逻辑,仅使用显式分隔标记切分 - 扩展工时数据显示范围从5个工作日到7天完整周,并添加周末展示控制 - 在团队工时统计中新增加班工时字段并调整图表展示方式 - 移除团队工时分布中的分类矩阵,简化数据结构 - 为API访问日志和错误日志搜索组件添加用户选择功能 - 调整日志搜索界面布局,将用户编号输入改为用户选择下拉框 - 移除用户类型筛选条件以简化搜索界面 - 更新工时图表配置以支持周末数据显示和新的数据结构
This commit is contained in:
@@ -253,12 +253,18 @@ export interface WorkbenchWorklogDistributionItem {
|
||||
/** 周标准工时:系统无配置项,后端不返回,产品确认前端落常量 35(不是 40) */
|
||||
export const WORKBENCH_WEEK_TARGET_HOURS = 35;
|
||||
|
||||
/** 单周工时视图(builder 衍生;逐日工时为后端按填报日期段均摊到工作日的推算值) */
|
||||
/** 单周工时视图(builder 衍生;接口返回周一~周日逐日工时,按周填报时仅均摊到工作日) */
|
||||
export interface WorkbenchWeekWorklogView {
|
||||
weekStart: string;
|
||||
weekLabel: string;
|
||||
/** 周一~周五逐日工时(5 长度,均摊推算值,周末份额归周五) */
|
||||
/** 周一~周日逐日工时(7 长度;周末单天工时保留在周末当天) */
|
||||
dailyHours: number[];
|
||||
/** 是否展示周末两列;仅当周六或周日任一天 > 0 时展示 */
|
||||
showWeekend: boolean;
|
||||
/** 图表/逐日展示实际使用的横轴标签 */
|
||||
visibleDayLabels: string[];
|
||||
/** 图表/逐日展示实际使用的逐日工时数据 */
|
||||
visibleDailyHours: number[];
|
||||
/** 本周累计 */
|
||||
totalHours: number;
|
||||
target: number;
|
||||
@@ -269,7 +275,10 @@ export interface WorkbenchWeekWorklogView {
|
||||
distribution: WorkbenchWorklogDistributionItem[];
|
||||
}
|
||||
|
||||
const WEEKDAY_COUNT = 5;
|
||||
const WORKDAY_COUNT = 5;
|
||||
const FULL_WEEKDAY_COUNT = 7;
|
||||
const WORKDAY_LABELS = ['一', '二', '三', '四', '五'] as const;
|
||||
const FULL_WEEKDAY_LABELS = ['一', '二', '三', '四', '五', '六', '日'] as const;
|
||||
|
||||
function roundHours(value: number) {
|
||||
return Math.round(value * 10) / 10;
|
||||
@@ -291,7 +300,12 @@ export function buildWorkbenchWeekWorklogView(source: Api.Project.MyWorklogWeekR
|
||||
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 dailyHours = Array.from({ length: FULL_WEEKDAY_COUNT }, (_, i) => roundHours(source.dailyHours[i] ?? 0));
|
||||
const sat = Number(dailyHours[5] ?? 0);
|
||||
const sun = Number(dailyHours[6] ?? 0);
|
||||
const showWeekend = sat > 0 || sun > 0;
|
||||
const visibleDayLabels = showWeekend ? [...FULL_WEEKDAY_LABELS] : [...WORKDAY_LABELS];
|
||||
const visibleDailyHours = showWeekend ? [...dailyHours] : dailyHours.slice(0, WORKDAY_COUNT);
|
||||
const totalHours = roundHours(dailyHours.reduce((s, h) => s + h, 0));
|
||||
const target = WORKBENCH_WEEK_TARGET_HOURS;
|
||||
const delta = roundHours(totalHours - target);
|
||||
@@ -301,6 +315,9 @@ export function buildWorkbenchWeekWorklogView(source: Api.Project.MyWorklogWeekR
|
||||
weekStart: source.weekStart,
|
||||
weekLabel,
|
||||
dailyHours,
|
||||
showWeekend,
|
||||
visibleDayLabels,
|
||||
visibleDailyHours,
|
||||
totalHours,
|
||||
target,
|
||||
delta,
|
||||
@@ -320,8 +337,6 @@ export function getGreeting(hour: number = dayjs().hour()) {
|
||||
|
||||
// === 团队工时分布(D16 团队 tab,原 C12 teamWorklog) ===
|
||||
|
||||
export type WorkbenchTeamWorklogItemKind = 'project' | 'personal' | 'other';
|
||||
|
||||
/** 团队工时分布视图(builder 衍生) */
|
||||
export interface WorkbenchTeamWorklogView {
|
||||
weekStart: string;
|
||||
@@ -330,11 +345,8 @@ export interface WorkbenchTeamWorklogView {
|
||||
memberId: string;
|
||||
memberName: string;
|
||||
totalHours: number;
|
||||
overtimeHours: number;
|
||||
}>;
|
||||
/** 项目/个人/其他 列(保序,去重) */
|
||||
categories: Array<{ key: string; label: string; kind: WorkbenchTeamWorklogItemKind }>;
|
||||
/** 每个 category 一个 series,data[i] = 第 i 个成员该 category 的工时(缺则 0) */
|
||||
seriesMatrix: Array<{ key: string; label: string; kind: WorkbenchTeamWorklogItemKind; data: number[] }>;
|
||||
/** 团队总工时 */
|
||||
totalHours: number;
|
||||
/** 团队人均工时 */
|
||||
@@ -345,10 +357,6 @@ export interface WorkbenchTeamWorklogView {
|
||||
fillRate: number;
|
||||
/** 偏低人数:< 团队均值 × 0.8 */
|
||||
lowCount: number;
|
||||
/** 加班人数:> 周标准 × 1.125 */
|
||||
highCount: number;
|
||||
/** 加班判定阈值(小时),KPI 文案展示用 */
|
||||
overtimeThreshold: number;
|
||||
/** 工时最低成员(用于底部小结) */
|
||||
lowest: { memberName: string; hours: number } | null;
|
||||
/** 工时最高成员(用于底部小结) */
|
||||
@@ -360,68 +368,46 @@ export function buildWorkbenchTeamWorklogView(source: Api.Project.TeamWorklogWee
|
||||
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 members = source.members.map((member, index) => {
|
||||
const items = member.items.map(toWorklogDistributionItem);
|
||||
const totalHours = roundHours(items.reduce((sum, item) => sum + item.hours, 0));
|
||||
const overtimeHours = roundHours(member.overtimeHours ?? 0);
|
||||
|
||||
// 列保序去重:按成员遍历顺序首次出现即入列;项目优先、个人/其他按出现顺序追加
|
||||
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());
|
||||
return {
|
||||
memberId: member.userId,
|
||||
memberName: index === 0 ? `${member.userNickname}(我)` : member.userNickname,
|
||||
totalHours,
|
||||
overtimeHours
|
||||
};
|
||||
});
|
||||
|
||||
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 totalHours = roundHours(members.reduce((sum, member) => sum + member.totalHours, 0));
|
||||
const memberCount = members.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;
|
||||
const lowCount = members.filter(member => member.totalHours < lowThreshold).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 };
|
||||
for (const member of members) {
|
||||
if (!lowest || member.totalHours < lowest.hours)
|
||||
lowest = { memberName: member.memberName, hours: member.totalHours };
|
||||
if (!highest || member.totalHours > highest.hours)
|
||||
highest = { memberName: member.memberName, hours: member.totalHours };
|
||||
}
|
||||
|
||||
return {
|
||||
weekStart: source.weekStart,
|
||||
weekLabel,
|
||||
members: memberView,
|
||||
categories,
|
||||
seriesMatrix,
|
||||
members,
|
||||
totalHours,
|
||||
averageHours,
|
||||
expectedTotalHours,
|
||||
fillRate,
|
||||
lowCount,
|
||||
highCount,
|
||||
overtimeThreshold: roundHours(highThreshold),
|
||||
lowest,
|
||||
highest
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user