v1.9.0-rc.4 - TaskDrawer upgrades

This commit is contained in:
LINING-PC\lining
2026-02-02 22:38:19 +08:00
parent 8148969e1e
commit aacd7457fe
78 changed files with 406 additions and 305 deletions
+1 -1
View File
@@ -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')
+101 -101
View File
@@ -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",
+13 -13
View File
@@ -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,
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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
}
+35
View File
@@ -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!')
+144 -74
View File
@@ -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;
}
+6 -57
View File
@@ -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(() => {
// 使TaskBarID
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`)
}
// CanvasTaskBar使
@@ -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()
})
+104 -29
View File
@@ -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 {