|
@@ -114,10 +114,14 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
|
|
|
// 设计师画像(用于智能推荐)
|
|
|
designerProfiles: any[] = [
|
|
|
- { id: 'zhang', name: '张三', skills: ['现代风格', '北欧风格'], workload: 70, avgRating: 4.5, experience: 3 },
|
|
|
- { id: 'li', name: '李四', skills: ['新中式', '宋式风格'], workload: 45, avgRating: 4.8, experience: 5 },
|
|
|
- { id: 'wang', name: '王五', skills: ['北欧风格', '日式风格'], workload: 85, avgRating: 4.2, experience: 2 },
|
|
|
- { id: 'zhao', name: '赵六', skills: ['现代风格', '轻奢风格'], workload: 30, avgRating: 4.6, experience: 4 }
|
|
|
+ { id: 'zhang', name: '张三', skills: ['现代风格', '北欧风格'], workload: 95, avgRating: 4.5, experience: 3 },
|
|
|
+ { id: 'li', name: '李四', skills: ['新中式', '宋式风格'], workload: 25, avgRating: 4.8, experience: 5 },
|
|
|
+ { id: 'wang', name: '王五', skills: ['北欧风格', '日式风格'], workload: 75, avgRating: 4.2, experience: 2 },
|
|
|
+ { id: 'zhao', name: '赵六', skills: ['现代风格', '轻奢风格'], workload: 15, avgRating: 4.6, experience: 4 },
|
|
|
+ { id: 'sun', name: '孙七', skills: ['简约风格', '工业风格'], workload: 35, avgRating: 4.3, experience: 3 },
|
|
|
+ { id: 'zhou', name: '周八', skills: ['欧式风格', '美式风格'], workload: 5, avgRating: 4.7, experience: 6 },
|
|
|
+ { id: 'wu', name: '吴九', skills: ['地中海风格', '田园风格'], workload: 60, avgRating: 4.4, experience: 4 },
|
|
|
+ { id: 'chen', name: '陈十', skills: ['现代简约', '新古典'], workload: 0, avgRating: 4.9, experience: 7 }
|
|
|
];
|
|
|
|
|
|
// 10个项目阶段
|
|
@@ -150,7 +154,7 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
private workloadChart: any | null = null;
|
|
|
workloadDimension: 'designer' | 'member' = 'designer';
|
|
|
// 甘特时间尺度:仅周/月
|
|
|
- ganttScale: 'week' | 'month' = 'week';
|
|
|
+ ganttScale: 'day' | 'week' | 'month' = 'week';
|
|
|
// 新增:甘特模式(项目 / 设计师排班)
|
|
|
ganttMode: 'project' | 'designer' = 'project';
|
|
|
|
|
@@ -341,7 +345,7 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
|
|
|
// ===== 追加生成示例数据:保证总量达到100条 =====
|
|
|
const stageIds = this.projectStages.map(s => s.id);
|
|
|
- const designers = ['张三','李四','王五','赵六','钱七','孙八','周九','吴十','郑一','冯二','陈三','褚四'];
|
|
|
+ const designers = ['张三','李四','王五','赵六','孙七','周八','吴九','陈十'];
|
|
|
const statusMap: Record<string, string> = {
|
|
|
pendingApproval: '待确认',
|
|
|
pendingAssignment: '待分配',
|
|
@@ -355,26 +359,32 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
delivery: '已完成'
|
|
|
};
|
|
|
|
|
|
- for (let i = 8; i <= 100; i++) {
|
|
|
- const stageIndex = (i - 1) % stageIds.length;
|
|
|
+ // 为有项目的设计师分配项目
|
|
|
+ const busyDesigners = ['张三', '王五', '吴九']; // 高负荷设计师
|
|
|
+ const moderateDesigners = ['孙七']; // 中等负荷设计师
|
|
|
+ const idleDesigners = ['李四', '赵六', '周八', '陈十']; // 空闲设计师
|
|
|
+
|
|
|
+ // 为忙碌的设计师分配更多项目
|
|
|
+ for (let i = 8; i <= 30; i++) {
|
|
|
+ const designerIndex = (i - 8) % busyDesigners.length;
|
|
|
+ const designerName = busyDesigners[designerIndex];
|
|
|
+ const stageIndex = (i - 1) % 7 + 3; // 主要在进行中的阶段
|
|
|
const currentStage = stageIds[stageIndex];
|
|
|
const type: 'soft' | 'hard' = i % 2 === 0 ? 'soft' : 'hard';
|
|
|
- const urgency: 'high' | 'medium' | 'low' = i % 5 === 0 ? 'high' : (i % 3 === 0 ? 'medium' : 'low');
|
|
|
- const isOverdue = ['planning','modeling','rendering','postProduction','review','revision','delivery'].includes(currentStage) ? i % 7 === 0 : false;
|
|
|
- const overdueDays = isOverdue ? (i % 10) + 1 : 0;
|
|
|
- const hasDesigner = !['pendingApproval', 'pendingAssignment'].includes(currentStage);
|
|
|
- const designerName = hasDesigner ? designers[i % designers.length] : '';
|
|
|
+ const urgency: 'high' | 'medium' | 'low' = i % 4 === 0 ? 'high' : (i % 3 === 0 ? 'medium' : 'low');
|
|
|
+ const isOverdue = i % 8 === 0;
|
|
|
+ const overdueDays = isOverdue ? (i % 5) + 1 : 0;
|
|
|
const status = statusMap[currentStage] || '进行中';
|
|
|
const expectedEndDate = new Date();
|
|
|
- const daysOffset = isOverdue ? - (overdueDays + (i % 5)) : ((i % 20) + 3);
|
|
|
+ const daysOffset = isOverdue ? -(overdueDays + (i % 3)) : ((i % 15) + 5);
|
|
|
expectedEndDate.setDate(expectedEndDate.getDate() + daysOffset);
|
|
|
const daysToDeadline = Math.ceil((expectedEndDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
|
const dueSoon = !isOverdue && daysToDeadline >= 0 && daysToDeadline <= 3;
|
|
|
- const memberType: 'vip' | 'normal' = i % 4 === 0 ? 'vip' : 'normal';
|
|
|
+ const memberType: 'vip' | 'normal' = i % 5 === 0 ? 'vip' : 'normal';
|
|
|
|
|
|
this.projects.push({
|
|
|
id: `proj-${String(i).padStart(3, '0')}`,
|
|
|
- name: `${type === 'soft' ? '软装' : '硬装'}示例项目 ${i}`,
|
|
|
+ name: `${designerName}负责的${type === 'soft' ? '软装' : '硬装'}项目 ${i}`,
|
|
|
type,
|
|
|
memberType,
|
|
|
designerName,
|
|
@@ -389,6 +399,66 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
phases: []
|
|
|
});
|
|
|
}
|
|
|
+
|
|
|
+ // 为中等负荷设计师分配少量项目
|
|
|
+ for (let i = 31; i <= 35; i++) {
|
|
|
+ const designerName = moderateDesigners[0];
|
|
|
+ const stageIndex = (i - 1) % 5 + 4; // 中间阶段
|
|
|
+ const currentStage = stageIds[stageIndex];
|
|
|
+ const type: 'soft' | 'hard' = i % 2 === 0 ? 'soft' : 'hard';
|
|
|
+ const urgency: 'high' | 'medium' | 'low' = 'medium';
|
|
|
+ const status = statusMap[currentStage] || '进行中';
|
|
|
+ const expectedEndDate = new Date();
|
|
|
+ expectedEndDate.setDate(expectedEndDate.getDate() + (i % 10) + 7);
|
|
|
+ const memberType: 'vip' | 'normal' = 'normal';
|
|
|
+
|
|
|
+ this.projects.push({
|
|
|
+ id: `proj-${String(i).padStart(3, '0')}`,
|
|
|
+ name: `${designerName}负责的${type === 'soft' ? '软装' : '硬装'}项目 ${i}`,
|
|
|
+ type,
|
|
|
+ memberType,
|
|
|
+ designerName,
|
|
|
+ status,
|
|
|
+ expectedEndDate,
|
|
|
+ deadline: expectedEndDate,
|
|
|
+ isOverdue: false,
|
|
|
+ overdueDays: 0,
|
|
|
+ dueSoon: false,
|
|
|
+ urgency,
|
|
|
+ currentStage,
|
|
|
+ phases: []
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 空闲设计师不分配项目,或只分配很少的已完成项目
|
|
|
+ for (let i = 36; i <= 40; i++) {
|
|
|
+ const designerIndex = (i - 36) % idleDesigners.length;
|
|
|
+ const designerName = idleDesigners[designerIndex];
|
|
|
+ const currentStage = 'delivery'; // 已完成的项目
|
|
|
+ const type: 'soft' | 'hard' = i % 2 === 0 ? 'soft' : 'hard';
|
|
|
+ const urgency: 'high' | 'medium' | 'low' = 'low';
|
|
|
+ const status = '已完成';
|
|
|
+ const expectedEndDate = new Date();
|
|
|
+ expectedEndDate.setDate(expectedEndDate.getDate() - (i % 10) - 5); // 过去的日期
|
|
|
+ const memberType: 'vip' | 'normal' = 'normal';
|
|
|
+
|
|
|
+ this.projects.push({
|
|
|
+ id: `proj-${String(i).padStart(3, '0')}`,
|
|
|
+ name: `${designerName}已完成的${type === 'soft' ? '软装' : '硬装'}项目 ${i}`,
|
|
|
+ type,
|
|
|
+ memberType,
|
|
|
+ designerName,
|
|
|
+ status,
|
|
|
+ expectedEndDate,
|
|
|
+ deadline: expectedEndDate,
|
|
|
+ isOverdue: false,
|
|
|
+ overdueDays: 0,
|
|
|
+ dueSoon: false,
|
|
|
+ urgency,
|
|
|
+ currentStage,
|
|
|
+ phases: []
|
|
|
+ });
|
|
|
+ }
|
|
|
// ===== 示例数据生成结束 =====
|
|
|
|
|
|
// 统一补齐真实时间字段(deadline/createdAt),以真实字段贯通筛选与甘特
|
|
@@ -696,7 +766,7 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
// 设置甘特时间尺度
|
|
|
- setGanttScale(scale: 'week' | 'month'): void {
|
|
|
+ setGanttScale(scale: 'day' | 'week' | 'month'): void {
|
|
|
if (this.ganttScale !== scale) {
|
|
|
this.ganttScale = scale;
|
|
|
this.updateGantt();
|
|
@@ -960,28 +1030,45 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
|
const todayTs = today.getTime();
|
|
|
|
|
|
- // 时间轴按当前周/月
|
|
|
+ // 时间轴按当前周/月/日
|
|
|
let xMin: number;
|
|
|
let xMax: number;
|
|
|
let xSplitNumber: number;
|
|
|
let xLabelFormatter: (value: number) => string;
|
|
|
- if (this.ganttScale === 'week') {
|
|
|
- const day = today.getDay();
|
|
|
- const diffToMonday = (day === 0 ? 6 : day - 1);
|
|
|
- const startOfWeek = new Date(today.getTime() - diffToMonday * DAY);
|
|
|
- const endOfWeek = new Date(startOfWeek.getTime() + 7 * DAY - 1);
|
|
|
+ if (this.ganttScale === 'day') {
|
|
|
+ // 日视图:显示今日24小时
|
|
|
+ const startOfDay = new Date(today.getTime());
|
|
|
+ const endOfDay = new Date(today.getTime() + DAY - 1);
|
|
|
+ xMin = startOfDay.getTime();
|
|
|
+ xMax = endOfDay.getTime();
|
|
|
+ xSplitNumber = 24;
|
|
|
+ xLabelFormatter = (val) => `${new Date(val).getHours()}:00`;
|
|
|
+ } else if (this.ganttScale === 'week') {
|
|
|
+ // 周视图:从今天开始显示未来7天的具体日期
|
|
|
+ const startOfWeek = new Date(today.getTime());
|
|
|
+ const endOfWeek = new Date(today.getTime() + 7 * DAY - 1);
|
|
|
xMin = startOfWeek.getTime();
|
|
|
xMax = endOfWeek.getTime();
|
|
|
xSplitNumber = 7;
|
|
|
- const WEEK_LABELS = ['周日','周一','周二','周三','周四','周五','周六'];
|
|
|
- xLabelFormatter = (val) => WEEK_LABELS[new Date(val).getDay()];
|
|
|
+ xLabelFormatter = (val) => {
|
|
|
+ const date = new Date(val);
|
|
|
+ const month = date.getMonth() + 1;
|
|
|
+ const day = date.getDate();
|
|
|
+ return `${month}月${day}日`;
|
|
|
+ };
|
|
|
} else {
|
|
|
+ // 月视图:从当前月份开始显示未来几个月
|
|
|
const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
|
- const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
|
+ const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 3, 0, 23, 59, 59, 999); // 显示未来3个月
|
|
|
xMin = startOfMonth.getTime();
|
|
|
xMax = endOfMonth.getTime();
|
|
|
- xSplitNumber = 4;
|
|
|
- xLabelFormatter = (val) => `第${Math.ceil(new Date(val).getDate() / 7)}周`;
|
|
|
+ xSplitNumber = 3;
|
|
|
+ xLabelFormatter = (val) => {
|
|
|
+ const date = new Date(val);
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = date.getMonth() + 1;
|
|
|
+ return `${year}年${month}月`;
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
// 仅统计已分配项目
|
|
@@ -1001,18 +1088,28 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
|
|
|
const categories = sortedDesigners;
|
|
|
|
|
|
- // 工作量等级(用于左侧小圆点颜色)
|
|
|
+ // 工作量等级(用于左侧小圆点颜色和负荷状态判断)
|
|
|
const workloadLevelMap: Record<string, 'high'|'medium'|'low'> = {} as any;
|
|
|
+ const workloadStatusMap: Record<string, 'overloaded'|'busy'|'available'> = {} as any;
|
|
|
categories.forEach(name => {
|
|
|
const cnt = busyCountMap[name] || 0;
|
|
|
- workloadLevelMap[name] = cnt >= 5 ? 'high' : (cnt >= 3 ? 'medium' : 'low');
|
|
|
+ if (cnt >= 5) {
|
|
|
+ workloadLevelMap[name] = 'high';
|
|
|
+ workloadStatusMap[name] = 'overloaded'; // 不宜派单
|
|
|
+ } else if (cnt >= 3) {
|
|
|
+ workloadLevelMap[name] = 'medium';
|
|
|
+ workloadStatusMap[name] = 'busy'; // 适度忙碌
|
|
|
+ } else {
|
|
|
+ workloadLevelMap[name] = 'low';
|
|
|
+ workloadStatusMap[name] = 'available'; // 可接单
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
- // 条形颜色仍按项目紧急度
|
|
|
+ // 条形颜色按项目紧急度,增强高负荷时段的视觉效果
|
|
|
const colorByUrgency: Record<'high'|'medium'|'low', string> = {
|
|
|
- high: '#ef4444',
|
|
|
- medium: '#f59e0b',
|
|
|
- low: '#22c55e'
|
|
|
+ high: '#dc2626', // 更深的红色,突出高紧急度
|
|
|
+ medium: '#ea580c', // 更深的橙色
|
|
|
+ low: '#16a34a' // 更深的绿色
|
|
|
} as const;
|
|
|
|
|
|
const data = assigned.flatMap(p => {
|
|
@@ -1021,14 +1118,104 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
const start = p.createdAt ? new Date(p.createdAt).getTime() : end - baseDays * DAY;
|
|
|
const yIndex = categories.indexOf(p.designerName);
|
|
|
if (yIndex === -1) return [] as any[];
|
|
|
- const color = colorByUrgency[p.urgency] || '#60a5fa';
|
|
|
+
|
|
|
+ // 根据设计师工作负荷状态调整项目条的视觉效果
|
|
|
+ const workloadStatus = workloadStatusMap[p.designerName];
|
|
|
+ let color = colorByUrgency[p.urgency] || '#60a5fa';
|
|
|
+ let borderWidth = 1;
|
|
|
+ let borderColor = 'transparent';
|
|
|
+
|
|
|
+ // 高负荷时段增强视觉效果
|
|
|
+ if (workloadStatus === 'overloaded') {
|
|
|
+ borderWidth = 3;
|
|
|
+ borderColor = '#991b1b'; // 深红色边框
|
|
|
+ // 对于超负荷状态,使用更深的红色调
|
|
|
+ if (p.urgency === 'high') {
|
|
|
+ color = '#7f1d1d'; // 深红色
|
|
|
+ } else if (p.urgency === 'medium') {
|
|
|
+ color = '#c2410c'; // 深橙色
|
|
|
+ } else {
|
|
|
+ color = '#dc2626'; // 红色(即使是低紧急度也用红色表示超负荷)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return [{
|
|
|
name: p.name,
|
|
|
- value: [yIndex, start, end, p.designerName, p.urgency, p.memberType, p.currentStage],
|
|
|
- itemStyle: { color }
|
|
|
+ value: [yIndex, start, end, p.designerName, p.urgency, p.memberType, p.currentStage, workloadStatus],
|
|
|
+ itemStyle: {
|
|
|
+ color,
|
|
|
+ borderWidth,
|
|
|
+ borderColor,
|
|
|
+ opacity: workloadStatus === 'overloaded' ? 0.9 : 1.0
|
|
|
+ }
|
|
|
}];
|
|
|
});
|
|
|
|
|
|
+ // 生成空闲时段背景数据 - 只在真正空闲的时间段显示
|
|
|
+ const idleBackgroundData: any[] = [];
|
|
|
+
|
|
|
+ categories.forEach((designerName, yIndex) => {
|
|
|
+ const designerProjects = byDesigner[designerName] || [];
|
|
|
+ const workloadStatus = workloadStatusMap[designerName];
|
|
|
+
|
|
|
+ // 获取该设计师的所有项目时间段
|
|
|
+ const projectTimeRanges = designerProjects.map(p => {
|
|
|
+ const end = new Date(p.deadline).getTime();
|
|
|
+ const baseDays = p.type === 'hard' ? 30 : 14;
|
|
|
+ const start = p.createdAt ? new Date(p.createdAt).getTime() : end - baseDays * DAY;
|
|
|
+ return { start, end };
|
|
|
+ }).sort((a, b) => a.start - b.start);
|
|
|
+
|
|
|
+ // 找出空闲时间段
|
|
|
+ const idleTimeRanges: { start: number; end: number }[] = [];
|
|
|
+
|
|
|
+ if (projectTimeRanges.length === 0) {
|
|
|
+ // 完全没有项目,整个时间轴都是空闲
|
|
|
+ idleTimeRanges.push({ start: xMin, end: xMax });
|
|
|
+ } else {
|
|
|
+ // 检查项目之间的空隙
|
|
|
+ let currentTime = xMin;
|
|
|
+
|
|
|
+ for (const range of projectTimeRanges) {
|
|
|
+ if (currentTime < range.start) {
|
|
|
+ // 在项目开始前有空闲时间
|
|
|
+ idleTimeRanges.push({ start: currentTime, end: range.start });
|
|
|
+ }
|
|
|
+ currentTime = Math.max(currentTime, range.end);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查最后一个项目后是否还有空闲时间
|
|
|
+ if (currentTime < xMax) {
|
|
|
+ idleTimeRanges.push({ start: currentTime, end: xMax });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 为每个空闲时间段创建背景数据
|
|
|
+ idleTimeRanges.forEach((idleRange, index) => {
|
|
|
+ // 只有当空闲时间段足够长时才显示(至少1天)
|
|
|
+ if (idleRange.end - idleRange.start >= DAY) {
|
|
|
+ let backgroundColor = 'transparent';
|
|
|
+
|
|
|
+ if (workloadStatus === 'available') {
|
|
|
+ backgroundColor = 'rgba(34, 197, 94, 0.15)'; // 淡绿色背景表示空闲可接单
|
|
|
+ } else if (workloadStatus === 'overloaded') {
|
|
|
+ backgroundColor = 'rgba(239, 68, 68, 0.1)'; // 淡红色背景表示超负荷
|
|
|
+ }
|
|
|
+
|
|
|
+ if (backgroundColor !== 'transparent') {
|
|
|
+ idleBackgroundData.push({
|
|
|
+ name: `${designerName}-空闲${index + 1}`,
|
|
|
+ value: [yIndex, idleRange.start, idleRange.end, designerName, 'background', workloadStatus],
|
|
|
+ itemStyle: {
|
|
|
+ color: backgroundColor,
|
|
|
+ borderWidth: 0
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
const prevOpt: any = (this.ganttChart as any).getOption ? (this.ganttChart as any).getOption() : null;
|
|
|
const total = categories.length || 1;
|
|
|
const visible = Math.min(total, 30);
|
|
@@ -1042,12 +1229,27 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
trigger: 'item',
|
|
|
formatter: (params: any) => {
|
|
|
const v = params.value;
|
|
|
+ if (v[4] === 'background') {
|
|
|
+ const workloadStatus = v[5];
|
|
|
+ const statusText = workloadStatus === 'available' ? '空闲可接单' :
|
|
|
+ workloadStatus === 'overloaded' ? '超负荷不宜派单' : '适度忙碌';
|
|
|
+ return `设计师:${v[3]}<br/>状态:${statusText}`;
|
|
|
+ }
|
|
|
const start = new Date(v[1]);
|
|
|
const end = new Date(v[2]);
|
|
|
- return `项目:${params.name}<br/>设计师:${v[3]}<br/>阶段:${v[6]}<br/>起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`;
|
|
|
+ const workloadStatus = v[7];
|
|
|
+ const statusText = workloadStatus === 'available' ? '可接单' :
|
|
|
+ workloadStatus === 'overloaded' ? '超负荷' : '适度忙碌';
|
|
|
+ return `项目:${params.name}<br/>设计师:${v[3]}(${statusText})<br/>阶段:${v[6]}<br/>起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`;
|
|
|
}
|
|
|
},
|
|
|
- grid: { left: 110, right: 64, top: 30, bottom: 30 },
|
|
|
+ legend: {
|
|
|
+ data: ['空闲可接单', '适度忙碌', '超负荷不宜派单', '高紧急', '中紧急', '低紧急'],
|
|
|
+ bottom: 0,
|
|
|
+ itemGap: 20,
|
|
|
+ textStyle: { fontSize: 12 }
|
|
|
+ },
|
|
|
+ grid: { left: 110, right: 64, top: 30, bottom: 60 },
|
|
|
xAxis: {
|
|
|
type: 'time',
|
|
|
min: xMin,
|
|
@@ -1067,13 +1269,16 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
formatter: (val: string) => {
|
|
|
const lvl = workloadLevelMap[val] || 'low';
|
|
|
const count = busyCountMap[val] || 0;
|
|
|
+ const status = workloadStatusMap[val] || 'available';
|
|
|
const text = val.length > 8 ? val.slice(0, 8) + '…' : val;
|
|
|
- return `{${lvl}Dot|●} ${text}(${count}项)`;
|
|
|
+ const statusIcon = status === 'available' ? '🟢' :
|
|
|
+ status === 'overloaded' ? '🔴' : '🟡';
|
|
|
+ return `{${lvl}Dot|●} ${statusIcon} ${text}(${count}项)`;
|
|
|
},
|
|
|
rich: {
|
|
|
- highDot: { color: '#ef4444' },
|
|
|
- mediumDot: { color: '#f59e0b' },
|
|
|
- lowDot: { color: '#22c55e' }
|
|
|
+ highDot: { color: '#dc2626' },
|
|
|
+ mediumDot: { color: '#ea580c' },
|
|
|
+ lowDot: { color: '#16a34a' }
|
|
|
}
|
|
|
},
|
|
|
axisTick: { show: false },
|
|
@@ -1084,8 +1289,36 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
{ type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true }
|
|
|
],
|
|
|
series: [
|
|
|
+ // 背景层 - 显示空闲时段
|
|
|
{
|
|
|
type: 'custom',
|
|
|
+ name: '工作负荷背景',
|
|
|
+ renderItem: (params: any, api: any) => {
|
|
|
+ const categoryIndex = api.value(0);
|
|
|
+ const start = api.coord([api.value(1), categoryIndex]);
|
|
|
+ const end = api.coord([api.value(2), categoryIndex]);
|
|
|
+ const height = api.size([0, 1])[1] * 0.8;
|
|
|
+ const rectShape = echarts.graphic.clipRectByRect({
|
|
|
+ x: start[0],
|
|
|
+ y: start[1] - height / 2,
|
|
|
+ width: Math.max(end[0] - start[0], 2),
|
|
|
+ height
|
|
|
+ }, {
|
|
|
+ x: params.coordSys.x,
|
|
|
+ y: params.coordSys.y,
|
|
|
+ width: params.coordSys.width,
|
|
|
+ height: params.coordSys.height
|
|
|
+ });
|
|
|
+ return rectShape ? { type: 'rect', shape: rectShape, style: api.style() } : undefined;
|
|
|
+ },
|
|
|
+ encode: { x: [1, 2], y: 0 },
|
|
|
+ data: idleBackgroundData,
|
|
|
+ z: 1
|
|
|
+ },
|
|
|
+ // 项目条层
|
|
|
+ {
|
|
|
+ type: 'custom',
|
|
|
+ name: '项目进度',
|
|
|
renderItem: (params: any, api: any) => {
|
|
|
const categoryIndex = api.value(0);
|
|
|
const start = api.coord([api.value(1), categoryIndex]);
|
|
@@ -1108,6 +1341,7 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
data,
|
|
|
itemStyle: { borderRadius: 4 },
|
|
|
emphasis: { focus: 'self' },
|
|
|
+ z: 2,
|
|
|
markLine: {
|
|
|
silent: true,
|
|
|
symbol: 'none',
|