v1.9.0-rc.4 - TaskDrawer upgrades
@@ -831,7 +831,7 @@ const handleTaskUpdateEvent = (e: { task: Task }) => {
|
||||
// 任务添加事件处理器
|
||||
const handleTaskAddEvent = (e: { task: Task }) => {
|
||||
// 打印全部的tasks
|
||||
console.log('当前所有任务:', tasks.value)
|
||||
//console.log('当前所有任务:', tasks.value)
|
||||
const newTask = e.task
|
||||
showMessage(`Demo 任务[${newTask.name}] 已创建`, 'success')
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"id": "resource-001",
|
||||
"name": "资源1 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=1",
|
||||
"avatar": "/50-11.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -470,7 +470,7 @@
|
||||
"id": "resource-002",
|
||||
"name": "资源2 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=2",
|
||||
"avatar": "/50-12.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -921,7 +921,7 @@
|
||||
"id": "resource-003",
|
||||
"name": "资源3 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=3",
|
||||
"avatar": "/50-13.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -1387,7 +1387,7 @@
|
||||
"id": "resource-004",
|
||||
"name": "资源4 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=4",
|
||||
"avatar": "/50-14.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -1808,7 +1808,7 @@
|
||||
"id": "resource-005",
|
||||
"name": "资源5 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=5",
|
||||
"avatar": "/50-15.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -2259,7 +2259,7 @@
|
||||
"id": "resource-006",
|
||||
"name": "资源6 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=6",
|
||||
"avatar": "/50-16.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"id": "resource-007",
|
||||
"name": "资源7 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=7",
|
||||
"avatar": "/50-17.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -2658,7 +2658,7 @@
|
||||
"技能C"
|
||||
],
|
||||
"utilization": 93,
|
||||
"color": "#ffc107",
|
||||
"color": "#a8a8a8",
|
||||
"tasks": [
|
||||
{
|
||||
"id": 7000,
|
||||
@@ -3101,7 +3101,7 @@
|
||||
"id": "resource-008",
|
||||
"name": "资源8 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=8",
|
||||
"avatar": "/50-18.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -3447,7 +3447,7 @@
|
||||
"id": "resource-009",
|
||||
"name": "资源9 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=9",
|
||||
"avatar": "/50-19.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -3913,7 +3913,7 @@
|
||||
"id": "resource-010",
|
||||
"name": "资源10 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=10",
|
||||
"avatar": "/50-20.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -4244,7 +4244,7 @@
|
||||
"id": "resource-011",
|
||||
"name": "资源11 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=11",
|
||||
"avatar": "/50-21.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -4665,7 +4665,7 @@
|
||||
"id": "resource-012",
|
||||
"name": "资源12 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=12",
|
||||
"avatar": "/50-22.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -5011,7 +5011,7 @@
|
||||
"id": "resource-013",
|
||||
"name": "资源13 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=13",
|
||||
"avatar": "/50-23.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -5387,7 +5387,7 @@
|
||||
"id": "resource-014",
|
||||
"name": "资源14 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=14",
|
||||
"avatar": "/50-24.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -5778,7 +5778,7 @@
|
||||
"id": "resource-015",
|
||||
"name": "资源15 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=15",
|
||||
"avatar": "/50-25.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -6214,7 +6214,7 @@
|
||||
"id": "resource-016",
|
||||
"name": "资源16 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=16",
|
||||
"avatar": "/50-26.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -6530,7 +6530,7 @@
|
||||
"id": "resource-017",
|
||||
"name": "资源17 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=17",
|
||||
"avatar": "/50-27.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -6981,7 +6981,7 @@
|
||||
"id": "resource-018",
|
||||
"name": "资源18 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=18",
|
||||
"avatar": "/50-28.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -7357,7 +7357,7 @@
|
||||
"id": "resource-019",
|
||||
"name": "资源19 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=19",
|
||||
"avatar": "/50-29.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -7718,7 +7718,7 @@
|
||||
"id": "resource-020",
|
||||
"name": "资源20 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=20",
|
||||
"avatar": "/50-30.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -8154,7 +8154,7 @@
|
||||
"id": "resource-021",
|
||||
"name": "资源21 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=21",
|
||||
"avatar": "/50-31.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -8515,7 +8515,7 @@
|
||||
"id": "resource-022",
|
||||
"name": "资源22 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=22",
|
||||
"avatar": "/50-32.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -8921,7 +8921,7 @@
|
||||
"id": "resource-023",
|
||||
"name": "资源23 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=23",
|
||||
"avatar": "/50-33.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -9297,7 +9297,7 @@
|
||||
"id": "resource-024",
|
||||
"name": "资源24 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=24",
|
||||
"avatar": "/50-34.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -9718,7 +9718,7 @@
|
||||
"id": "resource-025",
|
||||
"name": "资源25 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=25",
|
||||
"avatar": "/50-35.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -10064,7 +10064,7 @@
|
||||
"id": "resource-026",
|
||||
"name": "资源26 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=26",
|
||||
"avatar": "/50-36.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -10455,7 +10455,7 @@
|
||||
"id": "resource-027",
|
||||
"name": "资源27 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=27",
|
||||
"avatar": "/50-37.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -10771,7 +10771,7 @@
|
||||
"id": "resource-028",
|
||||
"name": "资源28 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=28",
|
||||
"avatar": "/50-38.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -11192,7 +11192,7 @@
|
||||
"id": "resource-029",
|
||||
"name": "资源29 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=29",
|
||||
"avatar": "/50-39.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -11598,7 +11598,7 @@
|
||||
"id": "resource-030",
|
||||
"name": "资源30 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=30",
|
||||
"avatar": "/50-40.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -11944,7 +11944,7 @@
|
||||
"id": "resource-031",
|
||||
"name": "资源31 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=31",
|
||||
"avatar": "/50-41.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -12410,7 +12410,7 @@
|
||||
"id": "resource-032",
|
||||
"name": "资源32 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=32",
|
||||
"avatar": "/50-42.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -12816,7 +12816,7 @@
|
||||
"id": "resource-033",
|
||||
"name": "资源33 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=33",
|
||||
"avatar": "/50-43.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -13222,7 +13222,7 @@
|
||||
"id": "resource-034",
|
||||
"name": "资源34 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=34",
|
||||
"avatar": "/50-44.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -13688,7 +13688,7 @@
|
||||
"id": "resource-035",
|
||||
"name": "资源35 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=35",
|
||||
"avatar": "/50-45.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -14139,7 +14139,7 @@
|
||||
"id": "resource-036",
|
||||
"name": "资源36 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=36",
|
||||
"avatar": "/50-46.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -14590,7 +14590,7 @@
|
||||
"id": "resource-037",
|
||||
"name": "资源37 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=37",
|
||||
"avatar": "/50-47.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -14951,7 +14951,7 @@
|
||||
"id": "resource-038",
|
||||
"name": "资源38 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=38",
|
||||
"avatar": "/50-48.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -15357,7 +15357,7 @@
|
||||
"id": "resource-039",
|
||||
"name": "资源39 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=39",
|
||||
"avatar": "/50-49.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -15673,7 +15673,7 @@
|
||||
"id": "resource-040",
|
||||
"name": "资源40 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=40",
|
||||
"avatar": "/50-50.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -16109,7 +16109,7 @@
|
||||
"id": "resource-041",
|
||||
"name": "资源41 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=41",
|
||||
"avatar": "/50-51.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -16485,7 +16485,7 @@
|
||||
"id": "resource-042",
|
||||
"name": "资源42 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=42",
|
||||
"avatar": "/50-52.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -16906,7 +16906,7 @@
|
||||
"id": "resource-043",
|
||||
"name": "资源43 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=43",
|
||||
"avatar": "/50-53.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -17252,7 +17252,7 @@
|
||||
"id": "resource-044",
|
||||
"name": "资源44 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=44",
|
||||
"avatar": "/50-54.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -17583,7 +17583,7 @@
|
||||
"id": "resource-045",
|
||||
"name": "资源45 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=45",
|
||||
"avatar": "/50-55.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -17944,7 +17944,7 @@
|
||||
"id": "resource-046",
|
||||
"name": "资源46 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=46",
|
||||
"avatar": "/50-56.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -18380,7 +18380,7 @@
|
||||
"id": "resource-047",
|
||||
"name": "资源47 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=47",
|
||||
"avatar": "/50-57.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -18726,7 +18726,7 @@
|
||||
"id": "resource-048",
|
||||
"name": "资源48 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=48",
|
||||
"avatar": "/50-58.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -19147,7 +19147,7 @@
|
||||
"id": "resource-049",
|
||||
"name": "资源49 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=49",
|
||||
"avatar": "/50-59.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -19493,7 +19493,7 @@
|
||||
"id": "resource-050",
|
||||
"name": "资源50 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=50",
|
||||
"avatar": "/50-60.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -19914,7 +19914,7 @@
|
||||
"id": "resource-051",
|
||||
"name": "资源51 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=51",
|
||||
"avatar": "/50-61.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -20320,7 +20320,7 @@
|
||||
"id": "resource-052",
|
||||
"name": "资源52 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=52",
|
||||
"avatar": "/50-62.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -20651,7 +20651,7 @@
|
||||
"id": "resource-053",
|
||||
"name": "资源53 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=53",
|
||||
"avatar": "/50-63.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -21027,7 +21027,7 @@
|
||||
"id": "resource-054",
|
||||
"name": "资源54 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=54",
|
||||
"avatar": "/50-64.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -21343,7 +21343,7 @@
|
||||
"id": "resource-055",
|
||||
"name": "资源55 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=55",
|
||||
"avatar": "/50-65.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -21779,7 +21779,7 @@
|
||||
"id": "resource-056",
|
||||
"name": "资源56 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=56",
|
||||
"avatar": "/50-66.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -22245,7 +22245,7 @@
|
||||
"id": "resource-057",
|
||||
"name": "资源57 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=57",
|
||||
"avatar": "/50-67.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -22561,7 +22561,7 @@
|
||||
"id": "resource-058",
|
||||
"name": "资源58 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=58",
|
||||
"avatar": "/50-68.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -22937,7 +22937,7 @@
|
||||
"id": "resource-059",
|
||||
"name": "资源59 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=59",
|
||||
"avatar": "/50-69.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -23373,7 +23373,7 @@
|
||||
"id": "resource-060",
|
||||
"name": "资源60 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=60",
|
||||
"avatar": "/50-70.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -23794,7 +23794,7 @@
|
||||
"id": "resource-061",
|
||||
"name": "资源61 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=61",
|
||||
"avatar": "/50-71.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -24110,7 +24110,7 @@
|
||||
"id": "resource-062",
|
||||
"name": "资源62 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=62",
|
||||
"avatar": "/50-72.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -24531,7 +24531,7 @@
|
||||
"id": "resource-063",
|
||||
"name": "资源63 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=63",
|
||||
"avatar": "/50-73.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -24982,7 +24982,7 @@
|
||||
"id": "resource-064",
|
||||
"name": "资源64 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=64",
|
||||
"avatar": "/50-74.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -25313,7 +25313,7 @@
|
||||
"id": "resource-065",
|
||||
"name": "资源65 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=65",
|
||||
"avatar": "/50-75.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -25719,7 +25719,7 @@
|
||||
"id": "resource-066",
|
||||
"name": "资源66 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=66",
|
||||
"avatar": "/50-76.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -26125,7 +26125,7 @@
|
||||
"id": "resource-067",
|
||||
"name": "资源67 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=67",
|
||||
"avatar": "/50-77.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -26531,7 +26531,7 @@
|
||||
"id": "resource-068",
|
||||
"name": "资源68 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=68",
|
||||
"avatar": "/50-78.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -26937,7 +26937,7 @@
|
||||
"id": "resource-069",
|
||||
"name": "资源69 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=69",
|
||||
"avatar": "/50-79.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -27403,7 +27403,7 @@
|
||||
"id": "resource-070",
|
||||
"name": "资源70 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=70",
|
||||
"avatar": "/50-80.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -27794,7 +27794,7 @@
|
||||
"id": "resource-071",
|
||||
"name": "资源71 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=71",
|
||||
"avatar": "/50-81.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -28140,7 +28140,7 @@
|
||||
"id": "resource-072",
|
||||
"name": "资源72 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=72",
|
||||
"avatar": "/50-82.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -28561,7 +28561,7 @@
|
||||
"id": "resource-073",
|
||||
"name": "资源73 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=73",
|
||||
"avatar": "/50-83.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -28982,7 +28982,7 @@
|
||||
"id": "resource-074",
|
||||
"name": "资源74 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=74",
|
||||
"avatar": "/50-84.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -29298,7 +29298,7 @@
|
||||
"id": "resource-075",
|
||||
"name": "资源75 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=75",
|
||||
"avatar": "/50-85.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -29689,7 +29689,7 @@
|
||||
"id": "resource-076",
|
||||
"name": "资源76 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=76",
|
||||
"avatar": "/50-86.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -30110,7 +30110,7 @@
|
||||
"id": "resource-077",
|
||||
"name": "资源77 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=77",
|
||||
"avatar": "/50-87.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -30546,7 +30546,7 @@
|
||||
"id": "resource-078",
|
||||
"name": "资源78 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=78",
|
||||
"avatar": "/50-88.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -30967,7 +30967,7 @@
|
||||
"id": "resource-079",
|
||||
"name": "资源79 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=79",
|
||||
"avatar": "/50-89.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -31328,7 +31328,7 @@
|
||||
"id": "resource-080",
|
||||
"name": "资源80 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=80",
|
||||
"avatar": "/50-90.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -31704,7 +31704,7 @@
|
||||
"id": "resource-081",
|
||||
"name": "资源81 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=81",
|
||||
"avatar": "/50-91.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -32020,7 +32020,7 @@
|
||||
"id": "resource-082",
|
||||
"name": "资源82 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=82",
|
||||
"avatar": "/50-92.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -32366,7 +32366,7 @@
|
||||
"id": "resource-083",
|
||||
"name": "资源83 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=83",
|
||||
"avatar": "/50-93.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -32712,7 +32712,7 @@
|
||||
"id": "resource-084",
|
||||
"name": "资源84 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=84",
|
||||
"avatar": "/50-94.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -33058,7 +33058,7 @@
|
||||
"id": "resource-085",
|
||||
"name": "资源85 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=85",
|
||||
"avatar": "/50-95.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -33374,7 +33374,7 @@
|
||||
"id": "resource-086",
|
||||
"name": "资源86 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=86",
|
||||
"avatar": "/50-96.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -33825,7 +33825,7 @@
|
||||
"id": "resource-087",
|
||||
"name": "资源87 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=87",
|
||||
"avatar": "/50-97.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -34186,7 +34186,7 @@
|
||||
"id": "resource-088",
|
||||
"name": "资源88 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=88",
|
||||
"avatar": "/50-98.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -34517,7 +34517,7 @@
|
||||
"id": "resource-089",
|
||||
"name": "资源89 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=89",
|
||||
"avatar": "/50-99.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -34953,7 +34953,7 @@
|
||||
"id": "resource-090",
|
||||
"name": "资源90 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=90",
|
||||
"avatar": "/50-100.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -35404,7 +35404,7 @@
|
||||
"id": "resource-091",
|
||||
"name": "资源91 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=91",
|
||||
"avatar": "/50-101.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -35795,7 +35795,7 @@
|
||||
"id": "resource-092",
|
||||
"name": "资源92 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=92",
|
||||
"avatar": "/50-102.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -36186,7 +36186,7 @@
|
||||
"id": "resource-093",
|
||||
"name": "资源93 - 项目经理",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=93",
|
||||
"avatar": "/50-103.jpg",
|
||||
"department": "市场部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -36517,7 +36517,7 @@
|
||||
"id": "resource-094",
|
||||
"name": "资源94 - 工程师",
|
||||
"type": "工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=94",
|
||||
"avatar": "/50-104.jpg",
|
||||
"department": "工程部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -36863,7 +36863,7 @@
|
||||
"id": "resource-095",
|
||||
"name": "资源95 - 测试工程师",
|
||||
"type": "测试工程师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=95",
|
||||
"avatar": "/50-105.jpg",
|
||||
"department": "测试部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -37194,7 +37194,7 @@
|
||||
"id": "resource-096",
|
||||
"name": "资源96 - 研究员",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=96",
|
||||
"avatar": "/50-106.jpg",
|
||||
"department": "研发部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -37615,7 +37615,7 @@
|
||||
"id": "resource-097",
|
||||
"name": "资源97 - 试验主任",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=97",
|
||||
"avatar": "/50-107.jpg",
|
||||
"department": "临床部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -38066,7 +38066,7 @@
|
||||
"id": "resource-098",
|
||||
"name": "资源98 - 数据分析师",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=98",
|
||||
"avatar": "/50-108.jpg",
|
||||
"department": "质量部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -38517,7 +38517,7 @@
|
||||
"id": "resource-099",
|
||||
"name": "资源99 - 质量专员",
|
||||
"type": "质量专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=99",
|
||||
"avatar": "/50-109.jpg",
|
||||
"department": "注册部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
@@ -38863,7 +38863,7 @@
|
||||
"id": "resource-100",
|
||||
"name": "资源100 - 注册专员",
|
||||
"type": "注册专员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=100",
|
||||
"avatar": "/50-110.jpg",
|
||||
"department": "生产部",
|
||||
"skills": [
|
||||
"技能A",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"id": "resource-chen",
|
||||
"name": "首席研究员 Dr. Chen",
|
||||
"type": "研究员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=1",
|
||||
"avatar": "/50-11.jpg",
|
||||
"department": "临床研究部",
|
||||
"skills": ["临床试验管理", "项目规划", "团队领导"],
|
||||
"utilization": 85,
|
||||
@@ -49,7 +49,7 @@
|
||||
"id": "resource-wang",
|
||||
"name": "I期试验主任 Dr. Wang",
|
||||
"type": "试验主任",
|
||||
"avatar": "https://i.pravatar.cc/150?img=2",
|
||||
"avatar": "/50-12.jpg",
|
||||
"department": "I期临床研究组",
|
||||
"skills": ["I期试验设计", "药代动力学", "安全性评估"],
|
||||
"utilization": 92,
|
||||
@@ -94,7 +94,7 @@
|
||||
"id": "resource-li",
|
||||
"name": "方案设计师 李明",
|
||||
"type": "方案设计师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=3",
|
||||
"avatar": "/50-13.jpg",
|
||||
"department": "临床研究部",
|
||||
"skills": ["方案设计", "伦理审查", "监管申报"],
|
||||
"utilization": 75,
|
||||
@@ -128,7 +128,7 @@
|
||||
"id": "resource-zhang",
|
||||
"name": "数据分析师 张伟",
|
||||
"type": "数据分析师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=4",
|
||||
"avatar": "/50-14.jpg",
|
||||
"department": "数据管理与统计部",
|
||||
"skills": ["统计分析", "数据可视化", "报告撰写"],
|
||||
"utilization": 68,
|
||||
@@ -173,7 +173,7 @@
|
||||
"id": "resource-liu",
|
||||
"name": "质量保证专员 刘芳",
|
||||
"type": "质量保证",
|
||||
"avatar": "https://i.pravatar.cc/150?img=5",
|
||||
"avatar": "/50-15.jpg",
|
||||
"department": "质量保证部",
|
||||
"skills": ["GCP审计", "质量控制", "SOP管理"],
|
||||
"utilization": 88,
|
||||
@@ -208,7 +208,7 @@
|
||||
"id": "resource-zhao",
|
||||
"name": "II期试验协调员 赵敏",
|
||||
"type": "试验协调员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=6",
|
||||
"avatar": "/50-16.jpg",
|
||||
"department": "II期临床研究组",
|
||||
"skills": ["试验协调", "中心管理", "受试者跟踪"],
|
||||
"utilization": 55,
|
||||
@@ -235,7 +235,7 @@
|
||||
"id": "resource-wu",
|
||||
"name": "设备管理员 吴强",
|
||||
"type": "设备管理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=7",
|
||||
"avatar": "/50-17.jpg",
|
||||
"department": "设备与后勤部",
|
||||
"skills": ["设备维护", "库存管理", "供应链协调"],
|
||||
"utilization": 42,
|
||||
@@ -262,7 +262,7 @@
|
||||
"id": "resource-huang",
|
||||
"name": "III期项目经理 黄建国",
|
||||
"type": "项目经理",
|
||||
"avatar": "https://i.pravatar.cc/150?img=8",
|
||||
"avatar": "/50-18.jpg",
|
||||
"department": "III期临床研究组",
|
||||
"skills": ["大型试验管理", "多中心协调", "里程碑管理"],
|
||||
"utilization": 35,
|
||||
@@ -282,7 +282,7 @@
|
||||
"id": "resource-sun",
|
||||
"name": "生物统计师 孙丽",
|
||||
"type": "生物统计师",
|
||||
"avatar": "https://i.pravatar.cc/150?img=9",
|
||||
"avatar": "/50-19.jpg",
|
||||
"department": "数据管理与统计部",
|
||||
"skills": ["统计建模", "样本量计算", "数据分析"],
|
||||
"utilization": 95,
|
||||
@@ -338,7 +338,7 @@
|
||||
"id": "resource-zhou",
|
||||
"name": "临床监查员 周杰",
|
||||
"type": "临床监查员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=10",
|
||||
"avatar": "/50-20.jpg",
|
||||
"department": "临床监查部",
|
||||
"skills": ["现场监查", "数据核查", "问题解决"],
|
||||
"utilization": 110,
|
||||
@@ -394,7 +394,7 @@
|
||||
"id": "resource-xu",
|
||||
"name": "医学撰写员 徐静",
|
||||
"type": "医学撰写员",
|
||||
"avatar": "https://i.pravatar.cc/150?img=11",
|
||||
"avatar": "/50-21.jpg",
|
||||
"department": "医学事务部",
|
||||
"skills": ["研究报告撰写", "文献检索", "医学翻译"],
|
||||
"utilization": 72,
|
||||
@@ -439,7 +439,7 @@
|
||||
"id": "resource-ma",
|
||||
"name": "药物警戒专员 马云飞",
|
||||
"type": "药物警戒",
|
||||
"avatar": "https://i.pravatar.cc/150?img=12",
|
||||
"avatar": "/50-22.jpg",
|
||||
"department": "药物警戒部",
|
||||
"skills": ["不良事件管理", "安全性报告", "信号检测"],
|
||||
"utilization": 88,
|
||||
@@ -475,7 +475,7 @@
|
||||
"id": "resource-gao",
|
||||
"name": "注册事务专员 高敏",
|
||||
"type": "注册事务",
|
||||
"avatar": "https://i.pravatar.cc/150?img=13",
|
||||
"avatar": "/50-23.jpg",
|
||||
"department": "注册事务部",
|
||||
"skills": ["IND申报", "监管沟通", "文件管理"],
|
||||
"utilization": 65,
|
||||
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -234,7 +234,7 @@ const addMilestone = () => {
|
||||
type: 'milestone',
|
||||
icon: 'diamond',
|
||||
})
|
||||
console.log('milestones: ', milestones.value)
|
||||
//console.log('milestones: ', milestones.value)
|
||||
newTask.value = { name: '', startDate: '', endDate: '' }
|
||||
showAddMilestoneDialog.value = false
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ const addMilestone = () => {
|
||||
type: 'milestone',
|
||||
icon: 'diamond',
|
||||
})
|
||||
console.log('milestones: ', milestones.value)
|
||||
//console.log('milestones: ', milestones.value)
|
||||
newTask.value = { name: '', startDate: '', endDate: '' }
|
||||
showAddMilestoneDialog.value = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// 替换数据集中的远程头像URL为本地路径
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const files = [
|
||||
'demo/data.json',
|
||||
'demo/data-resources.json',
|
||||
'demo/data-resources-large.json',
|
||||
]
|
||||
|
||||
files.forEach(filePath => {
|
||||
const fullPath = path.join(__dirname, '..', filePath)
|
||||
console.log(`Processing ${filePath}...`)
|
||||
|
||||
try {
|
||||
let content = fs.readFileSync(fullPath, 'utf-8')
|
||||
|
||||
// 替换 https://i.pravatar.cc/150?img=X 为 /50-(X+10).jpg
|
||||
content = content.replace(/https:\/\/i\.pravatar\.cc\/150\?img=(\d+)/g, (match, num) => {
|
||||
const newNum = parseInt(num) + 10
|
||||
return `/50-${newNum}.jpg`
|
||||
})
|
||||
|
||||
fs.writeFileSync(fullPath, content, 'utf-8')
|
||||
console.log(`✓ ${filePath} updated successfully`)
|
||||
} catch (error) {
|
||||
console.error(`✗ Error processing ${filePath}:`, error.message)
|
||||
}
|
||||
})
|
||||
|
||||
console.log('\nAll files processed!')
|
||||
@@ -13,6 +13,7 @@ interface AssigneeOption {
|
||||
key?: string | number
|
||||
value: string | number
|
||||
label: string
|
||||
avatar?: string // v1.9.0 资源头像
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -367,6 +368,9 @@ watch(
|
||||
// 使用 nextTick 确保 resetForm 的响应式更新完成后再赋值
|
||||
nextTick(() => {
|
||||
Object.assign(formData, taskData)
|
||||
|
||||
// v1.9.0 从 assignee/avatar 映射到 resources
|
||||
mapAssigneeToResources()
|
||||
})
|
||||
} else if (props.task && !props.isEdit) {
|
||||
// 新建模式,重置表单
|
||||
@@ -489,6 +493,10 @@ const handleSubmit = async () => {
|
||||
}
|
||||
try {
|
||||
submitting.value = true
|
||||
|
||||
// v1.9.0 提交前从 resources 映射到 assignee/avatar
|
||||
mapResourcesToAssignee()
|
||||
|
||||
const taskData: Task = {
|
||||
...formData,
|
||||
id: props.isEdit && props.task ? props.task.id : Date.now(),
|
||||
@@ -667,18 +675,6 @@ function confirmTimer(desc: string) {
|
||||
handleStartTimer(desc)
|
||||
}
|
||||
|
||||
// 处理负责人变更
|
||||
const handleAssigneeChanged = (event: Event) => {
|
||||
const value = (event.target as HTMLSelectElement).value
|
||||
// 通过value过滤props.assigneeOptions获取对应的label
|
||||
const selected = props.assigneeOptions?.find(option => option.value === value)
|
||||
if (selected) {
|
||||
// 这里可以根据需要处理选中的负责人信息
|
||||
// 例如,可以将负责人名称存储在formData中
|
||||
formData.assigneeName = selected.label
|
||||
}
|
||||
}
|
||||
|
||||
// v1.9.0 资源占比管理
|
||||
const addResource = () => {
|
||||
if (!formData.resources) {
|
||||
@@ -715,6 +711,54 @@ const handleResourceChange = (index: number, field: 'id' | 'percent', value: str
|
||||
}
|
||||
}
|
||||
|
||||
// v1.9.0 从 task.assignee/avatar 映射到 resources
|
||||
const mapAssigneeToResources = () => {
|
||||
if (!formData.resources) {
|
||||
formData.resources = []
|
||||
}
|
||||
|
||||
// 如果已有resources数据,保持不变(优先使用resources)
|
||||
if (formData.resources && formData.resources.length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// 从 assignee/avatar 映射到 resources
|
||||
const assignees = Array.isArray(formData.assignee) ? formData.assignee : (formData.assignee ? [formData.assignee] : [])
|
||||
|
||||
formData.resources = assignees.map((assigneeId) => {
|
||||
// 查找对应的assigneeOption获取名称
|
||||
const option = props.assigneeOptions?.find(opt => opt.value === assigneeId)
|
||||
return {
|
||||
id: assigneeId,
|
||||
name: option?.label || String(assigneeId),
|
||||
percent: 100, // 默认100%
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// v1.9.0 从 resources 映射回 task.assignee/avatar 数组
|
||||
const mapResourcesToAssignee = () => {
|
||||
if (!formData.resources || formData.resources.length === 0) {
|
||||
formData.assignee = ''
|
||||
formData.avatar = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 映射 assignee 数组
|
||||
const assignees = formData.resources.map(r => String(r.id)).filter(id => id)
|
||||
formData.assignee = assignees.length > 1 ? assignees : (assignees[0] || '')
|
||||
|
||||
// 映射 avatar 数组(从assigneeOptions中查找对应的avatar)
|
||||
const avatars = formData.resources
|
||||
.map(r => {
|
||||
const option = props.assigneeOptions?.find(opt => opt.value === r.id)
|
||||
return option?.avatar || ''
|
||||
})
|
||||
.filter(avatar => avatar)
|
||||
|
||||
formData.avatar = avatars.length > 1 ? avatars : (avatars[0] || '')
|
||||
}
|
||||
|
||||
// 计算任务状态
|
||||
const taskStatus = computed(() => {
|
||||
if (!props.task) return { text: '', color: '', bgColor: '', borderColor: '' }
|
||||
@@ -949,24 +993,17 @@ const taskStatus = computed(() => {
|
||||
<span v-if="errors.type" class="error-text">{{ errors.type }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="task-assignee">{{ t.assignee }}</label>
|
||||
<select id="task-assignee" v-model="formData.assignee" class="form-select" @change="handleAssigneeChanged">
|
||||
<option value="">{{ t.selectAssignee }}</option>
|
||||
<option
|
||||
v-for="assignee in props.assigneeOptions"
|
||||
:key="assignee.key ?? assignee.value"
|
||||
:value="assignee.value"
|
||||
>
|
||||
{{ assignee.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- v1.9.0 资源分配(含占比配置) -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ t.resourceAllocation || '资源分配' }}</label>
|
||||
<div class="resource-list">
|
||||
<!-- 资源分配标题行 -->
|
||||
<div class="resource-header">
|
||||
<span class="resource-header-label">资源名称</span>
|
||||
<span class="resource-header-label">占用比例</span>
|
||||
<span class="resource-header-action"></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(resource, index) in formData.resources"
|
||||
:key="index"
|
||||
@@ -982,32 +1019,22 @@ const taskStatus = computed(() => {
|
||||
v-for="assignee in props.assigneeOptions"
|
||||
:key="assignee.key ?? assignee.value"
|
||||
:value="assignee.value"
|
||||
:data-avatar="assignee.avatar"
|
||||
>
|
||||
{{ assignee.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div class="percent-input-wrapper">
|
||||
<select
|
||||
v-model.number="resource.percent"
|
||||
class="form-select percent-select"
|
||||
@change="handleResourceChange(index, 'percent', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option :value="25">25%</option>
|
||||
<option :value="50">50%</option>
|
||||
<option :value="75">75%</option>
|
||||
<option :value="100">100%</option>
|
||||
</select>
|
||||
<input
|
||||
v-model.number="resource.percent"
|
||||
type="number"
|
||||
class="form-input percent-input"
|
||||
min="20"
|
||||
max="100"
|
||||
@input="handleResourceChange(index, 'percent', ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<span class="percent-unit">%</span>
|
||||
</div>
|
||||
<select
|
||||
v-model.number="resource.percent"
|
||||
class="form-select percent-select"
|
||||
@change="handleResourceChange(index, 'percent', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option :value="25">25%</option>
|
||||
<option :value="50">50%</option>
|
||||
<option :value="75">75%</option>
|
||||
<option :value="100">100%</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@@ -1015,7 +1042,12 @@ const taskStatus = computed(() => {
|
||||
title="删除资源"
|
||||
@click="removeResource(index)"
|
||||
>
|
||||
×
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -1375,7 +1407,7 @@ const taskStatus = computed(() => {
|
||||
.form-input,
|
||||
.form-select,
|
||||
.form-textarea {
|
||||
padding: 12px 16px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--gantt-border-medium, #dcdfe6);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
@@ -1681,54 +1713,81 @@ const taskStatus = computed(() => {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 资源分配标题行 */
|
||||
.resource-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--gantt-bg-toolbar, #fafafa);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--gantt-border-light, #ebeef5);
|
||||
}
|
||||
|
||||
.resource-header-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--gantt-text-secondary, #606266);
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.resource-header-label:first-child {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resource-header-label:nth-child(2) {
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resource-header-action {
|
||||
width: 48px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--gantt-bg-secondary, #f5f7fa);
|
||||
border-radius: 4px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.resource-select {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
min-width: 180px;
|
||||
max-width: 280px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.percent-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
.resource-select option {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.percent-select {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.percent-input {
|
||||
width: 60px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.percent-unit {
|
||||
font-size: 14px;
|
||||
color: var(--gantt-text-secondary, #606266);
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-remove-resource {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 1px solid var(--gantt-border-base, #dcdfe6);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--gantt-text-secondary, #606266);
|
||||
color: var(--gantt-danger, #f56c6c);
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-remove-resource:hover {
|
||||
@@ -1739,7 +1798,7 @@ const taskStatus = computed(() => {
|
||||
|
||||
.btn-add-resource {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
padding: 10px;
|
||||
background: transparent;
|
||||
border: 1px dashed var(--gantt-border-base, #dcdfe6);
|
||||
border-radius: 4px;
|
||||
@@ -1747,6 +1806,7 @@ const taskStatus = computed(() => {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.btn-add-resource:hover {
|
||||
@@ -1754,6 +1814,16 @@ const taskStatus = computed(() => {
|
||||
background: var(--gantt-primary-light, #ecf5ff);
|
||||
}
|
||||
|
||||
/* 暗黑模式 */
|
||||
:global(html[data-theme='dark']) .resource-header {
|
||||
background: var(--gantt-bg-toolbar, rgba(255, 255, 255, 0.03)) !important;
|
||||
border-color: var(--gantt-border-light, rgba(255, 255, 255, 0.1)) !important;
|
||||
}
|
||||
|
||||
:global(html[data-theme='dark']) .resource-header-label {
|
||||
color: var(--gantt-text-secondary, #a8a8a8) !important;
|
||||
}
|
||||
|
||||
:global(html[data-theme='dark']) .resource-item {
|
||||
background: var(--gantt-bg-secondary, rgba(255, 255, 255, 0.05)) !important;
|
||||
}
|
||||
|
||||
@@ -127,30 +127,20 @@ const getConflictTasksForTask = (resourceId: string | number, taskId: string | n
|
||||
const currentStart = new Date(currentTask.startDate).getTime()
|
||||
const currentEnd = new Date(currentTask.endDate).getTime()
|
||||
|
||||
// 获取当前资源在当前任务中的占比
|
||||
const getCurrentPercent = (task: Task): number => {
|
||||
if (!task.resources || !Array.isArray(task.resources)) return 100
|
||||
const allocation = task.resources.find((r: any) => String(r.id) === String(resourceId))
|
||||
return allocation?.percent ?? 100
|
||||
}
|
||||
|
||||
const currentPercent = getCurrentPercent(currentTask)
|
||||
|
||||
// 找出所有与当前任务时间重叠且导致超载的任务
|
||||
// v1.9.7 修复:返回所有与当前任务时间重叠的冲突任务
|
||||
// 不需要再次验证占比相加是否超过100%,因为resourceConflicts已经包含了所有冲突的任务ID
|
||||
// 当多个任务同时重叠时(如3个任务各75%),应该全部返回,而不是只返回第一个两两超载的任务
|
||||
const conflictTasks = resource.tasks.filter(task => {
|
||||
if (task.id === taskId) return false
|
||||
if (!task.startDate || !task.endDate) return false
|
||||
// 任务必须在冲突任务集合中
|
||||
if (!conflictTaskIds.has(task.id)) return false
|
||||
|
||||
const taskStart = new Date(task.startDate).getTime()
|
||||
const taskEnd = new Date(task.endDate).getTime()
|
||||
|
||||
// 检查时间重叠
|
||||
if (!(currentStart < taskEnd && taskStart < currentEnd)) return false
|
||||
|
||||
// 检查占比相加是否超过100%
|
||||
const taskPercent = getCurrentPercent(task)
|
||||
return currentPercent + taskPercent > 100
|
||||
// 检查时间重叠:只要与当前任务有时间交集,就是冲突任务
|
||||
return currentStart < taskEnd && taskStart < currentEnd
|
||||
})
|
||||
|
||||
return conflictTasks
|
||||
@@ -186,7 +176,6 @@ let resourceTaskLayoutsCallCount = 0
|
||||
const resourceTaskLayouts = computed(() => {
|
||||
resourceTaskLayoutsCallCount++
|
||||
const startTime = performance.now()
|
||||
console.log(`[🔍 Performance] resourceTaskLayouts computed #${resourceTaskLayoutsCallCount} triggered`)
|
||||
|
||||
const layoutMap = new Map<string | number, {
|
||||
taskRowMap: Map<string | number, number>,
|
||||
@@ -195,7 +184,6 @@ const resourceTaskLayouts = computed(() => {
|
||||
}>()
|
||||
|
||||
if (viewMode.value !== 'resource') {
|
||||
console.log(`[🔍 Performance] resourceTaskLayouts: skipped (not resource view)`)
|
||||
return layoutMap
|
||||
}
|
||||
|
||||
@@ -233,7 +221,6 @@ const resourceTaskLayouts = computed(() => {
|
||||
// 输出性能日志
|
||||
if (resources.length > 0) {
|
||||
const hitRate = ((cacheHits / resources.length) * 100).toFixed(1)
|
||||
console.log(`[Performance] resourceTaskLayouts: ${duration}ms | computed ${resources.length} resources, cache hit rate: ${hitRate}%`)
|
||||
}
|
||||
|
||||
return layoutMap
|
||||
@@ -244,7 +231,6 @@ watch(dataSource, () => {
|
||||
if (layoutCache.size > 100) {
|
||||
const keysToDelete = Array.from(layoutCache.keys()).slice(0, layoutCache.size - 100)
|
||||
keysToDelete.forEach(key => layoutCache.delete(key))
|
||||
console.log(`[Performance] Layout cache cleaned: removed ${keysToDelete.length} entries`)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -285,11 +271,9 @@ let resourceRowPositionsCallCount = 0
|
||||
const resourceRowPositions = computed(() => {
|
||||
resourceRowPositionsCallCount++
|
||||
const startTime = performance.now()
|
||||
console.log(`[🔍 Performance] resourceRowPositions computed #${resourceRowPositionsCallCount} triggered`)
|
||||
const positions = new Map<string | number, number>()
|
||||
|
||||
if (viewMode.value !== 'resource') {
|
||||
console.log(`[🔍 Performance] resourceRowPositions: skipped (not resource view)`)
|
||||
return positions
|
||||
}
|
||||
|
||||
@@ -328,12 +312,6 @@ const resourceRowPositions = computed(() => {
|
||||
const endTime = performance.now()
|
||||
const duration = (endTime - startTime).toFixed(2)
|
||||
|
||||
if (processedCount < resources.length) {
|
||||
console.log(`[Phase2] resourceRowPositions: ${duration}ms | lazy-computed ${processedCount}/${resources.length} resources`)
|
||||
} else {
|
||||
console.log(`[Performance] resourceRowPositions: ${duration}ms | processed ${resources.length} resources`)
|
||||
}
|
||||
|
||||
return positions
|
||||
})
|
||||
|
||||
@@ -1595,9 +1573,6 @@ watch([timelineScrollLeft, timelineContainerWidth], ([newScrollLeft, newWidth])
|
||||
// v1.9.5 P2-1优化 - 计算水平方向可见的时间范围(用于TaskBar过滤)
|
||||
const visibleTimeRange = computed(() => {
|
||||
visibleTimeRangeCallCount++
|
||||
if (visibleTimeRangeCallCount % 10 === 0) {
|
||||
console.log(`[Performance] visibleTimeRange called ${visibleTimeRangeCallCount} times`)
|
||||
}
|
||||
|
||||
const scrollLeft = debouncedScrollLeft.value
|
||||
const containerWidth = debouncedContainerWidth.value || timelineContainerWidth.value
|
||||
@@ -1623,8 +1598,6 @@ const visibleTimeRange = computed(() => {
|
||||
if (visibleTimeRangeCallCount % 10 === 0) {
|
||||
const scale = currentTimeScale.value
|
||||
const daysDiff = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24))
|
||||
console.log(`[Sprint2-Debug] visibleTimeRange: ${scale}, scrollLeft=${scrollLeft.toFixed(0)}, containerWidth=${containerWidth.toFixed(0)}, buffer=${bufferWidth.toFixed(0)}`)
|
||||
console.log(`[Sprint2-Debug] Date range: ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]} (${daysDiff} days)`)
|
||||
}
|
||||
|
||||
return { startDate, endDate }
|
||||
@@ -1749,7 +1722,6 @@ const visibleTaskRange = computed(() => {
|
||||
const startTime = performance.now()
|
||||
const scrollTop = timelineBodyScrollTop.value
|
||||
const containerHeight = timelineBodyHeight.value || 600
|
||||
console.log(`[🔍 Performance] visibleTaskRange computed #${visibleTaskRangeCallCount} triggered | scrollTop: ${scrollTop}`)
|
||||
|
||||
if (viewMode.value === 'resource') {
|
||||
// 资源视图:基于资源行的实际高度计算可见范围
|
||||
@@ -1787,7 +1759,6 @@ const visibleTaskRange = computed(() => {
|
||||
|
||||
const endTime = performance.now()
|
||||
const duration = (endTime - startTime).toFixed(2)
|
||||
console.log(`[Performance] visibleTaskRange: ${duration}ms | range: ${startIndex}-${endIndex} / ${resources.length}`)
|
||||
|
||||
return {
|
||||
startIndex: Math.max(0, startIndex),
|
||||
@@ -1977,7 +1948,6 @@ const rebuildResourceTaskQueues = () => {
|
||||
// v1.9.6 Sprint4 - 日志:记录实际处理的资源数量
|
||||
const totalResources = (dataSource.value as Resource[]).length
|
||||
const visibleResourcesCount = visibleResources.value.length
|
||||
console.log(`[Sprint4] rebuildResourceTaskQueues: processing ${visibleResourcesCount}/${totalResources} visible resources`)
|
||||
|
||||
// v1.9.6 Sprint2(P5) - 渲染缓存增量更新:保留已有记录
|
||||
const currentCache = new Map(taskBarRenderCache.value)
|
||||
@@ -2055,13 +2025,9 @@ const rebuildResourceTaskQueues = () => {
|
||||
resourceRenderPhase.value = 'visible'
|
||||
taskBarRenderCache.value = newCache // 更新缓存
|
||||
|
||||
// v1.9.6 Sprint4 - 输出优化后的统计信息
|
||||
console.log(`[Sprint4] Will render ${totalVisibleTaskBars} TaskBars (from ${visibleResourcesCount} visible resources)`)
|
||||
|
||||
// v1.9.6 Sprint2(P5) - 输出缓存统计
|
||||
const totalTaskBars = newCache.size
|
||||
const cacheHitRate = totalTaskBars > 0 ? ((cachedCount / totalTaskBars) * 100).toFixed(1) : '0.0'
|
||||
console.log(`[Sprint2-P5] TaskBar cache: ${cachedCount}/${totalTaskBars} already rendered (${cacheHitRate}% hit rate)`)
|
||||
|
||||
scheduleResourceBatchRender()
|
||||
}
|
||||
@@ -2102,7 +2068,6 @@ watch(
|
||||
|
||||
if (updatedCount > 0) {
|
||||
taskBarRenderCache.value = cache
|
||||
console.log(`[Sprint2-P5] Marked ${updatedCount} TaskBars as rendered (cache size: ${cache.size})`)
|
||||
}
|
||||
},
|
||||
{ deep: false },
|
||||
@@ -2123,7 +2088,6 @@ const visibleResourcesWithFilteredTasks = computed(() => {
|
||||
// v1.9.6 Sprint2(P1) - 临时调试:每次都输出时间范围
|
||||
if (filteredTasksCallCount <= 5 || filteredTasksCallCount % 10 === 0) {
|
||||
const daysDiff = Math.ceil((visibleEndDate.getTime() - visibleStartDate.getTime()) / (1000 * 60 * 60 * 24))
|
||||
console.log(`[Sprint2-Debug] #${filteredTasksCallCount} visibleTimeRange: ${visibleStartDate.toISOString().split('T')[0]} to ${visibleEndDate.toISOString().split('T')[0]} (${daysDiff} days)`)
|
||||
}
|
||||
|
||||
// 性能监控统计
|
||||
@@ -2175,7 +2139,6 @@ const visibleResourcesWithFilteredTasks = computed(() => {
|
||||
// v1.9.6 Sprint2(P1) - 性能监控日志(每10次输出一次)
|
||||
if (filteredTasksCallCount % 5 === 0 && totalOriginalTasks > 0) {
|
||||
const filterRate = ((1 - totalFilteredTasks / totalOriginalTasks) * 100).toFixed(1)
|
||||
console.log(`[Sprint2-P1] visibleResourcesWithFilteredTasks #${filteredTasksCallCount}: ${totalFilteredTasks}/${totalOriginalTasks} taskbars, filtered: ${filterRate}%`)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -2572,7 +2535,6 @@ const contentHeight = computed(() => {
|
||||
|
||||
const endTime = performance.now()
|
||||
const duration = (endTime - startTime).toFixed(2)
|
||||
console.log(`[Performance] contentHeight: ${duration}ms | processed ${resources.length} resources`)
|
||||
|
||||
return Math.max(totalHeight, minHeight, timelineBodyHeight.value)
|
||||
}
|
||||
@@ -2640,7 +2602,6 @@ const generateTimelineData = (): any => {
|
||||
// 使用缓存版本提升性能
|
||||
const result = getCachedTimelineData()
|
||||
const duration = (performance.now() - startTime).toFixed(2)
|
||||
console.log(`[🔍 Performance] generateTimelineData: ${duration}ms | scale: ${currentTimeScale.value}`)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -2872,7 +2833,6 @@ const getGlobalWeekPosition = (monthIndex: number, weekIndex: number) => {
|
||||
// 更新时间刻度方法 - 供外部调用
|
||||
const updateTimeScale = (scale: TimelineScale) => {
|
||||
perfMonitor2.start(`updateTimeScale-${scale}`)
|
||||
console.log(`[🔧 Action] updateTimeScale: ${currentTimeScale.value} → ${scale}`)
|
||||
|
||||
currentTimeScale.value = scale
|
||||
|
||||
@@ -2958,7 +2918,6 @@ const updateTimeScale = (scale: TimelineScale) => {
|
||||
}
|
||||
|
||||
// 重新生成时间线数据
|
||||
console.log('[📊 Timeline Data] Regenerating timeline data for new scale...')
|
||||
timelineData.value = generateTimelineData()
|
||||
|
||||
// 等待DOM更新后触发多个重新计算事件
|
||||
@@ -3021,18 +2980,15 @@ watch(
|
||||
([newData, newScale]) => {
|
||||
positionCacheWatchCount++
|
||||
const watchStartTime = performance.now()
|
||||
console.log(`[🔍 Performance] positionCache watch triggered #${positionCacheWatchCount} | scale: ${newScale}`)
|
||||
|
||||
if (newData && newScale) {
|
||||
// 调用缓存构建(内部会判断是否需要重建)
|
||||
const cacheStartTime = performance.now()
|
||||
positionCache.buildCache(newData as any[], newScale)
|
||||
const cacheDuration = (performance.now() - cacheStartTime).toFixed(2)
|
||||
console.log(`[🔍 Performance] positionCache.buildCache: ${cacheDuration}ms`)
|
||||
}
|
||||
|
||||
const totalDuration = (performance.now() - watchStartTime).toFixed(2)
|
||||
console.log(`[🔍 Performance] positionCache watch completed: ${totalDuration}ms`)
|
||||
},
|
||||
{ immediate: true } // 立即执行,确保初始化时也构建缓存
|
||||
)
|
||||
@@ -3615,7 +3571,6 @@ const scrollToDate = (date: Date | string) => {
|
||||
// 更新任务
|
||||
const updateTask = (updatedTask: Task) => {
|
||||
perfMonitor2.start('updateTask')
|
||||
console.log('[🔧 Task Update] Task updated:', updatedTask.id)
|
||||
|
||||
// 不直接修改props数据,而是通过事件通知父组件
|
||||
// 触发全局事件,通知父组件更新数据
|
||||
@@ -3785,11 +3740,8 @@ const handleTaskBarDragEnd = (updatedTask: Task) => {
|
||||
|
||||
// 只有当行数发生变化时,才需要触发全量重绘
|
||||
if (newRowCount !== oldRowCount) {
|
||||
console.log(`[Auto-Layout] Resource ${targetResourceId} layout changed: ${oldRowCount} -> ${newRowCount} rows after drag`)
|
||||
// 行数变化,需要触发重绘
|
||||
taskBarRenderKey.value++
|
||||
} else {
|
||||
console.log(`[Auto-Layout] Resource ${targetResourceId} layout unchanged (${newRowCount} rows), skip render key update`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3833,10 +3785,7 @@ const handleTaskBarResizeEnd = (updatedTask: Task) => {
|
||||
|
||||
// 只有当行数发生变化时,才需要触发全量重绘
|
||||
if (newRowCount !== oldRowCount) {
|
||||
console.log(`[Auto-Layout] Resource ${targetResourceId} layout changed: ${oldRowCount} -> ${newRowCount} rows after resize`)
|
||||
taskBarRenderKey.value++
|
||||
} else {
|
||||
console.log(`[Auto-Layout] Resource ${targetResourceId} layout unchanged (${newRowCount} rows), skip render key update`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ watch(isDraggingTaskBar, (dragging) => {
|
||||
nextTick(() => {
|
||||
// 检查是否可以使用增量更新(单个TaskBar变化且有ID记录)
|
||||
if (lastChangedTaskId.value !== null) {
|
||||
console.log(`[GanttConflicts] Incremental update for task ${lastChangedTaskId.value}`)
|
||||
recalculateConflictsIncremental(lastChangedTaskId.value)
|
||||
lastChangedTaskId.value = null // 清除记录
|
||||
} else {
|
||||
@@ -136,12 +135,10 @@ watch(isDraggingTimeline, (dragging) => {
|
||||
if (dragging) {
|
||||
// 拖拽开始时立即清除Canvas
|
||||
clearCanvas()
|
||||
console.log('[GanttConflicts] Timeline drag started, canvas cleared')
|
||||
} else {
|
||||
// 拖拽结束后重新计算并绘制
|
||||
nextTick(() => {
|
||||
recalculateConflicts()
|
||||
console.log('[GanttConflicts] Timeline drag ended, conflicts recalculated')
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -194,7 +191,6 @@ function recalculateConflictsIncremental(changedTaskId: string | number) {
|
||||
// 找到变化的任务
|
||||
const changedTask = props.tasks.find(t => t.id === changedTaskId)
|
||||
if (!changedTask) {
|
||||
console.log(`[GanttConflicts] Changed task ${changedTaskId} not found, fallback to full recalculation`)
|
||||
recalculateConflicts()
|
||||
return
|
||||
}
|
||||
@@ -210,8 +206,6 @@ function recalculateConflictsIncremental(changedTaskId: string | number) {
|
||||
return !(taskEnd < changedStart || taskStart > changedEnd)
|
||||
})
|
||||
|
||||
console.log(`[GanttConflicts] Incremental: ${affectedTasks.length} affected tasks (out of ${props.tasks.length} total)`)
|
||||
|
||||
// 只对受影响的任务进行冲突检测
|
||||
const newConflicts = detectConflicts(affectedTasks, props.resourceId)
|
||||
|
||||
@@ -222,8 +216,6 @@ function recalculateConflictsIncremental(changedTaskId: string | number) {
|
||||
return !zone.tasks.some(task => affectedTaskIds.has(task.id))
|
||||
})
|
||||
|
||||
console.log(`[GanttConflicts] Incremental: kept ${unchangedConflicts.length} unchanged conflicts, adding ${newConflicts.length} new conflicts`)
|
||||
|
||||
// 合并未变化的冲突和新计算的冲突
|
||||
const allConflicts = [...unchangedConflicts.map(z => ({
|
||||
startDate: z.startDate,
|
||||
@@ -301,8 +293,6 @@ function recalculateConflictsIncremental(changedTaskId: string | number) {
|
||||
const endTime = performance.now()
|
||||
const elapsed = endTime - startTime
|
||||
|
||||
console.log(`[GanttConflicts] Incremental update completed in ${elapsed.toFixed(2)}ms, ${conflictZones.value.length} total conflict zones`)
|
||||
|
||||
// 使用增量重绘
|
||||
renderConflictsIncremental()
|
||||
}
|
||||
@@ -321,9 +311,6 @@ function recalculateConflicts() {
|
||||
return `${t.name}(${percent}%)`
|
||||
}).join(', ')
|
||||
|
||||
console.log(`[GanttConflicts] Resource ${props.resourceId}: ${props.tasks.length} tasks [${tasksInfo}], detected ${conflicts.length} conflict zones`)
|
||||
console.log(`[GanttConflicts] Props - scrollLeft: ${props.scrollLeft}, containerWidth: ${props.containerWidth}, width: ${props.width}`)
|
||||
|
||||
// v1.9.4 P1优化 - 使用坐标缓存
|
||||
conflictZones.value = conflicts.map((zone) => {
|
||||
// 生成缓存key(基于时间戳避免日期对象比较)
|
||||
@@ -432,8 +419,6 @@ function recalculateConflicts() {
|
||||
} else {
|
||||
renderConflictsIncremental()
|
||||
}
|
||||
|
||||
console.log(`[GanttConflicts] Resource ${props.resourceId}: Rendered ${conflictZones.value.length} conflict zones on canvas`)
|
||||
}
|
||||
|
||||
// 计算冲突区域在Canvas上的位置(与TaskBar使用相同逻辑)
|
||||
@@ -784,19 +769,15 @@ function detectChangedZones(
|
||||
function drawConflictZone(ctx: CanvasRenderingContext2D, zone: ConflictZone) {
|
||||
// v1.9.6 修复:使用 === undefined 检查,避免 left=0 时被错误跳过
|
||||
if (zone.left === undefined || zone.width === undefined || zone.width <= 0) {
|
||||
console.log(`[GanttConflicts] drawConflictZone skipped: left=${zone.left}, width=${zone.width}`)
|
||||
return
|
||||
}
|
||||
|
||||
// 视口裁剪优化
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas || zone.left + zone.width < 0 || zone.left > canvas.width) {
|
||||
console.log(`[GanttConflicts] drawConflictZone out of viewport: left=${zone.left}, width=${zone.width}, canvas.width=${canvas?.width}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[GanttConflicts] Drawing zone: left=${zone.left}, width=${zone.width}, top=${zone.top}, height=${zone.height}, level=${zone.level}`)
|
||||
|
||||
// 绘制纹理背景
|
||||
drawTextureBackground(ctx, zone)
|
||||
|
||||
@@ -811,18 +792,14 @@ function drawConflictZone(ctx: CanvasRenderingContext2D, zone: ConflictZone) {
|
||||
function renderConflicts() {
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) {
|
||||
console.log('[GanttConflicts] renderConflicts: canvas not found')
|
||||
return
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
console.log('[GanttConflicts] renderConflicts: context not found')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[GanttConflicts] renderConflicts: canvas=${canvas.width}x${canvas.height}, zones=${conflictZones.value.length}`)
|
||||
|
||||
// 清空Canvas
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
@@ -838,13 +815,11 @@ function renderConflicts() {
|
||||
function clearCanvas() {
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) {
|
||||
console.log('[GanttConflicts] clearCanvas: canvas not found')
|
||||
return
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
console.log('[GanttConflicts] clearCanvas: context not found')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -853,8 +828,6 @@ function clearCanvas() {
|
||||
|
||||
// 重置冲突区域列表,强制全量重绘(不清空纹理缓存,可以复用)
|
||||
previousConflictZones.value = []
|
||||
|
||||
console.log('[GanttConflicts] Canvas cleared, ready for redraw')
|
||||
}
|
||||
|
||||
// 绘制纹理背景
|
||||
@@ -987,7 +960,6 @@ function drawTopWarning(ctx: CanvasRenderingContext2D, zone: ConflictZone) {
|
||||
onMounted(() => {
|
||||
// v1.9.6 修复:资源视图使用虚拟滚动,可见的资源行必然在视口内
|
||||
// 直接计算冲突,不需要IntersectionObserver延迟渲染
|
||||
console.log(`[GanttConflicts] Resource ${props.resourceId}: mounted, will calculate conflicts immediately`)
|
||||
nextTick(() => {
|
||||
recalculateConflicts()
|
||||
})
|
||||
|
||||
@@ -197,6 +197,7 @@ const conflictInfoList = computed(() => {
|
||||
// 计算当前任务的资源占比
|
||||
const currentPercent = props.resourcePercent || 100
|
||||
|
||||
// v1.9.8 修改:只显示冲突任务自己的投入占比
|
||||
return props.conflictTasks.map(conflictTask => {
|
||||
if (!conflictTask.startDate || !conflictTask.endDate) return null
|
||||
|
||||
@@ -214,14 +215,10 @@ const conflictInfoList = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// 计算重叠时间段
|
||||
// 计算当前任务与该冲突任务的重叠时间段
|
||||
const overlapStart = Math.max(currentStart, conflictStart)
|
||||
const overlapEnd = Math.min(currentEnd, conflictEnd)
|
||||
|
||||
// 计算超载百分比
|
||||
const totalPercent = currentPercent + conflictPercent
|
||||
const overloadPercent = totalPercent - 100
|
||||
|
||||
const formatDate = (timestamp: number) => {
|
||||
const date = new Date(timestamp)
|
||||
return `${date.getMonth() + 1}/${date.getDate()}`
|
||||
@@ -231,14 +228,76 @@ const conflictInfoList = computed(() => {
|
||||
taskName: conflictTask.name,
|
||||
overlapStart: formatDate(overlapStart),
|
||||
overlapEnd: formatDate(overlapEnd),
|
||||
currentPercent,
|
||||
conflictPercent,
|
||||
totalPercent,
|
||||
overloadPercent,
|
||||
conflictPercent, // 该冲突任务自己的投入占比
|
||||
}
|
||||
}).filter(Boolean)
|
||||
})
|
||||
|
||||
// v1.9.8 新增:计算总超载量(所有任务的总和 - 100%)
|
||||
const totalOverloadPercent = computed(() => {
|
||||
if (!props.hasConflict || !props.conflictTasks || props.conflictTasks.length === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const currentTask = props.task
|
||||
if (!currentTask.startDate || !currentTask.endDate) return 0
|
||||
|
||||
const currentStart = new Date(currentTask.startDate).getTime()
|
||||
const currentEnd = new Date(currentTask.endDate).getTime()
|
||||
const currentPercent = props.resourcePercent || 100
|
||||
|
||||
// 计算所有冲突任务在重叠时间段内的最大总占比
|
||||
let maxTotalPercent = currentPercent
|
||||
|
||||
// 收集所有涉及的任务(当前任务 + 所有冲突任务)
|
||||
const allTasks = [currentTask, ...props.conflictTasks]
|
||||
|
||||
// 找出所有任务的时间交集区域,计算最大总占比
|
||||
allTasks.forEach((task1, i) => {
|
||||
if (!task1.startDate || !task1.endDate) return
|
||||
const start1 = new Date(task1.startDate).getTime()
|
||||
const end1 = new Date(task1.endDate).getTime()
|
||||
|
||||
allTasks.forEach((task2, j) => {
|
||||
if (i >= j || !task2.startDate || !task2.endDate) return
|
||||
const start2 = new Date(task2.startDate).getTime()
|
||||
const end2 = new Date(task2.endDate).getTime()
|
||||
|
||||
// 检查是否有时间重叠
|
||||
if (start1 < end2 && start2 < end1) {
|
||||
// 计算该重叠区间的所有任务总占比
|
||||
const overlapStart = Math.max(start1, start2)
|
||||
const overlapEnd = Math.min(end1, end2)
|
||||
|
||||
let intervalTotal = 0
|
||||
allTasks.forEach(task => {
|
||||
if (!task.startDate || !task.endDate) return
|
||||
const tStart = new Date(task.startDate).getTime()
|
||||
const tEnd = new Date(task.endDate).getTime()
|
||||
|
||||
// 检查任务是否在该重叠区间内
|
||||
if (tStart < overlapEnd && tEnd > overlapStart) {
|
||||
let taskPercent = 100
|
||||
if (task.resources && Array.isArray(task.resources)) {
|
||||
const allocation = task.resources.find(
|
||||
(r: any) => String(r.id) === String(props.currentResourceId),
|
||||
)
|
||||
if (allocation && allocation.percent !== undefined) {
|
||||
taskPercent = allocation.percent
|
||||
}
|
||||
}
|
||||
intervalTotal += taskPercent
|
||||
}
|
||||
})
|
||||
|
||||
maxTotalPercent = Math.max(maxTotalPercent, intervalTotal)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return Math.max(0, maxTotalPercent - 100)
|
||||
})
|
||||
|
||||
// v1.9.4 P1优化 - 带防抖的鼠标进入处理
|
||||
const handleMouseEnter = () => {
|
||||
// 清除之前的隐藏定时器
|
||||
@@ -375,25 +434,26 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<!-- 冲突预警(有冲突时才显示) -->
|
||||
<div v-if="hasConflict && conflictInfoList.length > 0" class="conflict-section">
|
||||
<!-- 固定标题 -->
|
||||
<div class="conflict-header">
|
||||
<svg class="warning-icon" viewBox="0 0 24 24" width="14" height="14">
|
||||
<path fill="currentColor" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
||||
</svg>
|
||||
<span class="conflict-title">资源超载警告</span>
|
||||
<span class="total-overload">+{{ totalOverloadPercent }}%</span>
|
||||
</div>
|
||||
<div v-for="(info, index) in conflictInfoList" :key="index" class="conflict-item">
|
||||
<div class="conflict-task-name">与《{{ info.taskName }}》冲突</div>
|
||||
<div class="conflict-detail">
|
||||
<span class="conflict-label">冲突时段:</span>
|
||||
<span class="conflict-value">{{ info.overlapStart }} ~ {{ info.overlapEnd }}</span>
|
||||
</div>
|
||||
<div class="conflict-detail">
|
||||
<span class="conflict-label">资源占用:</span>
|
||||
<span class="conflict-value">{{ info.currentPercent }}% + {{ info.conflictPercent }}% = {{ info.totalPercent }}%</span>
|
||||
</div>
|
||||
<div class="conflict-detail overload-highlight">
|
||||
<span class="conflict-label">超载:</span>
|
||||
<span class="conflict-value">+{{ info.overloadPercent }}%</span>
|
||||
<!-- 可滚动的冲突列表 -->
|
||||
<div class="conflict-list-container">
|
||||
<div v-for="(info, index) in conflictInfoList" :key="index" class="conflict-item">
|
||||
<div class="conflict-task-name">与《{{ info.taskName }}》冲突</div>
|
||||
<div class="conflict-detail">
|
||||
<span class="conflict-label">冲突时段:</span>
|
||||
<span class="conflict-value">{{ info.overlapStart }} ~ {{ info.overlapEnd }}</span>
|
||||
</div>
|
||||
<div class="conflict-detail">
|
||||
<span class="conflict-label">任务投入占比:</span>
|
||||
<span class="conflict-value">{{ info.conflictPercent }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -489,8 +549,14 @@ onUnmounted(() => {
|
||||
.conflict-section {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2); /* 添加滚动支持 */
|
||||
max-height: 200px; /* 限制冲突区域最大高度 */
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 可滚动的冲突列表容器 - v1.9.8 */
|
||||
.conflict-list-container {
|
||||
max-height: 200px; /* 限制冲突列表最大高度 */
|
||||
overflow-y: auto; /* 垂直滚动 */
|
||||
overflow-x: hidden;
|
||||
/* 细滚动条样式 */
|
||||
@@ -499,21 +565,22 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
/* Webkit浏览器(Chrome、Safari、Edge)的细滚动条样式 */
|
||||
.conflict-section::-webkit-scrollbar {
|
||||
.conflict-list-container::-webkit-scrollbar {
|
||||
width: 4px; /* 细滚动条 */
|
||||
}
|
||||
|
||||
.conflict-section::-webkit-scrollbar-track {
|
||||
.conflict-list-container::-webkit-scrollbar-track {
|
||||
background: transparent; /* 透明轨道 */
|
||||
}
|
||||
|
||||
.conflict-section::-webkit-scrollbar-thumb {
|
||||
.conflict-list-container::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3); /* 半透明滑块 */
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.conflict-section::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.5); /* 悬停时更明显 */}
|
||||
.conflict-list-container::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.5); /* 悬停时更明显 */
|
||||
}
|
||||
|
||||
.conflict-header {
|
||||
display: flex;
|
||||
@@ -527,6 +594,14 @@ onUnmounted(() => {
|
||||
|
||||
.conflict-title {
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.total-overload {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #ff5252;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.conflict-item {
|
||||
|
||||