From 0b80903f3d48b3c39570c097a4334cb7eb71d08f Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期日, 21 九月 2025 14:11:42 +0800
Subject: [PATCH] feat:通用任务初始化
---
ruoyi-system/src/main/resources/mapper/system/SysTaskLogMapper.xml | 104
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java | 103
ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskType.java | 43
ruoyi-ui/src/api/task.js | 159 +
prd/任务车辆关联.md | 301 ++
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAttachmentMapper.java | 77
sql/task_menu.sql | 27
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java | 84
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java | 174 +
ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskCodeGenerator.java | 38
ruoyi-system/src/main/resources/mapper/system/SysTaskAttachmentMapper.xml | 95
ruoyi-ui/src/router/index.js | 14
ruoyi-system/src/main/resources/mapper/system/SysTaskVehicleMapper.xml | 114 +
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java | 369 +++
sql/task_tables.sql | 113 +
ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskStatusValidator.java | 66
ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/VehicleType.java | 43
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAttachment.java | 126 +
ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskStatus.java | 46
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java | 205 +
ruoyi-ui/src/views/task/general/detail.vue | 564 +++++
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskLogMapper.java | 77
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskStatisticsVO.java | 98
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java | 567 +++++
prd/通用任务管理功能说明.md | 1156 ++++++++++
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskQueryVO.java | 140 +
ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml | 198 +
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java | 109
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskVehicleMapper.java | 103
README_TASK.md | 183 +
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java | 103
ruoyi-ui/src/views/task/general/index.vue | 535 ++++
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskVehicle.java | 152 +
sql/task_dict_data.sql | 39
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskLog.java | 152 +
ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskVehicleStatus.java | 46
sql/vehicle_info.sql | 5
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java | 158 +
38 files changed, 6,686 insertions(+), 0 deletions(-)
diff --git a/README_TASK.md b/README_TASK.md
new file mode 100644
index 0000000..a6c0dca
--- /dev/null
+++ b/README_TASK.md
@@ -0,0 +1,183 @@
+# 閫氱敤浠诲姟绠$悊鍔熻兘浣跨敤璇存槑
+
+## 鍔熻兘姒傝堪
+
+閫氱敤浠诲姟绠$悊鍔熻兘鏄熀浜庤嫢渚濇鏋跺紑鍙戠殑瀹屾暣浠诲姟绠$悊绯荤粺锛屾敮鎸佺淮淇繚鍏汇�佸姞娌逛换鍔°�佸叾浠栦换鍔$被鍨嬬殑鍏ㄧ敓鍛藉懆鏈熺鐞嗐��
+
+## 涓昏鍔熻兘
+
+### 1. 浠诲姟绠$悊
+- **浠诲姟鍒涘缓**锛氭敮鎸佸垱寤虹淮淇繚鍏汇�佸姞娌逛换鍔°�佸叾浠栫被鍨嬩换鍔�
+- **浠诲姟鏌ヨ**锛氭敮鎸佹寜浠诲姟缂栧彿銆佺被鍨嬨�佺姸鎬併�佹椂闂磋寖鍥寸瓑鏉′欢鏌ヨ
+- **浠诲姟缂栬緫**锛氭敮鎸佷慨鏀逛换鍔℃弿杩般�佸湴鍧�銆佹椂闂淬�佹墽琛屼汉绛変俊鎭�
+- **浠诲姟鍒犻櫎**锛氭敮鎸佸崟涓垨鎵归噺鍒犻櫎浠诲姟
+- **浠诲姟鍒嗛厤**锛氭敮鎸佸皢浠诲姟鍒嗛厤缁欐寚瀹氭墽琛屼汉
+- **鐘舵�佺鐞�**锛氭敮鎸佷换鍔$姸鎬佹祦杞紙寰呭紑濮嬧啋浠诲姟涓啋宸插畬鎴愶紝浠讳綍鐘舵�佸彲鍙栨秷锛�
+
+### 2. 杞﹁締绠$悊
+- **杞﹁締鍒嗛厤**锛氭敮鎸佸皢杞﹁締鍒嗛厤缁欎换鍔�
+- **鎵归噺鍒嗛厤**锛氭敮鎸佷竴娆℃�у垎閰嶅杈嗚溅缁欎换鍔�
+- **鍙栨秷鍒嗛厤**锛氭敮鎸佸彇娑堣溅杈嗕笌浠诲姟鐨勫叧鑱�
+- **杞﹁締鏌ヨ**锛氭敮鎸佹煡璇㈠彲鐢ㄨ溅杈嗗垪琛�
+
+### 3. 闄勪欢绠$悊
+- **鏂囦欢涓婁紶**锛氭敮鎸佷笂浼犱换鍔$浉鍏抽檮浠讹紙PDF銆丏OC銆佸浘鐗囩瓑锛�
+- **鏂囦欢涓嬭浇**锛氭敮鎸佷笅杞藉凡涓婁紶鐨勯檮浠�
+- **鏂囦欢鍒犻櫎**锛氭敮鎸佸垹闄や笉闇�瑕佺殑闄勪欢
+
+### 4. 鎿嶄綔鏃ュ織
+- **鏃ュ織璁板綍**锛氳嚜鍔ㄨ褰曟墍鏈変换鍔℃搷浣滄棩蹇�
+- **鏃ュ織鏌ヨ**锛氭敮鎸佹煡鐪嬩换鍔$殑鎿嶄綔鍘嗗彶
+- **鎿嶄綔杩借釜**锛氳褰曟搷浣滀汉銆佹搷浣滄椂闂淬�佹搷浣滃唴瀹圭瓑璇︾粏淇℃伅
+
+### 5. 缁熻鍔熻兘
+- **浠诲姟缁熻**锛氭彁渚涗换鍔℃�绘暟銆佸悇鐘舵�佷换鍔℃暟閲忋�佽秴鏃朵换鍔$瓑缁熻淇℃伅
+- **鎴戠殑浠诲姟**锛氭敮鎸佹煡鐪嬪綋鍓嶇敤鎴峰垱寤烘垨鍒嗛厤鐨勪换鍔�
+- **瓒呮椂鎻愰啋**锛氳嚜鍔ㄨ瘑鍒拰鎻愰啋瓒呮椂浠诲姟
+
+## 鏁版嵁搴撹〃缁撴瀯
+
+### 鏍稿績琛�
+1. **sys_task** - 浠诲姟涓昏〃
+2. **sys_task_vehicle** - 浠诲姟杞﹁締鍏宠仈琛�
+3. **sys_task_attachment** - 浠诲姟闄勪欢琛�
+4. **sys_task_log** - 浠诲姟鎿嶄綔鏃ュ織琛�
+
+### 鏁版嵁瀛楀吀
+- **sys_task_type** - 浠诲姟绫诲瀷瀛楀吀
+- **sys_task_status** - 浠诲姟鐘舵�佸瓧鍏�
+- **sys_vehicle_type** - 杞﹁締绫诲瀷瀛楀吀
+- **sys_vehicle_status** - 杞﹁締鐘舵�佸瓧鍏�
+- **sys_task_vehicle_status** - 浠诲姟杞﹁締鍏宠仈鐘舵�佸瓧鍏�
+
+## 閮ㄧ讲姝ラ
+
+### 1. 鏁版嵁搴撳垵濮嬪寲
+```sql
+-- 鎵ц琛ㄧ粨鏋勫垱寤鸿剼鏈�
+source sql/task_tables.sql;
+
+-- 鎵ц鏁版嵁瀛楀吀鍒濆鍖栬剼鏈�
+source sql/task_dict_data.sql;
+
+-- 鎵ц鑿滃崟鏉冮檺鍒濆鍖栬剼鏈�
+source sql/task_menu.sql;
+```
+
+### 2. 鍚庣閮ㄧ讲
+1. 纭繚鎵�鏈塉ava鏂囦欢宸叉纭斁缃埌瀵瑰簲鐩綍
+2. 閲嶆柊缂栬瘧椤圭洰锛歚mvn clean compile`
+3. 閲嶅惎搴旂敤鏈嶅姟
+
+### 3. 鍓嶇閮ㄧ讲
+1. 纭繚鎵�鏈塚ue鏂囦欢宸叉纭斁缃埌瀵瑰簲鐩綍
+2. 閲嶆柊鏋勫缓鍓嶇锛歚npm run build:prod`
+3. 閮ㄧ讲鍒癢eb鏈嶅姟鍣�
+
+### 4. 鏉冮檺閰嶇疆
+1. 鐧诲綍绯荤粺绠$悊鍚庡彴
+2. 杩涘叆"绯荤粺绠$悊" 鈫� "鑿滃崟绠$悊"
+3. 纭浠诲姟绠$悊鐩稿叧鑿滃崟宸叉纭垱寤�
+4. 涓虹浉搴旇鑹插垎閰嶄换鍔$鐞嗘潈闄�
+
+## API鎺ュ彛璇存槑
+
+### 浠诲姟绠$悊鎺ュ彛
+- `GET /task/list` - 鏌ヨ浠诲姟鍒楄〃
+- `GET /task/{taskId}` - 鑾峰彇浠诲姟璇︽儏
+- `POST /task` - 鍒涘缓浠诲姟
+- `PUT /task` - 鏇存柊浠诲姟
+- `DELETE /task/{taskIds}` - 鍒犻櫎浠诲姟
+- `PUT /task/{taskId}/assign` - 鍒嗛厤浠诲姟
+- `PUT /task/{taskId}/status` - 鏇存柊浠诲姟鐘舵��
+
+### 杞﹁締绠$悊鎺ュ彛
+- `GET /task/vehicle/list/{taskId}` - 鏌ヨ浠诲姟鍏宠仈杞﹁締
+- `GET /task/vehicle/available` - 鏌ヨ鍙敤杞﹁締
+- `POST /task/vehicle/assign/{taskId}` - 鍒嗛厤杞﹁締
+- `POST /task/vehicle/assign-batch/{taskId}` - 鎵归噺鍒嗛厤杞﹁締
+- `DELETE /task/vehicle/{taskId}/{vehicleId}` - 鍙栨秷杞﹁締鍒嗛厤
+
+### 闄勪欢绠$悊鎺ュ彛
+- `GET /task/attachment/list/{taskId}` - 鏌ヨ浠诲姟闄勪欢
+- `POST /task/attachment/upload/{taskId}` - 涓婁紶闄勪欢
+- `DELETE /task/attachment/{attachmentId}` - 鍒犻櫎闄勪欢
+
+### 缁熻鎺ュ彛
+- `GET /task/statistics` - 鑾峰彇浠诲姟缁熻淇℃伅
+- `GET /task/overdue` - 鑾峰彇瓒呮椂浠诲姟鍒楄〃
+- `GET /task/my` - 鑾峰彇鎴戠殑浠诲姟鍒楄〃
+
+## 浣跨敤璇存槑
+
+### 1. 鍒涘缓浠诲姟
+1. 杩涘叆"浠诲姟绠$悊" 鈫� "閫氱敤浠诲姟"
+2. 鐐瑰嚮"鏂板"鎸夐挳
+3. 濉啓浠诲姟淇℃伅锛�
+ - 浠诲姟绫诲瀷锛氶�夋嫨缁翠慨淇濆吇銆佸姞娌逛换鍔℃垨鍏朵粬
+ - 浠诲姟鎻忚堪锛氳缁嗘弿杩颁换鍔″唴瀹�
+ - 鍑哄彂鍦板潃锛氫换鍔¤捣濮嬪湴鐐�
+ - 鐩殑鍦板潃锛氫换鍔$洰鏍囧湴鐐�
+ - 璁″垝鏃堕棿锛氳缃鍒掑紑濮嬪拰缁撴潫鏃堕棿
+ - 鎵ц浜猴細閫夋嫨浠诲姟鎵ц浜�
+ - 澶囨敞锛氬叾浠栬鏄庝俊鎭�
+4. 鐐瑰嚮"纭畾"淇濆瓨
+
+### 2. 鍒嗛厤杞﹁締
+1. 鍦ㄤ换鍔″垪琛ㄤ腑鐐瑰嚮"鏌ョ湅"杩涘叆浠诲姟璇︽儏
+2. 鍦�"鍏宠仈杞﹁締"鍖哄煙鐐瑰嚮"鍒嗛厤杞﹁締"
+3. 閫夋嫨瑕佸垎閰嶇殑杞﹁締锛堝彲澶氶�夛級
+4. 濉啓鍒嗛厤澶囨敞
+5. 鐐瑰嚮"纭畾"瀹屾垚鍒嗛厤
+
+### 3. 涓婁紶闄勪欢
+1. 鍦ㄤ换鍔¤鎯呴〉闈㈢偣鍑�"涓婁紶闄勪欢"
+2. 閫夋嫨瑕佷笂浼犵殑鏂囦欢锛堟敮鎸丳DF銆丏OC銆佸浘鐗囩瓑鏍煎紡锛�
+3. 鏂囦欢澶у皬涓嶈秴杩�10MB
+4. 涓婁紶瀹屾垚鍚庡彲鍦ㄩ檮浠跺垪琛ㄤ腑鏌ョ湅鍜屼笅杞�
+
+### 4. 鐘舵�佺鐞�
+1. 鍦ㄤ换鍔″垪琛ㄦ垨璇︽儏椤甸潰鐐瑰嚮"鐘舵�佸彉鏇�"
+2. 閫夋嫨鏂扮殑浠诲姟鐘舵��
+3. 濉啓鐘舵�佸彉鏇村娉�
+4. 绯荤粺浼氳嚜鍔ㄨ褰曠姸鎬佸彉鏇存棩蹇�
+
+## 鏉冮檺璇存槑
+
+### 鑿滃崟鏉冮檺
+- `task:general:view` - 浠诲姟鏌ョ湅鏉冮檺
+- `task:general:query` - 浠诲姟鏌ヨ鏉冮檺
+- `task:general:add` - 浠诲姟鏂板鏉冮檺
+- `task:general:edit` - 浠诲姟淇敼鏉冮檺
+- `task:general:remove` - 浠诲姟鍒犻櫎鏉冮檺
+- `task:general:assign` - 浠诲姟鍒嗛厤鏉冮檺
+- `task:general:status` - 鐘舵�佸彉鏇存潈闄�
+- `task:general:export` - 浠诲姟瀵煎嚭鏉冮檺
+
+### 鏁版嵁鏉冮檺
+绯荤粺鍩轰簬閮ㄩ棬杩涜鏁版嵁闅旂锛岀敤鎴峰彧鑳芥煡鐪嬪拰鎿嶄綔鏈儴闂ㄥ強涓嬬骇閮ㄩ棬鐨勬暟鎹��
+
+## 娉ㄦ剰浜嬮」
+
+1. **浠诲姟缂栧彿**锛氱郴缁熻嚜鍔ㄧ敓鎴愶紝鏍煎紡涓篢ASK+鏃ユ湡+搴忓彿
+2. **鐘舵�佹祦杞�**锛氬繀椤绘寜鐓т笟鍔¤鍒欒繘琛岀姸鎬佹祦杞紝涓嶅厑璁歌烦璺冨紡鍙樻洿
+3. **鏂囦欢涓婁紶**锛氭敮鎸佺殑鏂囦欢绫诲瀷鍜屽ぇ灏忔湁闄愬埗锛岃鎸夎姹備笂浼�
+4. **鏁版嵁澶囦唤**锛氬缓璁畾鏈熷浠戒换鍔$浉鍏虫暟鎹�
+5. **鎬ц兘浼樺寲**锛氬ぇ閲忔暟鎹椂寤鸿浣跨敤鍒嗛〉鏌ヨ
+
+## 鏁呴殰鎺掗櫎
+
+### 甯歌闂
+1. **鑿滃崟涓嶆樉绀�**锛氭鏌ヨ彍鍗曟潈闄愰厤缃拰瑙掕壊鍒嗛厤
+2. **鎺ュ彛璋冪敤澶辫触**锛氭鏌ュ悗绔湇鍔℃槸鍚︽甯稿惎鍔�
+3. **鏂囦欢涓婁紶澶辫触**锛氭鏌ユ枃浠跺ぇ灏忓拰鏍煎紡鏄惁绗﹀悎瑕佹眰
+4. **鐘舵�佸彉鏇村け璐�**锛氭鏌ョ姸鎬佹祦杞鍒欐槸鍚﹀厑璁�
+
+### 鏃ュ織鏌ョ湅
+- 鍚庣鏃ュ織锛氭煡鐪嬪簲鐢ㄦ棩蹇楁枃浠�
+- 鍓嶇鏃ュ織锛氭墦寮�娴忚鍣ㄥ紑鍙戣�呭伐鍏锋煡鐪嬫帶鍒跺彴
+- 鏁版嵁搴撴棩蹇楋細鏌ョ湅鏁版嵁搴撴搷浣滄棩蹇�
+
+## 鎶�鏈敮鎸�
+
+濡傛湁闂璇疯仈绯荤郴缁熺鐞嗗憳鎴栧紑鍙戝洟闃熴��
diff --git "a/prd/\344\273\273\345\212\241\350\275\246\350\276\206\345\205\263\350\201\224.md" "b/prd/\344\273\273\345\212\241\350\275\246\350\276\206\345\205\263\350\201\224.md"
new file mode 100644
index 0000000..9c02089
--- /dev/null
+++ "b/prd/\344\273\273\345\212\241\350\275\246\350\276\206\345\205\263\350\201\224.md"
@@ -0,0 +1,301 @@
+# 浠诲姟杞﹁締鍏宠仈鍏崇郴璁捐
+
+## 鍏崇郴姒傝堪
+
+鏍规嵁涓氬姟闇�姹傦紝浠诲姟绠$悊绯荤粺闇�瑕佸缓绔嬩互涓嬪叧鑱斿叧绯伙細
+
+1. **浠诲姟 鈫� 杞﹁締**锛氫竴涓换鍔″彲浠ュ叧鑱斿杈嗚溅锛屼竴杈嗚溅鍙互鎵ц澶氫釜浠诲姟
+2. **杞﹁締 鈫� 鏈烘瀯**锛氭瘡杈嗚溅褰掑睘浜庝竴涓満鏋�
+3. **浠诲姟 鈫� 鎵ц浜�**锛氭瘡涓换鍔″垎閰嶇粰涓�涓墽琛屼汉
+4. **鎵ц浜� 鈫� 鏈烘瀯**锛氭瘡涓墽琛屼汉褰掑睘浜庝竴涓満鏋�
+
+## 鏁版嵁搴撳叧绯昏璁�
+
+### 1. 鏍稿績鍏宠仈鍏崇郴
+
+```mermaid
+erDiagram
+ sys_task ||--o{ sys_task_vehicle : "涓�瀵瑰"
+ tb_vehicle_info ||--o{ sys_task_vehicle : "涓�瀵瑰"
+ sys_dept ||--o{ tb_vehicle_info : "涓�瀵瑰"
+ sys_dept ||--o{ sys_task : "涓�瀵瑰"
+ sys_user ||--o{ sys_task : "涓�瀵瑰"
+ sys_dept ||--o{ sys_user : "涓�瀵瑰"
+
+ sys_task {
+ bigint task_id PK
+ varchar task_code UK
+ varchar task_type
+ varchar task_status
+ bigint creator_id FK
+ bigint assignee_id FK
+ bigint dept_id FK
+ bigint vehicle_id FK
+ }
+
+ tb_vehicle_info {
+ bigint vehicle_id PK
+ varchar platform_code
+ varchar vehicle_no UK
+ varchar vehicle_type
+ varchar vehicle_brand
+ varchar vehicle_model
+ varchar vehicle_color
+ char vehicle_status
+ varchar device_id
+ bigint dept_id FK
+ }
+
+ sys_task_vehicle {
+ bigint id PK
+ bigint task_id FK
+ bigint vehicle_id FK
+ datetime assign_time
+ varchar assign_by
+ varchar status
+ varchar remark
+ }
+
+ sys_dept {
+ bigint dept_id PK
+ varchar dept_name
+ bigint parent_id
+ }
+
+ sys_user {
+ bigint user_id PK
+ varchar user_name
+ bigint dept_id FK
+ }
+```
+
+### 2. 鍏宠仈鍏崇郴璇存槑
+
+#### 2.1 浠诲姟涓庤溅杈嗗叧鑱�
+- **鍏崇郴绫诲瀷**锛氬瀵瑰锛堥�氳繃涓棿琛� `sys_task_vehicle`锛�
+- **涓氬姟瑙勫垯**锛�
+ - **涓�涓换鍔″彲浠ュ垎閰嶅杈嗚溅**锛堝锛氫富杞�+澶囩敤杞︺�佸杞﹀崗鍚屼綔涓氾級
+ - **涓�杈嗚溅鍙互鎵ц澶氫釜浠诲姟**锛堟寜鏃堕棿椤哄簭锛岄伩鍏嶅啿绐侊級
+ - **浠诲姟杞﹁締鍏宠仈鏈夌嫭绔嬬姸鎬佺鐞�**锛堝凡鍒嗛厤銆佹墽琛屼腑銆佸凡瀹屾垚銆佸凡鍙栨秷锛�
+ - **鏀寔鎵归噺鍒嗛厤鍜屽彇娑堝垎閰�**
+ - **杞﹁締鍒嗛厤鏃堕獙璇佽溅杈嗙姸鎬佸拰鍙敤鎬�**
+
+#### 2.2 杞﹁締涓庢満鏋勫叧鑱�
+- **鍏崇郴绫诲瀷**锛氬瀵逛竴
+- **涓氬姟瑙勫垯**锛�
+ - 姣忚締杞﹀繀椤诲綊灞炰簬涓�涓満鏋�
+ - 鏈烘瀯鍒犻櫎鏃讹紝杞﹁締褰掑睘璁剧疆涓篘ULL锛堣蒋鍒犻櫎锛�
+ - 鏀寔鎸夋満鏋勬煡璇㈣溅杈�
+
+#### 2.3 浠诲姟涓庢墽琛屼汉鍏宠仈
+- **鍏崇郴绫诲瀷**锛氬瀵逛竴
+- **涓氬姟瑙勫垯**锛�
+ - 姣忎釜浠诲姟鍒嗛厤缁欎竴涓墽琛屼汉
+ - 鎵ц浜哄繀椤讳笌浠诲姟鍦ㄥ悓涓�鏈烘瀯鎴栦笂绾ф満鏋�
+ - 鏀寔浠诲姟閲嶆柊鍒嗛厤
+
+#### 2.4 鎵ц浜轰笌鏈烘瀯鍏宠仈
+- **鍏崇郴绫诲瀷**锛氬瀵逛竴
+- **涓氬姟瑙勫垯**锛�
+ - 姣忎釜鎵ц浜哄綊灞炰簬涓�涓満鏋�
+ - 鏀寔鏈烘瀯灞傜骇鏉冮檺鎺у埗
+
+## 鏁版嵁鏉冮檺鎺у埗
+
+### 1. 鏈烘瀯鏁版嵁闅旂
+```sql
+-- 鏌ヨ浠诲姟鏃舵寜鏈烘瀯杩囨护
+SELECT t.* FROM sys_task t
+WHERE t.dept_id IN (
+ SELECT dept_id FROM sys_dept
+ WHERE FIND_IN_SET(dept_id, @user_dept_ids)
+);
+
+-- 鏌ヨ杞﹁締鏃舵寜鏈烘瀯杩囨护
+SELECT v.* FROM tb_vehicle_info v
+WHERE v.dept_id IN (
+ SELECT dept_id FROM sys_dept
+ WHERE FIND_IN_SET(dept_id, @user_dept_ids)
+);
+```
+
+### 2. 鏉冮檺楠岃瘉瑙勫垯
+- **浠诲姟鍒涘缓**锛氬彧鑳藉垱寤烘湰鏈烘瀯鍙婁笅绾ф満鏋勭殑浠诲姟
+- **杞﹁締鍒嗛厤**锛氬彧鑳藉垎閰嶆湰鏈烘瀯鍙婁笅绾ф満鏋勭殑杞﹁締
+- **鎵ц浜哄垎閰�**锛氬彧鑳藉垎閰嶇粰鏈満鏋勫強涓嬬骇鏈烘瀯鐨勭敤鎴�
+- **鏁版嵁鏌ヨ**锛氬彧鑳芥煡鐪嬫湰鏈烘瀯鍙婁笅绾ф満鏋勭殑鏁版嵁
+
+## 涓氬姟鍦烘櫙绀轰緥
+
+### 1. 鍒涘缓缁翠慨浠诲姟骞跺垎閰嶅杈嗚溅
+```sql
+-- 1. 鍒涘缓浠诲姟
+INSERT INTO sys_task (task_code, task_type, task_status, creator_id, dept_id, ...)
+VALUES ('TASK202401150001', 'MAINTENANCE', 'PENDING', 100, 200, ...);
+
+-- 2. 鍒嗛厤澶氳締杞︼紙涓昏溅+澶囩敤杞︼級
+INSERT INTO sys_task_vehicle (task_id, vehicle_id, assign_by, status, remark)
+VALUES
+(1, 10, 'admin', 'ASSIGNED', '鍒嗛厤涓荤淮淇溅'),
+(1, 11, 'admin', 'ASSIGNED', '鍒嗛厤澶囩敤缁翠慨杞�'),
+(1, 12, 'admin', 'ASSIGNED', '鍒嗛厤宸ュ叿杞�');
+
+-- 3. 鍒嗛厤鎵ц浜�
+UPDATE sys_task SET assignee_id = 150 WHERE task_id = 1;
+```
+
+### 2. 鏌ヨ鏈烘瀯涓嬬殑鎵�鏈変换鍔″拰杞﹁締
+```sql
+-- 鏌ヨ鏈烘瀯浠诲姟
+SELECT t.task_code, t.task_type, t.task_status,
+ u.user_name as assignee_name,
+ d.dept_name
+FROM sys_task t
+LEFT JOIN sys_user u ON t.assignee_id = u.user_id
+LEFT JOIN sys_dept d ON t.dept_id = d.dept_id
+WHERE t.dept_id = 200;
+
+-- 鏌ヨ鏈烘瀯杞﹁締
+SELECT v.vehicle_no, v.vehicle_type, v.vehicle_status,
+ d.dept_name
+FROM tb_vehicle_info v
+LEFT JOIN sys_dept d ON v.dept_id = d.dept_id
+WHERE v.dept_id = 200;
+```
+
+### 3. 鏌ヨ浠诲姟鐨勮溅杈嗗垎閰嶆儏鍐�
+```sql
+SELECT t.task_code, t.task_type,
+ v.vehicle_no, v.vehicle_type,
+ tv.assign_time, tv.status, tv.remark
+FROM sys_task t
+LEFT JOIN sys_task_vehicle tv ON t.task_id = tv.task_id
+LEFT JOIN tb_vehicle_info v ON tv.vehicle_id = v.vehicle_id
+WHERE t.task_id = 1;
+```
+
+## 鎺ュ彛璁捐鎵╁睍
+
+### 1. 杞﹁締绠$悊鎺ュ彛
+```javascript
+// 鏌ヨ鏈烘瀯杞﹁締鍒楄〃
+export function listVehicleByDept(deptId, query) {
+ return request({
+ url: '/api/vehicle/list-by-dept/' + deptId,
+ method: 'get',
+ params: query
+ })
+}
+
+// 鏌ヨ鍙敤杞﹁締锛堟湭鍒嗛厤浠诲姟鐨勮溅杈嗭級
+export function listAvailableVehicles(deptId, taskType) {
+ return request({
+ url: '/api/vehicle/available',
+ method: 'get',
+ params: { deptId, taskType }
+ })
+}
+```
+
+### 2. 浠诲姟杞﹁締鍒嗛厤鎺ュ彛
+```javascript
+// 鎵归噺鍒嗛厤杞﹁締
+export function assignVehiclesToTask(taskId, vehicleIds, remark) {
+ return request({
+ url: '/api/task/' + taskId + '/assign-vehicles',
+ method: 'post',
+ data: { vehicleIds, remark }
+ })
+}
+
+// 鏌ヨ浠诲姟杞﹁締浣跨敤鎯呭喌
+export function getTaskVehicleUsage(taskId) {
+ return request({
+ url: '/api/task/' + taskId + '/vehicle-usage',
+ method: 'get'
+ })
+}
+```
+
+## 鏁版嵁涓�鑷存�т繚璇�
+
+### 1. 澶栭敭绾︽潫
+- 浠诲姟琛ㄥ紩鐢ㄨ溅杈嗚〃锛歚ON DELETE SET NULL`
+- 浠诲姟杞﹁締鍏宠仈琛細`ON DELETE CASCADE`
+- 杞﹁締琛ㄥ紩鐢ㄦ満鏋勮〃锛歚ON DELETE SET NULL`
+
+### 2. 涓氬姟瑙勫垯楠岃瘉
+- 杞﹁締鍒嗛厤鏃堕獙璇佽溅杈嗙姸鎬侊紙蹇呴』涓烘甯哥姸鎬侊級
+- 浠诲姟鍒嗛厤鏃堕獙璇佹墽琛屼汉鏉冮檺锛堝悓鏈烘瀯鎴栦笂绾ф満鏋勶級
+- 鏈烘瀯鍒犻櫎鏃跺鐞嗗叧鑱旀暟鎹紙杞垹闄ゆ垨杞Щ锛�
+
+### 3. 浜嬪姟鎺у埗
+```java
+@Transactional
+public void assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark) {
+ // 1. 楠岃瘉浠诲姟鐘舵��
+ Task task = taskMapper.selectById(taskId);
+ if (task.getStatus() == TaskStatus.COMPLETED) {
+ throw new BusinessException("宸插畬鎴愮殑浠诲姟涓嶈兘鍒嗛厤杞﹁締");
+ }
+
+ List<TaskVehicle> assignedVehicles = new ArrayList<>();
+
+ for (Long vehicleId : vehicleIds) {
+ // 2. 楠岃瘉杞﹁締鐘舵�佸拰鍙敤鎬�
+ Vehicle vehicle = vehicleMapper.selectById(vehicleId);
+ if (vehicle.getStatus() != VehicleStatus.ACTIVE) {
+ throw new BusinessException("杞﹁締ID " + vehicleId + " 鐘舵�佸紓甯革紝涓嶈兘鍒嗛厤");
+ }
+
+ // 3. 妫�鏌ヨ溅杈嗘槸鍚﹀凡琚叾浠栦换鍔″崰鐢�
+ if (isVehicleAssignedToOtherTask(vehicleId, taskId)) {
+ throw new BusinessException("杞﹁締ID " + vehicleId + " 宸茶鍏朵粬浠诲姟鍗犵敤");
+ }
+
+ // 4. 鍒涘缓鍏宠仈璁板綍
+ TaskVehicle taskVehicle = new TaskVehicle();
+ taskVehicle.setTaskId(taskId);
+ taskVehicle.setVehicleId(vehicleId);
+ taskVehicle.setStatus(TaskVehicleStatus.ASSIGNED);
+ taskVehicle.setRemark(remark);
+ taskVehicleMapper.insert(taskVehicle);
+ assignedVehicles.add(taskVehicle);
+ }
+
+ // 5. 璁板綍鎿嶄綔鏃ュ織
+ logTaskOperation(taskId, "ASSIGN_MULTIPLE_VEHICLES",
+ "鍒嗛厤浜� " + vehicleIds.size() + " 杈嗚溅: " + vehicleIds.toString());
+}
+
+@Transactional
+public void unassignVehicleFromTask(Long taskId, Long vehicleId) {
+ // 1. 楠岃瘉浠诲姟杞﹁締鍏宠仈鏄惁瀛樺湪
+ TaskVehicle taskVehicle = taskVehicleMapper.selectByTaskAndVehicle(taskId, vehicleId);
+ if (taskVehicle == null) {
+ throw new BusinessException("浠诲姟杞﹁締鍏宠仈涓嶅瓨鍦�");
+ }
+
+ // 2. 楠岃瘉鏄惁鍙互鍙栨秷鍒嗛厤
+ if (taskVehicle.getStatus() == TaskVehicleStatus.COMPLETED) {
+ throw new BusinessException("宸插畬鎴愮殑浠诲姟杞﹁締鍏宠仈涓嶈兘鍙栨秷");
+ }
+
+ // 3. 鍒犻櫎鍏宠仈璁板綍
+ taskVehicleMapper.deleteById(taskVehicle.getId());
+
+ // 4. 璁板綍鎿嶄綔鏃ュ織
+ logTaskOperation(taskId, "UNASSIGN_VEHICLE", "鍙栨秷鍒嗛厤杞﹁締ID: " + vehicleId);
+}
+```
+
+## 鎬荤粨
+
+閫氳繃浠ヤ笂璁捐锛屽疄鐜颁簡浠诲姟銆佽溅杈嗐�佹満鏋勫拰鎵ц浜轰箣闂寸殑瀹屾暣鍏宠仈鍏崇郴锛�
+
+1. **鏁版嵁瀹屾暣鎬�**锛氶�氳繃澶栭敭绾︽潫淇濊瘉鏁版嵁涓�鑷存��
+2. **鏉冮檺鎺у埗**锛氬熀浜庢満鏋勭殑鏁版嵁闅旂鍜屾潈闄愰獙璇�
+3. **涓氬姟鐏垫椿鎬�**锛氭敮鎸佸杞﹁締鍒嗛厤鍜岀姸鎬佺鐞�
+4. **鎵╁睍鎬�**锛氶鐣欎簡璁惧ID绛夊瓧娈碉紝鏀寔GPS瀹氫綅绛夊姛鑳芥墿灞�
+
+杩欎釜璁捐鏃㈡弧瓒充簡褰撳墠鐨勪笟鍔¢渶姹傦紝鍙堜负鏈潵鐨勫姛鑳芥墿灞曠暀涓嬩簡绌洪棿銆�
diff --git "a/prd/\351\200\232\347\224\250\344\273\273\345\212\241\347\256\241\347\220\206\345\212\237\350\203\275\350\257\264\346\230\216.md" "b/prd/\351\200\232\347\224\250\344\273\273\345\212\241\347\256\241\347\220\206\345\212\237\350\203\275\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..a235574
--- /dev/null
+++ "b/prd/\351\200\232\347\224\250\344\273\273\345\212\241\347\256\241\347\220\206\345\212\237\350\203\275\350\257\264\346\230\216.md"
@@ -0,0 +1,1156 @@
+# 閫氱敤浠诲姟绠$悊鍔熻兘璇存槑
+
+## 鍔熻兘姒傝堪
+
+鏍规嵁闇�姹傛枃妗o紝宸插畬鎴愰�氱敤浠诲姟鍜岃浆杩愪换鍔$殑鍚庡彴绠$悊鍔熻兘寮�鍙戯紝鍖呮嫭锛�
+
+1. **閫氱敤浠诲姟绠$悊** - 鏀寔缁翠慨淇濆吇銆佸姞娌逛换鍔°�佸叾浠栦换鍔$被鍨�
+2. **瀹屾暣鐨凜RUD鎿嶄綔** - 鍒涘缓銆佹煡璇€�佹洿鏂般�佸垹闄ゃ�佸垎閰嶃�佺姸鎬佺鐞�
+3. **鏉冮檺鎺у埗** - 鍩轰簬瑙掕壊鐨勬搷浣滄潈闄愭帶鍒�
+4. **鏁版嵁闅旂** - 鍩轰簬鏈烘瀯鐨勬暟鎹潈闄愰殧绂�
+
+## 鎶�鏈灦鏋�
+### 鍚庣鏋舵瀯 (ruoyi-task妯″潡)
+- 閲囩敤DDD鏋舵瀯璁捐锛屽垎灞傛槑纭�
+- **棰嗗煙灞�**: 浠诲姟鑱氬悎鏍广�佸�煎璞°�侀鍩熸湇鍔�
+- **搴旂敤灞�**: CQRS鍛戒护鏌ヨ鍒嗙銆佸簲鐢ㄦ湇鍔�
+- **鍩虹璁炬柦灞�**: 鎸佷箙鍖栥�佹秷鎭帹閫�
+- **鎺ュ彛灞�**: REST API鎺у埗鍣�
+
+### 鍓嶇鏋舵瀯 (ruoyi-ui妯″潡)
+- **椤甸潰缁勪欢**: 浠诲姟鍒楄〃銆佷换鍔¤鎯呫�佷换鍔″垱寤�
+- **API鏈嶅姟**: 缁熶竴鐨凙PI鎺ュ彛璋冪敤
+- **璺敱閰嶇疆**: 鍔ㄦ�佽矾鐢卞拰鏉冮檺鎺у埗
+
+## 鏍稿績鍔熻兘
+
+### 1. 浠诲姟绫诲瀷绠$悊
+
+#### 閫氱敤浠诲姟绫诲瀷
+- **缁翠慨淇濆吇** (MAINTENANCE) - 杞﹁締缁翠慨淇濆吇浠诲姟
+- **鍔犳补浠诲姟** (FUEL) - 杞﹁締鍔犳补浠诲姟
+- **鍏朵粬** (OTHER) - 鍏朵粬绫诲瀷浠诲姟
+
+
+
+### 2. 浠诲姟鐘舵�佺鐞�
+
+#### 浠诲姟鐘舵�佸畾涔�
+- **寰呭紑濮�** (PENDING) - 浠诲姟宸插垱寤猴紝绛夊緟寮�濮�
+- **浠诲姟涓�** (IN_PROGRESS) - 浠诲姟宸茬粡寮�濮�
+- **宸插畬鎴�** (COMPLETED) - 浠诲姟宸插畬鎴�
+- **宸插彇娑�** (CANCELLED) - 浠诲姟宸插彇娑�
+
+
+#### 浠诲姟鐘舵�佹祦杞鍒�
+##### 浠诲姟鐘舵�佹祦杞鍒�
+- **PENDING** 鈫� **IN_PROGRESS** 鈫� **COMPLETED**
+- 浠讳綍鐘舵�侀兘鍙互娴佽浆鍒� **CANCELLED**锛堥櫎浜� **COMPLETED**锛�
+
+
+
+##### 鐘舵�佹祦杞害鏉�
+1. 浠诲姟鐘舵�佹祦杞繀椤荤鍚堜笟鍔¢�昏緫椤哄簭
+2. 鏀寔鐘舵�佸洖閫�锛堝浠� IN_PROGRESS 鍥炲埌 PENDING锛�
+3. 浠诲姟鍙栨秷鍚庝笉鑳藉啀娆℃縺娲�
+
+### 3. 浠诲姟瀛楁璁捐
+
+#### 閫氱敤瀛楁
+- 浠诲姟缂栧彿銆佷换鍔″垱寤轰汉锛屼换鍔″綊灞炴満鏋�
+- 浠诲姟绫诲瀷銆佷换鍔$姸鎬�
+- 浠诲姟鐩殑鍦板潃銆佷换鍔″嚭鍙戝湴鍧�
+- 璁″垝寮�濮嬫椂闂淬�佽鍒掔粨鏉熸椂闂�
+- 瀹為檯寮�濮嬫椂闂淬�佸疄闄呯粨鏉熸椂闂�
+- 浠诲姟澶囨敞銆侀檮浠跺垪琛�
+
+## 鏁版嵁搴撹璁�
+
+### 1. 鏍稿績琛ㄧ粨鏋�
+
+#### 1.1 浠诲姟涓昏〃 (sys_task)
+```sql
+CREATE TABLE sys_task (
+ task_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '浠诲姟ID',
+ task_code VARCHAR(50) NOT NULL UNIQUE COMMENT '浠诲姟缂栧彿',
+ task_type VARCHAR(20) NOT NULL COMMENT '浠诲姟绫诲瀷锛歁AINTENANCE-缁翠慨淇濆吇锛孎UEL-鍔犳补浠诲姟锛孫THER-鍏朵粬',
+ task_status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '浠诲姟鐘舵�侊細PENDING-寰呭紑濮嬶紝IN_PROGRESS-浠诲姟涓紝COMPLETED-宸插畬鎴愶紝CANCELLED-宸插彇娑�',
+ task_description varchar(1000) COMMENT '浠诲姟鎻忚堪',
+
+ -- 鍦板潃淇℃伅
+ departure_address VARCHAR(500) COMMENT '鍑哄彂鍦板潃',
+ destination_address VARCHAR(500) COMMENT '鐩殑鍦板潃',
+
+ -- 鏃堕棿淇℃伅
+ planned_start_time DATETIME COMMENT '璁″垝寮�濮嬫椂闂�',
+ planned_end_time DATETIME COMMENT '璁″垝缁撴潫鏃堕棿',
+ actual_start_time DATETIME COMMENT '瀹為檯寮�濮嬫椂闂�',
+ actual_end_time DATETIME COMMENT '瀹為檯缁撴潫鏃堕棿',
+
+ -- 浜哄憳淇℃伅
+ creator_id BIGINT NOT NULL COMMENT '鍒涘缓浜篒D',
+ assignee_id BIGINT COMMENT '鎵ц浜篒D',
+ dept_id BIGINT NOT NULL COMMENT '褰掑睘閮ㄩ棬ID',
+
+ -- 绯荤粺瀛楁
+ create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ create_by VARCHAR(64) NOT NULL COMMENT '鍒涘缓鑰�',
+ update_by VARCHAR(64) COMMENT '鏇存柊鑰�',
+ remark VARCHAR(500) COMMENT '澶囨敞',
+ del_flag CHAR(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+
+ INDEX idx_task_code (task_code),
+ INDEX idx_task_type (task_type),
+ INDEX idx_task_status (task_status),
+ INDEX idx_creator_id (creator_id),
+ INDEX idx_assignee_id (assignee_id),
+ INDEX idx_dept_id (dept_id),
+ INDEX idx_planned_start_time (planned_start_time),
+ INDEX idx_create_time (create_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟绠$悊琛�';
+```
+
+#### 1.2 杞﹁締淇℃伅琛� (tb_vehicle_info)
+```sql
+-- 鍩轰簬鐜版湁杞﹁締琛ㄧ粨鏋勶紝娣诲姞鏈烘瀯鍏宠仈瀛楁
+CREATE TABLE tb_vehicle_info (
+ vehicle_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '杞﹁締ID',
+ platform_code VARCHAR(50) NOT NULL COMMENT '骞冲彴鏍囪瘑锛圓/B锛�',
+ vehicle_no VARCHAR(50) NOT NULL COMMENT '杞︾墝鍙�',
+ vehicle_type VARCHAR(50) NOT NULL COMMENT '杞﹁締绫诲瀷锛欰MBULANCE-鏁戞姢杞︼紝TRANSFER-杞繍杞︼紝MAINTENANCE-缁翠慨杞�',
+ vehicle_brand VARCHAR(50) COMMENT '杞﹁締鍝佺墝',
+ vehicle_model VARCHAR(50) COMMENT '杞﹁締鍨嬪彿',
+ vehicle_color VARCHAR(20) COMMENT '杞﹁締棰滆壊',
+ vehicle_status CHAR(1) DEFAULT '0' COMMENT '杞﹁締鐘舵�侊紙0姝e父 1鍋滅敤锛�',
+ device_id VARCHAR(50) DEFAULT NULL COMMENT '璁惧ID',
+
+ -- 鏈烘瀯鍏宠仈锛堟柊澧炲瓧娈碉級
+ dept_id BIGINT(20) DEFAULT NULL COMMENT '褰掑睘鏈烘瀯ID',
+
+ -- 绯荤粺瀛楁
+ create_by VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ create_time DATETIME COMMENT '鍒涘缓鏃堕棿',
+ update_by VARCHAR(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+ update_time DATETIME COMMENT '鏇存柊鏃堕棿',
+ remark VARCHAR(500) DEFAULT NULL COMMENT '澶囨敞',
+
+ PRIMARY KEY (vehicle_id),
+ INDEX idx_vehicle_no (vehicle_no),
+ INDEX idx_vehicle_type (vehicle_type),
+ INDEX idx_vehicle_status (vehicle_status),
+ INDEX idx_dept_id (dept_id),
+ INDEX idx_platform_code (platform_code),
+
+ FOREIGN KEY (dept_id) REFERENCES sys_dept(dept_id) ON DELETE SET NULL
+) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COMMENT='杞﹁締淇℃伅琛�';
+```
+
+#### 1.3 浠诲姟杞﹁締鍏宠仈琛� (sys_task_vehicle)
+```sql
+CREATE TABLE sys_task_vehicle (
+ id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '鍏宠仈ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ vehicle_id BIGINT NOT NULL COMMENT '杞﹁締ID',
+ assign_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒嗛厤鏃堕棿',
+ assign_by VARCHAR(64) NOT NULL COMMENT '鍒嗛厤浜�',
+ status VARCHAR(20) DEFAULT 'ASSIGNED' COMMENT '鍏宠仈鐘舵�侊細ASSIGNED-宸插垎閰嶏紝ACTIVE-鎵ц涓紝COMPLETED-宸插畬鎴愶紝CANCELLED-宸插彇娑�',
+ remark VARCHAR(500) COMMENT '澶囨敞',
+
+ INDEX idx_task_id (task_id),
+ INDEX idx_vehicle_id (vehicle_id),
+ INDEX idx_status (status),
+ INDEX idx_assign_time (assign_time),
+
+ UNIQUE KEY uk_task_vehicle (task_id, vehicle_id),
+ FOREIGN KEY (task_id) REFERENCES sys_task(task_id) ON DELETE CASCADE,
+ FOREIGN KEY (vehicle_id) REFERENCES tb_vehicle_info(vehicle_id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟杞﹁締鍏宠仈琛�';
+```
+
+#### 1.4 浠诲姟闄勪欢琛� (sys_task_attachment)
+```sql
+CREATE TABLE sys_task_attachment (
+ attachment_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '闄勪欢ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ file_name VARCHAR(255) NOT NULL COMMENT '鏂囦欢鍚�',
+ file_path VARCHAR(500) NOT NULL COMMENT '鏂囦欢璺緞',
+ file_size BIGINT COMMENT '鏂囦欢澶у皬锛堝瓧鑺傦級',
+ file_type VARCHAR(50) COMMENT '鏂囦欢绫诲瀷',
+ upload_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '涓婁紶鏃堕棿',
+ upload_by VARCHAR(64) NOT NULL COMMENT '涓婁紶鑰�',
+
+ INDEX idx_task_id (task_id),
+ FOREIGN KEY (task_id) REFERENCES sys_task(task_id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟闄勪欢琛�';
+```
+
+#### 1.3 浠诲姟鎿嶄綔鏃ュ織琛� (sys_task_log)
+```sql
+CREATE TABLE sys_task_log (
+ log_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '鏃ュ織ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ operation_type VARCHAR(20) NOT NULL COMMENT '鎿嶄綔绫诲瀷锛欳REATE-鍒涘缓锛孶PDATE-鏇存柊锛孉SSIGN-鍒嗛厤锛孲TATUS_CHANGE-鐘舵�佸彉鏇达紝DELETE-鍒犻櫎',
+ operation_desc VARCHAR(500) COMMENT '鎿嶄綔鎻忚堪',
+ old_value TEXT COMMENT '鎿嶄綔鍓嶅��',
+ new_value TEXT COMMENT '鎿嶄綔鍚庡��',
+ operator_id BIGINT NOT NULL COMMENT '鎿嶄綔浜篒D',
+ operator_name VARCHAR(64) NOT NULL COMMENT '鎿嶄綔浜哄鍚�',
+ operation_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鎿嶄綔鏃堕棿',
+ ip_address VARCHAR(128) COMMENT 'IP鍦板潃',
+
+ INDEX idx_task_id (task_id),
+ INDEX idx_operation_type (operation_type),
+ INDEX idx_operator_id (operator_id),
+ INDEX idx_operation_time (operation_time),
+ FOREIGN KEY (task_id) REFERENCES sys_task(task_id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟鎿嶄綔鏃ュ織琛�';
+```
+
+### 2. 鏁版嵁瀛楀吀璁捐
+
+#### 2.1 浠诲姟绫诲瀷瀛楀吀
+```sql
+INSERT INTO sys_dict_type VALUES ('sys_task_type', '浠诲姟绫诲瀷', '0', 'admin', sysdate(), '', null, '浠诲姟绫诲瀷鍒楄〃');
+INSERT INTO sys_dict_data VALUES (1, 1, '缁翠慨淇濆吇', 'MAINTENANCE', 'sys_task_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '缁翠慨淇濆吇浠诲姟');
+INSERT INTO sys_dict_data VALUES (2, 2, '鍔犳补浠诲姟', 'FUEL', 'sys_task_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '鍔犳补浠诲姟');
+INSERT INTO sys_dict_data VALUES (3, 3, '鍏朵粬', 'OTHER', 'sys_task_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '鍏朵粬绫诲瀷浠诲姟');
+```
+
+#### 2.2 浠诲姟鐘舵�佸瓧鍏�
+```sql
+INSERT INTO sys_dict_type VALUES ('sys_task_status', '浠诲姟鐘舵��', '0', 'admin', sysdate(), '', null, '浠诲姟鐘舵�佸垪琛�');
+INSERT INTO sys_dict_data VALUES (4, 1, '寰呭紑濮�', 'PENDING', 'sys_task_status', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸插垱寤猴紝绛夊緟寮�濮�');
+INSERT INTO sys_dict_data VALUES (5, 2, '浠诲姟涓�', 'IN_PROGRESS', 'sys_task_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸茬粡寮�濮�');
+INSERT INTO sys_dict_data VALUES (6, 3, '宸插畬鎴�', 'COMPLETED', 'sys_task_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸插畬鎴�');
+INSERT INTO sys_dict_data VALUES (7, 4, '宸插彇娑�', 'CANCELLED', 'sys_task_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸插彇娑�');
+```
+
+#### 2.3 杞﹁締绫诲瀷瀛楀吀
+```sql
+INSERT INTO sys_dict_type VALUES ('sys_vehicle_type', '杞﹁締绫诲瀷', '0', 'admin', sysdate(), '', null, '杞﹁締绫诲瀷鍒楄〃');
+INSERT INTO sys_dict_data VALUES (8, 1, '鏁戞姢杞�', 'AMBULANCE', 'sys_vehicle_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '鏁戞姢杞�');
+INSERT INTO sys_dict_data VALUES (9, 2, '杞繍杞�', 'TRANSFER', 'sys_vehicle_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '杞繍杞�');
+INSERT INTO sys_dict_data VALUES (10, 3, '缁翠慨杞�', 'MAINTENANCE', 'sys_vehicle_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '缁翠慨杞�');
+```
+
+#### 2.4 杞﹁締鐘舵�佸瓧鍏�
+```sql
+INSERT INTO sys_dict_type VALUES ('sys_vehicle_status', '杞﹁締鐘舵��', '0', 'admin', sysdate(), '', null, '杞﹁締鐘舵�佸垪琛�');
+INSERT INTO sys_dict_data VALUES (11, 1, '姝e父', '0', 'sys_vehicle_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締姝e父浣跨敤');
+INSERT INTO sys_dict_data VALUES (12, 2, '鍋滅敤', '1', 'sys_vehicle_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締鍋滅敤');
+```
+
+#### 2.5 浠诲姟杞﹁締鍏宠仈鐘舵�佸瓧鍏�
+```sql
+INSERT INTO sys_dict_type VALUES ('sys_task_vehicle_status', '浠诲姟杞﹁締鍏宠仈鐘舵��', '0', 'admin', sysdate(), '', null, '浠诲姟杞﹁締鍏宠仈鐘舵�佸垪琛�');
+INSERT INTO sys_dict_data VALUES (15, 1, '宸插垎閰�', 'ASSIGNED', 'sys_task_vehicle_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締宸插垎閰嶇粰浠诲姟');
+INSERT INTO sys_dict_data VALUES (16, 2, '鎵ц涓�', 'ACTIVE', 'sys_task_vehicle_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締姝e湪鎵ц浠诲姟');
+INSERT INTO sys_dict_data VALUES (17, 3, '宸插畬鎴�', 'COMPLETED', 'sys_task_vehicle_status', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締浠诲姟宸插畬鎴�');
+INSERT INTO sys_dict_data VALUES (18, 4, '宸插彇娑�', 'CANCELLED', 'sys_task_vehicle_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締浠诲姟宸插彇娑�');
+```
+
+## 鎺ュ彛璁捐
+
+### 1. REST API 瑙勮寖
+
+#### 1.1 浠诲姟绠$悊鎺ュ彛
+
+##### 1.1.1 鍒涘缓浠诲姟
+```
+POST /api/task
+Content-Type: application/json
+
+Request Body:
+{
+ "taskType": "MAINTENANCE",
+ "taskTitle": "杞﹁締缁翠慨淇濆吇",
+ "taskDescription": "瀹氭湡淇濆吇妫�鏌�",
+ "departureAddress": "鍖椾含甯傛湞闃冲尯",
+ "destinationAddress": "鍖椾含甯傛捣娣�鍖虹淮淇巶",
+ "plannedStartTime": "2024-01-15 09:00:00",
+ "plannedEndTime": "2024-01-15 17:00:00",
+ "assigneeId": 100,
+ "remark": "绱ф�ョ淮淇�"
+}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "taskId": 1,
+ "taskCode": "TASK202401150001"
+ }
+}
+```
+
+##### 1.1.2 鏌ヨ浠诲姟鍒楄〃
+```
+GET /api/task/list?pageNum=1&pageSize=10&taskType=MAINTENANCE&taskStatus=PENDING
+
+Response:
+{
+ "code": 200,
+ "msg": "鏌ヨ鎴愬姛",
+ "rows": [
+ {
+ "taskId": 1,
+ "taskCode": "TASK202401150001",
+ "taskType": "MAINTENANCE",
+ "taskStatus": "PENDING",
+ "taskTitle": "杞﹁締缁翠慨淇濆吇",
+ "departureAddress": "鍖椾含甯傛湞闃冲尯",
+ "destinationAddress": "鍖椾含甯傛捣娣�鍖虹淮淇巶",
+ "plannedStartTime": "2024-01-15 09:00:00",
+ "plannedEndTime": "2024-01-15 17:00:00",
+ "creatorName": "寮犱笁",
+ "assigneeName": "鏉庡洓",
+ "createTime": "2024-01-15 08:30:00"
+ }
+ ],
+ "total": 1
+}
+```
+
+##### 1.1.3 鑾峰彇浠诲姟璇︽儏
+```
+GET /api/task/{taskId}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "taskId": 1,
+ "taskCode": "TASK202401150001",
+ "taskType": "MAINTENANCE",
+ "taskStatus": "PENDING",
+ "taskTitle": "杞﹁締缁翠慨淇濆吇",
+ "taskDescription": "瀹氭湡淇濆吇妫�鏌�",
+ "departureAddress": "鍖椾含甯傛湞闃冲尯",
+ "destinationAddress": "鍖椾含甯傛捣娣�鍖虹淮淇巶",
+ "plannedStartTime": "2024-01-15 09:00:00",
+ "plannedEndTime": "2024-01-15 17:00:00",
+ "actualStartTime": null,
+ "actualEndTime": null,
+ "creatorName": "寮犱笁",
+ "assigneeName": "鏉庡洓",
+ "remark": "绱ф�ョ淮淇�",
+ "attachments": [],
+ "operationLogs": [],
+ "assignedVehicles": [
+ {
+ "id": 1,
+ "vehicleId": 1,
+ "vehicleNo": "浜珹12345",
+ "vehicleType": "AMBULANCE",
+ "assignTime": "2024-01-15 09:00:00",
+ "assignBy": "寮犱笁",
+ "status": "ASSIGNED",
+ "remark": "鍒嗛厤鏁戞姢杞︽墽琛屼换鍔�"
+ }
+ ]
+ }
+}
+```
+
+##### 1.1.4 鏇存柊浠诲姟
+```
+PUT /api/task/{taskId}
+Content-Type: application/json
+
+Request Body:
+{
+ "taskTitle": "杞﹁締缁翠慨淇濆吇锛堟洿鏂帮級",
+ "taskDescription": "瀹氭湡淇濆吇妫�鏌ワ紝澧炲姞瀹夊叏妫�鏌�",
+ "plannedStartTime": "2024-01-15 10:00:00",
+ "assigneeId": 101,
+ "remark": "鏇存柊鍚庣殑澶囨敞"
+}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+##### 1.1.5 鏇存柊浠诲姟鐘舵��
+```
+PUT /api/task/{taskId}/status
+Content-Type: application/json
+
+Request Body:
+{
+ "taskStatus": "IN_PROGRESS",
+ "actualStartTime": "2024-01-15 10:15:00",
+ "remark": "浠诲姟寮�濮嬫墽琛�"
+}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+##### 1.1.6 鍒嗛厤浠诲姟
+```
+PUT /api/task/{taskId}/assign
+Content-Type: application/json
+
+Request Body:
+{
+ "assigneeId": 102,
+ "remark": "閲嶆柊鍒嗛厤缁欑帇浜�"
+}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+##### 1.1.7 鍒犻櫎浠诲姟
+```
+DELETE /api/task/{taskIds}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 1.2 浠诲姟闄勪欢鎺ュ彛
+
+##### 1.2.1 涓婁紶闄勪欢
+```
+POST /api/task/{taskId}/attachment
+Content-Type: multipart/form-data
+
+Request Body:
+- file: 鏂囦欢鍐呭
+
+Response:
+{
+ "code": 200,
+ "msg": "涓婁紶鎴愬姛",
+ "data": {
+ "attachmentId": 1,
+ "fileName": "缁翠慨鎶ュ憡.pdf",
+ "filePath": "/uploads/task/2024/01/15/xxx.pdf"
+ }
+}
+```
+
+##### 1.2.2 鍒犻櫎闄勪欢
+```
+DELETE /api/task/attachment/{attachmentId}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 1.3 杞﹁締绠$悊鎺ュ彛
+
+##### 1.3.1 鏌ヨ杞﹁締鍒楄〃
+```
+GET /api/vehicle/list?pageNum=1&pageSize=10&vehicleType=AMBULANCE&vehicleStatus=ACTIVE&deptId=100
+
+Response:
+{
+ "code": 200,
+ "msg": "鏌ヨ鎴愬姛",
+ "rows": [
+ {
+ "vehicleId": 1,
+ "platformCode": "A",
+ "vehicleNo": "浜珹12345",
+ "vehicleType": "AMBULANCE",
+ "vehicleBrand": "濂旈┌",
+ "vehicleModel": "Sprinter",
+ "vehicleStatus": "0",
+ "deviceId": "DEV001",
+ "deptId": 100,
+ "deptName": "鍖椾含鎬ユ晳涓績",
+ "createTime": "2024-01-15 08:30:00"
+ }
+ ],
+ "total": 1
+}
+```
+
+##### 1.3.2 鑾峰彇杞﹁締璇︽儏
+```
+GET /api/vehicle/{vehicleId}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "vehicleId": 1,
+ "platformCode": "A",
+ "vehicleNo": "浜珹12345",
+ "vehicleType": "AMBULANCE",
+ "vehicleBrand": "濂旈┌",
+ "vehicleModel": "Sprinter",
+ "vehicleColor": "鐧借壊",
+ "vehicleStatus": "0",
+ "deviceId": "DEV001",
+ "deptId": 100,
+ "deptName": "鍖椾含鎬ユ晳涓績",
+ "createBy": "admin",
+ "createTime": "2024-01-15 08:30:00",
+ "updateBy": "admin",
+ "updateTime": "2024-01-15 10:30:00",
+ "remark": "杞﹁締鐘舵�佽壇濂�"
+ }
+}
+```
+
+##### 1.3.3 鍒嗛厤杞﹁締缁欎换鍔�
+```
+POST /api/task/{taskId}/assign-vehicle
+Content-Type: application/json
+
+Request Body:
+{
+ "vehicleId": 1,
+ "remark": "鍒嗛厤鏁戞姢杞︽墽琛屼换鍔�"
+}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+##### 1.3.4 鎵归噺鍒嗛厤杞﹁締缁欎换鍔�
+```
+POST /api/task/{taskId}/assign-vehicles
+Content-Type: application/json
+
+Request Body:
+{
+ "vehicleIds": [1, 2, 3],
+ "remark": "鍒嗛厤澶氳締杞︽墽琛屼换鍔�"
+}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "successCount": 3,
+ "failedCount": 0,
+ "details": [
+ {
+ "vehicleId": 1,
+ "vehicleNo": "浜珹12345",
+ "status": "success",
+ "message": "鍒嗛厤鎴愬姛"
+ },
+ {
+ "vehicleId": 2,
+ "vehicleNo": "浜珹12346",
+ "status": "success",
+ "message": "鍒嗛厤鎴愬姛"
+ },
+ {
+ "vehicleId": 3,
+ "vehicleNo": "浜珹12347",
+ "status": "success",
+ "message": "鍒嗛厤鎴愬姛"
+ }
+ ]
+ }
+}
+```
+
+##### 1.3.5 鍙栨秷浠诲姟杞﹁締鍒嗛厤
+```
+DELETE /api/task/{taskId}/vehicle/{vehicleId}
+
+Response:
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+##### 1.3.6 鏌ヨ浠诲姟鍏宠仈鐨勮溅杈�
+```
+GET /api/task/{taskId}/vehicles
+
+Response:
+{
+ "code": 200,
+ "msg": "鏌ヨ鎴愬姛",
+ "data": [
+ {
+ "id": 1,
+ "taskId": 1,
+ "vehicleId": 1,
+ "vehicleNo": "浜珹12345",
+ "vehicleType": "AMBULANCE",
+ "assignTime": "2024-01-15 09:00:00",
+ "assignBy": "寮犱笁",
+ "status": "ASSIGNED",
+ "remark": "鍒嗛厤鏁戞姢杞︽墽琛屼换鍔�"
+ }
+ ]
+}
+```
+
+##### 1.3.7 鏌ヨ鍙敤杞﹁締
+```
+GET /api/vehicle/available?deptId=100&taskType=MAINTENANCE
+
+Response:
+{
+ "code": 200,
+ "msg": "鏌ヨ鎴愬姛",
+ "data": [
+ {
+ "vehicleId": 1,
+ "vehicleNo": "浜珹12345",
+ "vehicleType": "AMBULANCE",
+ "vehicleBrand": "濂旈┌",
+ "vehicleModel": "Sprinter",
+ "vehicleStatus": "0",
+ "deptName": "鍖椾含鎬ユ晳涓績",
+ "currentLocation": "鍖椾含甯傛湞闃冲尯"
+ },
+ {
+ "vehicleId": 2,
+ "vehicleNo": "浜珹12346",
+ "vehicleType": "AMBULANCE",
+ "vehicleBrand": "濂旈┌",
+ "vehicleModel": "Sprinter",
+ "vehicleStatus": "0",
+ "deptName": "鍖椾含鎬ユ晳涓績",
+ "currentLocation": "鍖椾含甯傛捣娣�鍖�"
+ }
+ ]
+}
+```
+
+#### 1.4 浠诲姟缁熻鎺ュ彛
+
+##### 1.4.1 浠诲姟缁熻姒傝
+```
+GET /api/task/statistics
+
+Response:
+{
+ "code": 200,
+ "msg": "鏌ヨ鎴愬姛",
+ "data": {
+ "totalTasks": 100,
+ "pendingTasks": 20,
+ "inProgressTasks": 30,
+ "completedTasks": 45,
+ "cancelledTasks": 5,
+ "todayTasks": 8,
+ "overdueTasks": 3,
+ "vehicleUtilization": 85.5
+ }
+}
+```
+
+## 绋嬪簭璁捐
+
+### 1. 鍚庣鏋舵瀯璁捐
+
+#### 1.1 妯″潡缁撴瀯
+```
+ruoyi-task/
+鈹溾攢鈹� src/main/java/com/ruoyi/task/
+鈹� 鈹溾攢鈹� controller/ # 鎺у埗鍣ㄥ眰
+鈹� 鈹� 鈹溾攢鈹� TaskController.java
+鈹� 鈹� 鈹溾攢鈹� TaskAttachmentController.java
+鈹� 鈹� 鈹溾攢鈹� VehicleController.java
+鈹� 鈹� 鈹斺攢鈹� TaskVehicleController.java
+鈹� 鈹溾攢鈹� service/ # 鏈嶅姟灞�
+鈹� 鈹� 鈹溾攢鈹� ITaskService.java
+鈹� 鈹� 鈹溾攢鈹� impl/TaskServiceImpl.java
+鈹� 鈹� 鈹溾攢鈹� ITaskAttachmentService.java
+鈹� 鈹� 鈹溾攢鈹� impl/TaskAttachmentServiceImpl.java
+鈹� 鈹� 鈹溾攢鈹� IVehicleService.java
+鈹� 鈹� 鈹溾攢鈹� impl/VehicleServiceImpl.java
+鈹� 鈹� 鈹溾攢鈹� ITaskVehicleService.java
+鈹� 鈹� 鈹斺攢鈹� impl/TaskVehicleServiceImpl.java
+鈹� 鈹溾攢鈹� domain/ # 棰嗗煙灞�
+鈹� 鈹� 鈹溾攢鈹� Task.java
+鈹� 鈹� 鈹溾攢鈹� TaskAttachment.java
+鈹� 鈹� 鈹溾攢鈹� TaskLog.java
+鈹� 鈹� 鈹溾攢鈹� Vehicle.java
+鈹� 鈹� 鈹溾攢鈹� TaskVehicle.java
+鈹� 鈹� 鈹溾攢鈹� enums/
+鈹� 鈹� 鈹� 鈹溾攢鈹� TaskType.java
+鈹� 鈹� 鈹� 鈹溾攢鈹� TaskStatus.java
+鈹� 鈹� 鈹� 鈹溾攢鈹� VehicleType.java
+鈹� 鈹� 鈹� 鈹溾攢鈹� VehicleStatus.java
+鈹� 鈹� 鈹� 鈹斺攢鈹� TaskVehicleStatus.java
+鈹� 鈹� 鈹斺攢鈹� vo/
+鈹� 鈹� 鈹溾攢鈹� TaskQueryVO.java
+鈹� 鈹� 鈹溾攢鈹� TaskCreateVO.java
+鈹� 鈹� 鈹溾攢鈹� TaskUpdateVO.java
+鈹� 鈹� 鈹溾攢鈹� VehicleQueryVO.java
+鈹� 鈹� 鈹溾攢鈹� VehicleCreateVO.java
+鈹� 鈹� 鈹斺攢鈹� TaskVehicleAssignVO.java
+鈹� 鈹溾攢鈹� mapper/ # 鏁版嵁璁块棶灞�
+鈹� 鈹� 鈹溾攢鈹� TaskMapper.java
+鈹� 鈹� 鈹溾攢鈹� TaskAttachmentMapper.java
+鈹� 鈹� 鈹溾攢鈹� TaskLogMapper.java
+鈹� 鈹� 鈹溾攢鈹� VehicleMapper.java
+鈹� 鈹� 鈹斺攢鈹� TaskVehicleMapper.java
+鈹� 鈹溾攢鈹� config/ # 閰嶇疆绫�
+鈹� 鈹� 鈹斺攢鈹� TaskConfig.java
+鈹� 鈹斺攢鈹� utils/ # 宸ュ叿绫�
+鈹� 鈹溾攢鈹� TaskCodeGenerator.java
+鈹� 鈹溾攢鈹� TaskStatusValidator.java
+鈹� 鈹斺攢鈹� VehicleCodeGenerator.java
+鈹斺攢鈹� src/main/resources/
+ 鈹斺攢鈹� mapper/
+ 鈹溾攢鈹� TaskMapper.xml
+ 鈹溾攢鈹� TaskAttachmentMapper.xml
+ 鈹溾攢鈹� TaskLogMapper.xml
+ 鈹溾攢鈹� VehicleMapper.xml
+ 鈹斺攢鈹� TaskVehicleMapper.xml
+```
+
+#### 1.2 鏍稿績绫昏璁�
+
+##### 1.2.1 浠诲姟瀹炰綋绫� (Task.java)
+```java
+/**
+ * 浠诲姟瀹炰綋绫�
+ * 鍖呭惈浠诲姟鐨勬墍鏈夊睘鎬у拰涓氬姟鏂规硶
+ */
+public class Task extends BaseEntity {
+ private Long taskId;
+ private String taskCode;
+ private TaskType taskType;
+ private TaskStatus taskStatus;
+ private String taskTitle;
+ private String taskDescription;
+ private String departureAddress;
+ private String destinationAddress;
+ private Date plannedStartTime;
+ private Date plannedEndTime;
+ private Date actualStartTime;
+ private Date actualEndTime;
+ private Long creatorId;
+ private Long assigneeId;
+ private Long deptId;
+
+ // 鍏宠仈杞﹁締鍒楄〃锛堥�氳繃涓棿琛ㄦ煡璇級
+ private List<TaskVehicle> assignedVehicles;
+
+ // 涓氬姟鏂规硶
+ public boolean canChangeStatus(TaskStatus newStatus);
+ public boolean isOverdue();
+ public long getDuration();
+ public void start();
+ public void complete();
+ public void cancel();
+ public List<Vehicle> getAssignedVehicles();
+ public boolean hasVehicle(Long vehicleId);
+}
+```
+
+##### 1.2.2 浠诲姟鏈嶅姟鎺ュ彛 (ITaskService.java)
+```java
+/**
+ * 浠诲姟鏈嶅姟鎺ュ彛
+ * 瀹氫箟浠诲姟鐩稿叧鐨勪笟鍔℃搷浣�
+ */
+public interface ITaskService {
+ // 鍩虹CRUD鎿嶄綔
+ List<Task> selectTaskList(TaskQueryVO queryVO);
+ Task selectTaskById(Long taskId);
+ int insertTask(TaskCreateVO createVO);
+ int updateTask(TaskUpdateVO updateVO);
+ int deleteTaskByIds(Long[] taskIds);
+
+ // 涓氬姟鎿嶄綔
+ int assignTask(Long taskId, Long assigneeId, String remark);
+ int changeTaskStatus(Long taskId, TaskStatus newStatus, String remark);
+ int uploadAttachment(Long taskId, MultipartFile file);
+ int deleteAttachment(Long attachmentId);
+
+ // 杞﹁締绠$悊鎿嶄綔
+ int assignVehicleToTask(Long taskId, Long vehicleId, String remark);
+ int unassignVehicleFromTask(Long taskId, Long vehicleId);
+ int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark);
+ List<TaskVehicle> getTaskVehicles(Long taskId);
+ List<Vehicle> getAvailableVehicles(Long deptId, String taskType);
+
+ // 缁熻鏌ヨ
+ TaskStatisticsVO getTaskStatistics();
+ List<Task> selectOverdueTasks();
+ List<Task> selectMyTasks(Long userId);
+}
+```
+
+#### 1.3 涓氬姟瑙勫垯璁捐
+
+##### 1.3.1 浠诲姟鐘舵�佹祦杞鍒�
+```java
+/**
+ * 浠诲姟鐘舵�佹祦杞獙璇佸櫒
+ */
+@Component
+public class TaskStatusValidator {
+
+ private static final Map<TaskStatus, Set<TaskStatus>> ALLOWED_TRANSITIONS = new HashMap<>();
+
+ static {
+ // PENDING -> IN_PROGRESS, CANCELLED
+ ALLOWED_TRANSITIONS.put(PENDING, Set.of(IN_PROGRESS, CANCELLED));
+ // IN_PROGRESS -> COMPLETED, CANCELLED, PENDING
+ ALLOWED_TRANSITIONS.put(IN_PROGRESS, Set.of(COMPLETED, CANCELLED, PENDING));
+ // COMPLETED -> 涓嶅厑璁镐换浣曠姸鎬佸彉鏇�
+ ALLOWED_TRANSITIONS.put(COMPLETED, Set.of());
+ // CANCELLED -> 涓嶅厑璁镐换浣曠姸鎬佸彉鏇�
+ ALLOWED_TRANSITIONS.put(CANCELLED, Set.of());
+ }
+
+ public boolean canTransition(TaskStatus from, TaskStatus to) {
+ return ALLOWED_TRANSITIONS.get(from).contains(to);
+ }
+}
+```
+
+##### 1.3.2 浠诲姟缂栧彿鐢熸垚瑙勫垯
+```java
+/**
+ * 浠诲姟缂栧彿鐢熸垚鍣�
+ * 鏍煎紡锛歍ASK + YYYYMMDD + 4浣嶅簭鍙�
+ */
+@Component
+public class TaskCodeGenerator {
+
+ public String generateTaskCode() {
+ String dateStr = DateUtils.formatDate(new Date(), "yyyyMMdd");
+ String sequence = getNextSequence(dateStr);
+ return "TASK" + dateStr + sequence;
+ }
+
+ private String getNextSequence(String dateStr) {
+ // 鏌ヨ褰撴棩鏈�澶у簭鍙峰苟閫掑
+ // 瀹炵幇閫昏緫...
+ }
+}
+```
+
+### 2. 鍓嶇鏋舵瀯璁捐
+
+#### 2.1 椤甸潰缁勪欢缁撴瀯
+```
+src/views/task/
+鈹溾攢鈹� index.vue # 浠诲姟鍒楄〃椤甸潰
+鈹溾攢鈹� detail.vue # 浠诲姟璇︽儏椤甸潰
+鈹溾攢鈹� create.vue # 鍒涘缓浠诲姟椤甸潰
+鈹溾攢鈹� edit.vue # 缂栬緫浠诲姟椤甸潰
+鈹斺攢鈹� components/
+ 鈹溾攢鈹� TaskList.vue # 浠诲姟鍒楄〃缁勪欢
+ 鈹溾攢鈹� TaskForm.vue # 浠诲姟琛ㄥ崟缁勪欢
+ 鈹溾攢鈹� TaskStatus.vue # 浠诲姟鐘舵�佺粍浠�
+ 鈹溾攢鈹� TaskAttachment.vue # 浠诲姟闄勪欢缁勪欢
+ 鈹斺攢鈹� TaskLog.vue # 浠诲姟鏃ュ織缁勪欢
+```
+
+#### 2.2 API鏈嶅姟璁捐
+```javascript
+// src/api/task.js
+import request from '@/utils/request'
+
+// 浠诲姟绠$悊API
+export function listTask(query) {
+ return request({
+ url: '/api/task/list',
+ method: 'get',
+ params: query
+ })
+}
+
+export function getTask(taskId) {
+ return request({
+ url: '/api/task/' + taskId,
+ method: 'get'
+ })
+}
+
+export function addTask(data) {
+ return request({
+ url: '/api/task',
+ method: 'post',
+ data: data
+ })
+}
+
+export function updateTask(data) {
+ return request({
+ url: '/api/task/' + data.taskId,
+ method: 'put',
+ data: data
+ })
+}
+
+export function deleteTask(taskIds) {
+ return request({
+ url: '/api/task/' + taskIds,
+ method: 'delete'
+ })
+}
+
+export function assignTask(taskId, data) {
+ return request({
+ url: '/api/task/' + taskId + '/assign',
+ method: 'put',
+ data: data
+ })
+}
+
+export function changeTaskStatus(taskId, data) {
+ return request({
+ url: '/api/task/' + taskId + '/status',
+ method: 'put',
+ data: data
+ })
+}
+
+// 闄勪欢绠$悊API
+export function uploadAttachment(taskId, file) {
+ const formData = new FormData()
+ formData.append('file', file)
+ return request({
+ url: '/api/task/' + taskId + '/attachment',
+ method: 'post',
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ })
+}
+
+export function deleteAttachment(attachmentId) {
+ return request({
+ url: '/api/task/attachment/' + attachmentId,
+ method: 'delete'
+ })
+}
+
+// 缁熻API
+export function getTaskStatistics() {
+ return request({
+ url: '/api/task/statistics',
+ method: 'get'
+ })
+}
+
+// 杞﹁締绠$悊API
+export function listVehicleByDept(deptId, query) {
+ return request({
+ url: '/api/vehicle/list-by-dept/' + deptId,
+ method: 'get',
+ params: query
+ })
+}
+
+export function listAvailableVehicles(deptId, taskType) {
+ return request({
+ url: '/api/vehicle/available',
+ method: 'get',
+ params: { deptId, taskType }
+ })
+}
+
+export function assignVehiclesToTask(taskId, vehicleIds, remark) {
+ return request({
+ url: '/api/task/' + taskId + '/assign-vehicles',
+ method: 'post',
+ data: { vehicleIds, remark }
+ })
+}
+
+export function getTaskVehicleUsage(taskId) {
+ return request({
+ url: '/api/task/' + taskId + '/vehicle-usage',
+ method: 'get'
+ })
+}
+```
+
+### 3. 鏉冮檺鎺у埗璁捐
+
+#### 3.1 鑿滃崟鏉冮檺閰嶇疆
+```sql
+-- 浠诲姟绠$悊鑿滃崟
+INSERT INTO sys_menu VALUES (2000, '浠诲姟绠$悊', 0, 5, 'task', null, '', 1, 0, 'M', '0', '0', '', 'task', 'admin', sysdate(), '', null, '浠诲姟绠$悊鐩綍');
+
+-- 閫氱敤浠诲姟鑿滃崟
+INSERT INTO sys_menu VALUES (2001, '閫氱敤浠诲姟', 2000, 1, 'general', 'task/general/index', '', 1, 0, 'C', '0', '0', 'task:general:view', 'list', 'admin', sysdate(), '', null, '閫氱敤浠诲姟鑿滃崟');
+
+-- 浠诲姟绠$悊鎸夐挳鏉冮檺
+INSERT INTO sys_menu VALUES (2002, '浠诲姟鏌ヨ', 2001, 1, '', '', '', 1, 0, 'F', '0', '0', 'task:general:query', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2003, '浠诲姟鏂板', 2001, 2, '', '', '', 1, 0, 'F', '0', '0', 'task:general:add', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2004, '浠诲姟淇敼', 2001, 3, '', '', '', 1, 0, 'F', '0', '0', 'task:general:edit', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2005, '浠诲姟鍒犻櫎', 2001, 4, '', '', '', 1, 0, 'F', '0', '0', 'task:general:remove', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2006, '浠诲姟鍒嗛厤', 2001, 5, '', '', '', 1, 0, 'F', '0', '0', 'task:general:assign', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2007, '鐘舵�佸彉鏇�', 2001, 6, '', '', '', 1, 0, 'F', '0', '0', 'task:general:status', '#', 'admin', sysdate(), '', null, '');
+```
+
+#### 3.2 鏁版嵁鏉冮檺鎺у埗
+```java
+/**
+ * 浠诲姟鏁版嵁鏉冮檺鎺у埗
+ * 鍩轰簬閮ㄩ棬杩涜鏁版嵁闅旂
+ */
+@Aspect
+@Component
+public class TaskDataScopeAspect {
+
+ @Before("@annotation(dataScope)")
+ public void doBefore(JoinPoint point, DataScope dataScope) {
+ // 鑾峰彇褰撳墠鐢ㄦ埛閮ㄩ棬鏉冮檺
+ // 娣诲姞鏁版嵁鏉冮檺杩囨护鏉′欢
+ // 瀹炵幇閫昏緫...
+ }
+}
+```
+
+### 4. 寮傚父澶勭悊璁捐
+
+#### 4.1 涓氬姟寮傚父瀹氫箟
+```java
+/**
+ * 浠诲姟鐩稿叧涓氬姟寮傚父
+ */
+public class TaskException extends RuntimeException {
+ private String code;
+ private String message;
+
+ public TaskException(String code, String message) {
+ super(message);
+ this.code = code;
+ this.message = message;
+ }
+}
+
+/**
+ * 浠诲姟寮傚父甯搁噺
+ */
+public class TaskErrorCode {
+ public static final String TASK_NOT_FOUND = "TASK_001";
+ public static final String TASK_STATUS_INVALID = "TASK_002";
+ public static final String TASK_ASSIGNEE_INVALID = "TASK_003";
+ public static final String TASK_PERMISSION_DENIED = "TASK_004";
+}
+```
+
+#### 4.2 鍏ㄥ眬寮傚父澶勭悊
+```java
+/**
+ * 鍏ㄥ眬寮傚父澶勭悊鍣�
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(TaskException.class)
+ public AjaxResult handleTaskException(TaskException e) {
+ return AjaxResult.error(e.getCode(), e.getMessage());
+ }
+}
+```
+
+## 閮ㄧ讲鍜岄厤缃�
+
+### 1. 鐜瑕佹眰
+- JDK 1.8+
+- MySQL 5.7+
+- Redis 3.0+
+- Maven 3.6+
+
+### 2. 閰嶇疆鏂囦欢
+```yaml
+# application.yml
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/ry-task?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ username: root
+ password: password
+
+ redis:
+ host: localhost
+ port: 6379
+ password:
+ database: 0
+
+# 浠诲姟鐩稿叧閰嶇疆
+task:
+ # 浠诲姟缂栧彿鍓嶇紑
+ code-prefix: TASK
+ # 闄勪欢涓婁紶璺緞
+ upload-path: /uploads/task/
+ # 鏈�澶ч檮浠跺ぇ灏忥紙MB锛�
+ max-file-size: 10
+ # 鍏佽鐨勬枃浠剁被鍨�
+ allowed-file-types: pdf,doc,docx,jpg,jpeg,png
+```
+
+### 3. 鏁版嵁搴撳垵濮嬪寲
+```sql
+-- 鎵ц鏁版嵁搴撹〃鍒涘缓鑴氭湰
+source sql/task_tables.sql;
+
+-- 鎵ц鏁版嵁瀛楀吀鍒濆鍖栬剼鏈�
+source sql/task_dict_data.sql;
+
+-- 鎵ц鑿滃崟鏉冮檺鍒濆鍖栬剼鏈�
+source sql/task_menu.sql;
+```
+
+## 娴嬭瘯绛栫暐
+
+### 1. 鍗曞厓娴嬭瘯
+- 鏈嶅姟灞備笟鍔¢�昏緫娴嬭瘯
+- 鏁版嵁璁块棶灞傛祴璇�
+- 宸ュ叿绫绘祴璇�
+
+### 2. 闆嗘垚娴嬭瘯
+- API鎺ュ彛娴嬭瘯
+- 鏁版嵁搴撴搷浣滄祴璇�
+- 鏉冮檺鎺у埗娴嬭瘯
+
+### 3. 鎬ц兘娴嬭瘯
+- 骞跺彂鍒涘缓浠诲姟娴嬭瘯
+- 澶ф暟鎹噺鏌ヨ娴嬭瘯
+- 鏂囦欢涓婁紶鎬ц兘娴嬭瘯
+
+## 鐩戞帶鍜屾棩蹇�
+
+### 1. 涓氬姟鐩戞帶
+- 浠诲姟鍒涘缓鏁伴噺缁熻
+- 浠诲姟瀹屾垚鐜囩粺璁�
+- 浠诲姟瓒呮椂鐜囩粺璁�
+
+### 2. 绯荤粺鐩戞帶
+- API鍝嶅簲鏃堕棿鐩戞帶
+- 鏁版嵁搴撹繛鎺ユ睜鐩戞帶
+- 鏂囦欢瀛樺偍绌洪棿鐩戞帶
+
+### 3. 鏃ュ織璁板綍
+- 鎿嶄綔鏃ュ織璁板綍
+- 寮傚父鏃ュ織璁板綍
+- 鎬ц兘鏃ュ織璁板綍
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java
new file mode 100644
index 0000000..b12fa9e
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java
@@ -0,0 +1,84 @@
+package com.ruoyi.web.controller.task;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.system.domain.SysTask;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.SysTaskAttachment;
+import com.ruoyi.system.service.ISysTaskService;
+
+/**
+ * 浠诲姟闄勪欢Controller
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+@RestController
+@RequestMapping("/task/attachment")
+public class SysTaskAttachmentController extends BaseController {
+
+ @Autowired
+ private ISysTaskService sysTaskService;
+
+ /**
+ * 鏌ヨ浠诲姟闄勪欢鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping("/list/{taskId}")
+ public AjaxResult list(@PathVariable("taskId") Long taskId) {
+ SysTask task = sysTaskService.getTaskDetail(taskId);
+ return success(task.getAttachments());
+ }
+
+ /**
+ * 涓婁紶浠诲姟闄勪欢
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:edit')")
+ @Log(title = "浠诲姟闄勪欢", businessType = BusinessType.INSERT)
+ @PostMapping("/upload/{taskId}")
+ public AjaxResult upload(@PathVariable("taskId") Long taskId, @RequestParam("file") MultipartFile file) {
+ try {
+ int result = sysTaskService.uploadAttachment(taskId, file);
+ if (result > 0) {
+ return success("涓婁紶鎴愬姛");
+ } else {
+ return error("涓婁紶澶辫触");
+ }
+ } catch (Exception e) {
+ return error("涓婁紶澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鍒犻櫎浠诲姟闄勪欢
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:edit')")
+ @Log(title = "浠诲姟闄勪欢", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{attachmentId}")
+ public AjaxResult remove(@PathVariable("attachmentId") Long attachmentId) {
+ try {
+ int result = sysTaskService.deleteAttachment(attachmentId);
+ if (result > 0) {
+ return success("鍒犻櫎鎴愬姛");
+ } else {
+ return error("鍒犻櫎澶辫触");
+ }
+ } catch (Exception e) {
+ return error("鍒犻櫎澶辫触锛�" + e.getMessage());
+ }
+ }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
new file mode 100644
index 0000000..25cb9ec
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
@@ -0,0 +1,205 @@
+package com.ruoyi.web.controller.task;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.vo.TaskQueryVO;
+import com.ruoyi.system.domain.vo.TaskCreateVO;
+import com.ruoyi.system.domain.vo.TaskUpdateVO;
+import com.ruoyi.system.domain.vo.TaskStatisticsVO;
+import com.ruoyi.system.domain.enums.TaskStatus;
+import com.ruoyi.system.service.ISysTaskService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 浠诲姟绠$悊Controller
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+@RestController
+@RequestMapping("/task")
+public class SysTaskController extends BaseController {
+
+ @Autowired
+ private ISysTaskService sysTaskService;
+
+ /**
+ * 鏌ヨ浠诲姟绠$悊鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping("/list")
+ public TableDataInfo list(TaskQueryVO queryVO) {
+ startPage();
+ List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
+ return getDataTable(list);
+ }
+
+ /**
+ * 瀵煎嚭浠诲姟绠$悊鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:export')")
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, TaskQueryVO queryVO) {
+ List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
+ ExcelUtil<SysTask> util = new ExcelUtil<SysTask>(SysTask.class);
+ util.exportExcel(response, list, "浠诲姟绠$悊鏁版嵁");
+ }
+
+ /**
+ * 鑾峰彇浠诲姟绠$悊璇︾粏淇℃伅
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping(value = "/{taskId}")
+ public AjaxResult getInfo(@PathVariable("taskId") Long taskId) {
+ return success(sysTaskService.getTaskDetail(taskId));
+ }
+
+ /**
+ * 鏂板浠诲姟绠$悊
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:add')")
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+ @PostMapping
+ public AjaxResult add(@RequestBody TaskCreateVO createVO) {
+ return toAjax(sysTaskService.insertSysTask(createVO));
+ }
+
+ /**
+ * 淇敼浠诲姟绠$悊
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:edit')")
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public AjaxResult edit(@RequestBody TaskUpdateVO updateVO) {
+ return toAjax(sysTaskService.updateSysTask(updateVO));
+ }
+
+ /**
+ * 鍒犻櫎浠诲姟绠$悊
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:remove')")
+ @Log(title = "浠诲姟绠$悊", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{taskIds}")
+ public AjaxResult remove(@PathVariable Long[] taskIds) {
+ return toAjax(sysTaskService.deleteSysTaskByTaskIds(taskIds));
+ }
+
+ /**
+ * 鍒嗛厤浠诲姟
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:assign')")
+ @Log(title = "浠诲姟鍒嗛厤", businessType = BusinessType.UPDATE)
+ @PutMapping("/{taskId}/assign")
+ public AjaxResult assignTask(@PathVariable Long taskId, @RequestBody AssignTaskRequest request) {
+ return toAjax(sysTaskService.assignTask(taskId, request.getAssigneeId(), request.getRemark()));
+ }
+
+ /**
+ * 鏇存柊浠诲姟鐘舵��
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:status')")
+ @Log(title = "浠诲姟鐘舵�佸彉鏇�", businessType = BusinessType.UPDATE)
+ @PutMapping("/{taskId}/status")
+ public AjaxResult changeTaskStatus(@PathVariable Long taskId, @RequestBody ChangeStatusRequest request) {
+ TaskStatus newStatus = TaskStatus.getByCode(request.getTaskStatus());
+ if (newStatus == null) {
+ return error("鏃犳晥鐨勪换鍔$姸鎬�");
+ }
+ return toAjax(sysTaskService.changeTaskStatus(taskId, newStatus, request.getRemark()));
+ }
+
+ /**
+ * 鏌ヨ浠诲姟缁熻淇℃伅
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping("/statistics")
+ public AjaxResult getStatistics() {
+ TaskStatisticsVO statistics = sysTaskService.getTaskStatistics();
+ return success(statistics);
+ }
+
+ /**
+ * 鏌ヨ瓒呮椂浠诲姟鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping("/overdue")
+ public AjaxResult getOverdueTasks() {
+ List<SysTask> list = sysTaskService.selectOverdueTasks();
+ return success(list);
+ }
+
+ /**
+ * 鏌ヨ鎴戠殑浠诲姟鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping("/my")
+ public AjaxResult getMyTasks() {
+ List<SysTask> list = sysTaskService.selectMyTasks(getUserId());
+ return success(list);
+ }
+
+ /**
+ * 鍒嗛厤浠诲姟璇锋眰瀵硅薄
+ */
+ public static class AssignTaskRequest {
+ private Long assigneeId;
+ private String remark;
+
+ public Long getAssigneeId() {
+ return assigneeId;
+ }
+
+ public void setAssigneeId(Long assigneeId) {
+ this.assigneeId = assigneeId;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+ }
+
+ /**
+ * 鍙樻洿鐘舵�佽姹傚璞�
+ */
+ public static class ChangeStatusRequest {
+ private String taskStatus;
+ private String remark;
+
+ public String getTaskStatus() {
+ return taskStatus;
+ }
+
+ public void setTaskStatus(String taskStatus) {
+ this.taskStatus = taskStatus;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+ }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java
new file mode 100644
index 0000000..6d70f75
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java
@@ -0,0 +1,158 @@
+package com.ruoyi.web.controller.task;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.SysTaskVehicle;
+import com.ruoyi.system.service.ISysTaskService;
+
+/**
+ * 浠诲姟杞﹁締鍏宠仈Controller
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+@RestController
+@RequestMapping("/task/vehicle")
+public class SysTaskVehicleController extends BaseController {
+
+ @Autowired
+ private ISysTaskService sysTaskService;
+
+ /**
+ * 鏌ヨ浠诲姟鍏宠仈鐨勮溅杈嗗垪琛�
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping("/list/{taskId}")
+ public AjaxResult list(@PathVariable("taskId") Long taskId) {
+ List<SysTaskVehicle> list = sysTaskService.getTaskVehicles(taskId);
+ return success(list);
+ }
+
+ /**
+ * 鏌ヨ鍙敤杞﹁締鍒楄〃
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:query')")
+ @GetMapping("/available")
+ public AjaxResult getAvailableVehicles(@RequestParam Long deptId, @RequestParam(required = false) String taskType) {
+ List<SysTaskVehicle> list = sysTaskService.getAvailableVehicles(deptId, taskType);
+ return success(list);
+ }
+
+ /**
+ * 鍒嗛厤杞﹁締缁欎换鍔�
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:assign')")
+ @Log(title = "浠诲姟杞﹁締鍒嗛厤", businessType = BusinessType.INSERT)
+ @PostMapping("/assign/{taskId}")
+ public AjaxResult assignVehicle(@PathVariable("taskId") Long taskId, @RequestBody AssignVehicleRequest request) {
+ try {
+ int result = sysTaskService.assignVehicleToTask(taskId, request.getVehicleId(), request.getRemark());
+ if (result > 0) {
+ return success("鍒嗛厤鎴愬姛");
+ } else {
+ return error("鍒嗛厤澶辫触");
+ }
+ } catch (Exception e) {
+ return error("鍒嗛厤澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鎵归噺鍒嗛厤杞﹁締缁欎换鍔�
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:assign')")
+ @Log(title = "浠诲姟杞﹁締鎵归噺鍒嗛厤", businessType = BusinessType.INSERT)
+ @PostMapping("/assign-batch/{taskId}")
+ public AjaxResult assignVehicles(@PathVariable("taskId") Long taskId, @RequestBody BatchAssignVehicleRequest request) {
+ try {
+ int result = sysTaskService.assignMultipleVehiclesToTask(taskId, request.getVehicleIds(), request.getRemark());
+ if (result > 0) {
+ return success("鎵归噺鍒嗛厤鎴愬姛锛屽叡鍒嗛厤 " + result + " 杈嗚溅");
+ } else {
+ return error("鎵归噺鍒嗛厤澶辫触");
+ }
+ } catch (Exception e) {
+ return error("鎵归噺鍒嗛厤澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鍙栨秷浠诲姟杞﹁締鍒嗛厤
+ */
+ @PreAuthorize("@ss.hasPermi('task:general:assign')")
+ @Log(title = "鍙栨秷浠诲姟杞﹁締鍒嗛厤", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{taskId}/{vehicleId}")
+ public AjaxResult unassignVehicle(@PathVariable("taskId") Long taskId, @PathVariable("vehicleId") Long vehicleId) {
+ try {
+ int result = sysTaskService.unassignVehicleFromTask(taskId, vehicleId);
+ if (result > 0) {
+ return success("鍙栨秷鍒嗛厤鎴愬姛");
+ } else {
+ return error("鍙栨秷鍒嗛厤澶辫触");
+ }
+ } catch (Exception e) {
+ return error("鍙栨秷鍒嗛厤澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鍒嗛厤杞﹁締璇锋眰瀵硅薄
+ */
+ public static class AssignVehicleRequest {
+ private Long vehicleId;
+ private String remark;
+
+ public Long getVehicleId() {
+ return vehicleId;
+ }
+
+ public void setVehicleId(Long vehicleId) {
+ this.vehicleId = vehicleId;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+ }
+
+ /**
+ * 鎵归噺鍒嗛厤杞﹁締璇锋眰瀵硅薄
+ */
+ public static class BatchAssignVehicleRequest {
+ private List<Long> vehicleIds;
+ private String remark;
+
+ public List<Long> getVehicleIds() {
+ return vehicleIds;
+ }
+
+ public void setVehicleIds(List<Long> vehicleIds) {
+ this.vehicleIds = vehicleIds;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
new file mode 100644
index 0000000..0286efe
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
@@ -0,0 +1,369 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.system.domain.enums.TaskStatus;
+import com.ruoyi.system.domain.enums.TaskType;
+
+/**
+ * 浠诲姟绠$悊瀵硅薄 sys_task
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class SysTask extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 浠诲姟ID */
+ private Long taskId;
+
+ /** 浠诲姟缂栧彿 */
+ @Excel(name = "浠诲姟缂栧彿")
+ private String taskCode;
+
+ /** 浠诲姟绫诲瀷 */
+ @Excel(name = "浠诲姟绫诲瀷", readConverterExp = "MAINTENANCE=缁翠慨淇濆吇,FUEL=鍔犳补浠诲姟,OTHER=鍏朵粬")
+ private String taskType;
+
+ /** 浠诲姟鐘舵�� */
+ @Excel(name = "浠诲姟鐘舵��", readConverterExp = "PENDING=寰呭紑濮�,IN_PROGRESS=浠诲姟涓�,COMPLETED=宸插畬鎴�,CANCELLED=宸插彇娑�")
+ private String taskStatus;
+
+ /** 浠诲姟鎻忚堪 */
+ @Excel(name = "浠诲姟鎻忚堪")
+ private String taskDescription;
+
+ /** 鍑哄彂鍦板潃 */
+ @Excel(name = "鍑哄彂鍦板潃")
+ private String departureAddress;
+
+ /** 鐩殑鍦板潃 */
+ @Excel(name = "鐩殑鍦板潃")
+ private String destinationAddress;
+
+ /** 璁″垝寮�濮嬫椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "璁″垝寮�濮嬫椂闂�", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedStartTime;
+
+ /** 璁″垝缁撴潫鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "璁″垝缁撴潫鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedEndTime;
+
+ /** 瀹為檯寮�濮嬫椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "瀹為檯寮�濮嬫椂闂�", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date actualStartTime;
+
+ /** 瀹為檯缁撴潫鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "瀹為檯缁撴潫鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date actualEndTime;
+
+ /** 鍒涘缓浜篒D */
+ @Excel(name = "鍒涘缓浜篒D")
+ private Long creatorId;
+
+ /** 鎵ц浜篒D */
+ @Excel(name = "鎵ц浜篒D")
+ private Long assigneeId;
+
+ /** 褰掑睘閮ㄩ棬ID */
+ @Excel(name = "褰掑睘閮ㄩ棬ID")
+ private Long deptId;
+
+ /** 鍒涘缓浜哄鍚� */
+ @Excel(name = "鍒涘缓浜�")
+ private String creatorName;
+
+ /** 鎵ц浜哄鍚� */
+ @Excel(name = "鎵ц浜�")
+ private String assigneeName;
+
+ /** 閮ㄩ棬鍚嶇О */
+ @Excel(name = "閮ㄩ棬鍚嶇О")
+ private String deptName;
+
+ /** 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛� */
+ private String delFlag;
+
+ /** 鍏宠仈杞﹁締鍒楄〃 */
+ private List<SysTaskVehicle> assignedVehicles;
+
+ /** 闄勪欢鍒楄〃 */
+ private List<SysTaskAttachment> attachments;
+
+ /** 鎿嶄綔鏃ュ織鍒楄〃 */
+ private List<SysTaskLog> operationLogs;
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskCode(String taskCode) {
+ this.taskCode = taskCode;
+ }
+
+ public String getTaskCode() {
+ return taskCode;
+ }
+
+ public void setTaskType(String taskType) {
+ this.taskType = taskType;
+ }
+
+ public String getTaskType() {
+ return taskType;
+ }
+
+ public void setTaskStatus(String taskStatus) {
+ this.taskStatus = taskStatus;
+ }
+
+ public String getTaskStatus() {
+ return taskStatus;
+ }
+
+ public void setTaskDescription(String taskDescription) {
+ this.taskDescription = taskDescription;
+ }
+
+ public String getTaskDescription() {
+ return taskDescription;
+ }
+
+ public void setDepartureAddress(String departureAddress) {
+ this.departureAddress = departureAddress;
+ }
+
+ public String getDepartureAddress() {
+ return departureAddress;
+ }
+
+ public void setDestinationAddress(String destinationAddress) {
+ this.destinationAddress = destinationAddress;
+ }
+
+ public String getDestinationAddress() {
+ return destinationAddress;
+ }
+
+ public void setPlannedStartTime(Date plannedStartTime) {
+ this.plannedStartTime = plannedStartTime;
+ }
+
+ public Date getPlannedStartTime() {
+ return plannedStartTime;
+ }
+
+ public void setPlannedEndTime(Date plannedEndTime) {
+ this.plannedEndTime = plannedEndTime;
+ }
+
+ public Date getPlannedEndTime() {
+ return plannedEndTime;
+ }
+
+ public void setActualStartTime(Date actualStartTime) {
+ this.actualStartTime = actualStartTime;
+ }
+
+ public Date getActualStartTime() {
+ return actualStartTime;
+ }
+
+ public void setActualEndTime(Date actualEndTime) {
+ this.actualEndTime = actualEndTime;
+ }
+
+ public Date getActualEndTime() {
+ return actualEndTime;
+ }
+
+ public void setCreatorId(Long creatorId) {
+ this.creatorId = creatorId;
+ }
+
+ public Long getCreatorId() {
+ return creatorId;
+ }
+
+ public void setAssigneeId(Long assigneeId) {
+ this.assigneeId = assigneeId;
+ }
+
+ public Long getAssigneeId() {
+ return assigneeId;
+ }
+
+ public void setDeptId(Long deptId) {
+ this.deptId = deptId;
+ }
+
+ public Long getDeptId() {
+ return deptId;
+ }
+
+ public void setCreatorName(String creatorName) {
+ this.creatorName = creatorName;
+ }
+
+ public String getCreatorName() {
+ return creatorName;
+ }
+
+ public void setAssigneeName(String assigneeName) {
+ this.assigneeName = assigneeName;
+ }
+
+ public String getAssigneeName() {
+ return assigneeName;
+ }
+
+ public void setDeptName(String deptName) {
+ this.deptName = deptName;
+ }
+
+ public String getDeptName() {
+ return deptName;
+ }
+
+ public void setDelFlag(String delFlag) {
+ this.delFlag = delFlag;
+ }
+
+ public String getDelFlag() {
+ return delFlag;
+ }
+
+ public void setAssignedVehicles(List<SysTaskVehicle> assignedVehicles) {
+ this.assignedVehicles = assignedVehicles;
+ }
+
+ public List<SysTaskVehicle> getAssignedVehicles() {
+ return assignedVehicles;
+ }
+
+ public void setAttachments(List<SysTaskAttachment> attachments) {
+ this.attachments = attachments;
+ }
+
+ public List<SysTaskAttachment> getAttachments() {
+ return attachments;
+ }
+
+ public void setOperationLogs(List<SysTaskLog> operationLogs) {
+ this.operationLogs = operationLogs;
+ }
+
+ public List<SysTaskLog> getOperationLogs() {
+ return operationLogs;
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁鍙互鍙樻洿鐘舵��
+ */
+ public boolean canChangeStatus(TaskStatus newStatus) {
+ TaskStatus currentStatus = TaskStatus.getByCode(this.taskStatus);
+ if (currentStatus == null || newStatus == null) {
+ return false;
+ }
+
+ // 鐘舵�佹祦杞鍒�
+ switch (currentStatus) {
+ case PENDING:
+ return newStatus == TaskStatus.IN_PROGRESS || newStatus == TaskStatus.CANCELLED;
+ case IN_PROGRESS:
+ return newStatus == TaskStatus.COMPLETED || newStatus == TaskStatus.CANCELLED || newStatus == TaskStatus.PENDING;
+ case COMPLETED:
+ case CANCELLED:
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁瓒呮椂
+ */
+ public boolean isOverdue() {
+ if (plannedEndTime == null) {
+ return false;
+ }
+ return new Date().after(plannedEndTime) && !TaskStatus.COMPLETED.getCode().equals(taskStatus);
+ }
+
+ /**
+ * 鑾峰彇浠诲姟鎸佺画鏃堕棿锛堝垎閽燂級
+ */
+ public long getDuration() {
+ if (actualStartTime != null && actualEndTime != null) {
+ return (actualEndTime.getTime() - actualStartTime.getTime()) / (1000 * 60);
+ }
+ return 0;
+ }
+
+ /**
+ * 寮�濮嬩换鍔�
+ */
+ public void start() {
+ this.taskStatus = TaskStatus.IN_PROGRESS.getCode();
+ this.actualStartTime = new Date();
+ }
+
+ /**
+ * 瀹屾垚浠诲姟
+ */
+ public void complete() {
+ this.taskStatus = TaskStatus.COMPLETED.getCode();
+ this.actualEndTime = new Date();
+ }
+
+ /**
+ * 鍙栨秷浠诲姟
+ */
+ public void cancel() {
+ this.taskStatus = TaskStatus.CANCELLED.getCode();
+ }
+
+ /**
+ * 妫�鏌ユ槸鍚﹀垎閰嶄簡鎸囧畾杞﹁締
+ */
+ public boolean hasVehicle(Long vehicleId) {
+ if (assignedVehicles == null || vehicleId == null) {
+ return false;
+ }
+ return assignedVehicles.stream().anyMatch(v -> vehicleId.equals(v.getVehicleId()));
+ }
+
+ @Override
+ public String toString() {
+ return "SysTask{" +
+ "taskId=" + taskId +
+ ", taskCode='" + taskCode + '\'' +
+ ", taskType='" + taskType + '\'' +
+ ", taskStatus='" + taskStatus + '\'' +
+ ", taskDescription='" + taskDescription + '\'' +
+ ", departureAddress='" + departureAddress + '\'' +
+ ", destinationAddress='" + destinationAddress + '\'' +
+ ", plannedStartTime=" + plannedStartTime +
+ ", plannedEndTime=" + plannedEndTime +
+ ", actualStartTime=" + actualStartTime +
+ ", actualEndTime=" + actualEndTime +
+ ", creatorId=" + creatorId +
+ ", assigneeId=" + assigneeId +
+ ", deptId=" + deptId +
+ ", creatorName='" + creatorName + '\'' +
+ ", assigneeName='" + assigneeName + '\'' +
+ ", deptName='" + deptName + '\'' +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAttachment.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAttachment.java
new file mode 100644
index 0000000..6dd5bff
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAttachment.java
@@ -0,0 +1,126 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 浠诲姟闄勪欢瀵硅薄 sys_task_attachment
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class SysTaskAttachment extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 闄勪欢ID */
+ private Long attachmentId;
+
+ /** 浠诲姟ID */
+ @Excel(name = "浠诲姟ID")
+ private Long taskId;
+
+ /** 鏂囦欢鍚� */
+ @Excel(name = "鏂囦欢鍚�")
+ private String fileName;
+
+ /** 鏂囦欢璺緞 */
+ @Excel(name = "鏂囦欢璺緞")
+ private String filePath;
+
+ /** 鏂囦欢澶у皬锛堝瓧鑺傦級 */
+ @Excel(name = "鏂囦欢澶у皬")
+ private Long fileSize;
+
+ /** 鏂囦欢绫诲瀷 */
+ @Excel(name = "鏂囦欢绫诲瀷")
+ private String fileType;
+
+ /** 涓婁紶鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "涓婁紶鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date uploadTime;
+
+ /** 涓婁紶鑰� */
+ @Excel(name = "涓婁紶鑰�")
+ private String uploadBy;
+
+ public void setAttachmentId(Long attachmentId) {
+ this.attachmentId = attachmentId;
+ }
+
+ public Long getAttachmentId() {
+ return attachmentId;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFilePath(String filePath) {
+ this.filePath = filePath;
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+ public void setFileSize(Long fileSize) {
+ this.fileSize = fileSize;
+ }
+
+ public Long getFileSize() {
+ return fileSize;
+ }
+
+ public void setFileType(String fileType) {
+ this.fileType = fileType;
+ }
+
+ public String getFileType() {
+ return fileType;
+ }
+
+ public void setUploadTime(Date uploadTime) {
+ this.uploadTime = uploadTime;
+ }
+
+ public Date getUploadTime() {
+ return uploadTime;
+ }
+
+ public void setUploadBy(String uploadBy) {
+ this.uploadBy = uploadBy;
+ }
+
+ public String getUploadBy() {
+ return uploadBy;
+ }
+
+ @Override
+ public String toString() {
+ return "SysTaskAttachment{" +
+ "attachmentId=" + attachmentId +
+ ", taskId=" + taskId +
+ ", fileName='" + fileName + '\'' +
+ ", filePath='" + filePath + '\'' +
+ ", fileSize=" + fileSize +
+ ", fileType='" + fileType + '\'' +
+ ", uploadTime=" + uploadTime +
+ ", uploadBy='" + uploadBy + '\'' +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskLog.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskLog.java
new file mode 100644
index 0000000..36c9638
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskLog.java
@@ -0,0 +1,152 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 浠诲姟鎿嶄綔鏃ュ織瀵硅薄 sys_task_log
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class SysTaskLog extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 鏃ュ織ID */
+ private Long logId;
+
+ /** 浠诲姟ID */
+ @Excel(name = "浠诲姟ID")
+ private Long taskId;
+
+ /** 鎿嶄綔绫诲瀷 */
+ @Excel(name = "鎿嶄綔绫诲瀷", readConverterExp = "CREATE=鍒涘缓,UPDATE=鏇存柊,ASSIGN=鍒嗛厤,STATUS_CHANGE=鐘舵�佸彉鏇�,DELETE=鍒犻櫎")
+ private String operationType;
+
+ /** 鎿嶄綔鎻忚堪 */
+ @Excel(name = "鎿嶄綔鎻忚堪")
+ private String operationDesc;
+
+ /** 鎿嶄綔鍓嶅�� */
+ @Excel(name = "鎿嶄綔鍓嶅��")
+ private String oldValue;
+
+ /** 鎿嶄綔鍚庡�� */
+ @Excel(name = "鎿嶄綔鍚庡��")
+ private String newValue;
+
+ /** 鎿嶄綔浜篒D */
+ @Excel(name = "鎿嶄綔浜篒D")
+ private Long operatorId;
+
+ /** 鎿嶄綔浜哄鍚� */
+ @Excel(name = "鎿嶄綔浜哄鍚�")
+ private String operatorName;
+
+ /** 鎿嶄綔鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "鎿嶄綔鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date operationTime;
+
+ /** IP鍦板潃 */
+ @Excel(name = "IP鍦板潃")
+ private String ipAddress;
+
+ public void setLogId(Long logId) {
+ this.logId = logId;
+ }
+
+ public Long getLogId() {
+ return logId;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setOperationType(String operationType) {
+ this.operationType = operationType;
+ }
+
+ public String getOperationType() {
+ return operationType;
+ }
+
+ public void setOperationDesc(String operationDesc) {
+ this.operationDesc = operationDesc;
+ }
+
+ public String getOperationDesc() {
+ return operationDesc;
+ }
+
+ public void setOldValue(String oldValue) {
+ this.oldValue = oldValue;
+ }
+
+ public String getOldValue() {
+ return oldValue;
+ }
+
+ public void setNewValue(String newValue) {
+ this.newValue = newValue;
+ }
+
+ public String getNewValue() {
+ return newValue;
+ }
+
+ public void setOperatorId(Long operatorId) {
+ this.operatorId = operatorId;
+ }
+
+ public Long getOperatorId() {
+ return operatorId;
+ }
+
+ public void setOperatorName(String operatorName) {
+ this.operatorName = operatorName;
+ }
+
+ public String getOperatorName() {
+ return operatorName;
+ }
+
+ public void setOperationTime(Date operationTime) {
+ this.operationTime = operationTime;
+ }
+
+ public Date getOperationTime() {
+ return operationTime;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ @Override
+ public String toString() {
+ return "SysTaskLog{" +
+ "logId=" + logId +
+ ", taskId=" + taskId +
+ ", operationType='" + operationType + '\'' +
+ ", operationDesc='" + operationDesc + '\'' +
+ ", oldValue='" + oldValue + '\'' +
+ ", newValue='" + newValue + '\'' +
+ ", operatorId=" + operatorId +
+ ", operatorName='" + operatorName + '\'' +
+ ", operationTime=" + operationTime +
+ ", ipAddress='" + ipAddress + '\'' +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskVehicle.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskVehicle.java
new file mode 100644
index 0000000..6630b8f
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskVehicle.java
@@ -0,0 +1,152 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 浠诲姟杞﹁締鍏宠仈瀵硅薄 sys_task_vehicle
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class SysTaskVehicle extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 鍏宠仈ID */
+ private Long id;
+
+ /** 浠诲姟ID */
+ @Excel(name = "浠诲姟ID")
+ private Long taskId;
+
+ /** 杞﹁締ID */
+ @Excel(name = "杞﹁締ID")
+ private Long vehicleId;
+
+ /** 鍒嗛厤鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "鍒嗛厤鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date assignTime;
+
+ /** 鍒嗛厤浜� */
+ @Excel(name = "鍒嗛厤浜�")
+ private String assignBy;
+
+ /** 鍏宠仈鐘舵�� */
+ @Excel(name = "鍏宠仈鐘舵��", readConverterExp = "ASSIGNED=宸插垎閰�,ACTIVE=鎵ц涓�,COMPLETED=宸插畬鎴�,CANCELLED=宸插彇娑�")
+ private String status;
+
+ /** 杞︾墝鍙� */
+ @Excel(name = "杞︾墝鍙�")
+ private String vehicleNo;
+
+ /** 杞﹁締绫诲瀷 */
+ @Excel(name = "杞﹁締绫诲瀷", readConverterExp = "AMBULANCE=鏁戞姢杞�,TRANSFER=杞繍杞�,MAINTENANCE=缁翠慨杞�")
+ private String vehicleType;
+
+ /** 杞﹁締鍝佺墝 */
+ @Excel(name = "杞﹁締鍝佺墝")
+ private String vehicleBrand;
+
+ /** 杞﹁締鍨嬪彿 */
+ @Excel(name = "杞﹁締鍨嬪彿")
+ private String vehicleModel;
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setVehicleId(Long vehicleId) {
+ this.vehicleId = vehicleId;
+ }
+
+ public Long getVehicleId() {
+ return vehicleId;
+ }
+
+ public void setAssignTime(Date assignTime) {
+ this.assignTime = assignTime;
+ }
+
+ public Date getAssignTime() {
+ return assignTime;
+ }
+
+ public void setAssignBy(String assignBy) {
+ this.assignBy = assignBy;
+ }
+
+ public String getAssignBy() {
+ return assignBy;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setVehicleNo(String vehicleNo) {
+ this.vehicleNo = vehicleNo;
+ }
+
+ public String getVehicleNo() {
+ return vehicleNo;
+ }
+
+ public void setVehicleType(String vehicleType) {
+ this.vehicleType = vehicleType;
+ }
+
+ public String getVehicleType() {
+ return vehicleType;
+ }
+
+ public void setVehicleBrand(String vehicleBrand) {
+ this.vehicleBrand = vehicleBrand;
+ }
+
+ public String getVehicleBrand() {
+ return vehicleBrand;
+ }
+
+ public void setVehicleModel(String vehicleModel) {
+ this.vehicleModel = vehicleModel;
+ }
+
+ public String getVehicleModel() {
+ return vehicleModel;
+ }
+
+ @Override
+ public String toString() {
+ return "SysTaskVehicle{" +
+ "id=" + id +
+ ", taskId=" + taskId +
+ ", vehicleId=" + vehicleId +
+ ", assignTime=" + assignTime +
+ ", assignBy='" + assignBy + '\'' +
+ ", status='" + status + '\'' +
+ ", vehicleNo='" + vehicleNo + '\'' +
+ ", vehicleType='" + vehicleType + '\'' +
+ ", vehicleBrand='" + vehicleBrand + '\'' +
+ ", vehicleModel='" + vehicleModel + '\'' +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskStatus.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskStatus.java
new file mode 100644
index 0000000..9deb1dd
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskStatus.java
@@ -0,0 +1,46 @@
+package com.ruoyi.system.domain.enums;
+
+/**
+ * 浠诲姟鐘舵�佹灇涓�
+ *
+ * @author ruoyi
+ */
+public enum TaskStatus {
+
+ /** 寰呭紑濮� */
+ PENDING("PENDING", "寰呭紑濮�"),
+
+ /** 浠诲姟涓� */
+ IN_PROGRESS("IN_PROGRESS", "浠诲姟涓�"),
+
+ /** 宸插畬鎴� */
+ COMPLETED("COMPLETED", "宸插畬鎴�"),
+
+ /** 宸插彇娑� */
+ CANCELLED("CANCELLED", "宸插彇娑�");
+
+ private final String code;
+ private final String info;
+
+ TaskStatus(String code, String info) {
+ this.code = code;
+ this.info = info;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getInfo() {
+ return info;
+ }
+
+ public static TaskStatus getByCode(String code) {
+ for (TaskStatus status : values()) {
+ if (status.getCode().equals(code)) {
+ return status;
+ }
+ }
+ return null;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskType.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskType.java
new file mode 100644
index 0000000..1cdb98a
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskType.java
@@ -0,0 +1,43 @@
+package com.ruoyi.system.domain.enums;
+
+/**
+ * 浠诲姟绫诲瀷鏋氫妇
+ *
+ * @author ruoyi
+ */
+public enum TaskType {
+
+ /** 缁翠慨淇濆吇 */
+ MAINTENANCE("MAINTENANCE", "缁翠慨淇濆吇"),
+
+ /** 鍔犳补浠诲姟 */
+ FUEL("FUEL", "鍔犳补浠诲姟"),
+
+ /** 鍏朵粬 */
+ OTHER("OTHER", "鍏朵粬");
+
+ private final String code;
+ private final String info;
+
+ TaskType(String code, String info) {
+ this.code = code;
+ this.info = info;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getInfo() {
+ return info;
+ }
+
+ public static TaskType getByCode(String code) {
+ for (TaskType type : values()) {
+ if (type.getCode().equals(code)) {
+ return type;
+ }
+ }
+ return null;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskVehicleStatus.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskVehicleStatus.java
new file mode 100644
index 0000000..4a6c99e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/TaskVehicleStatus.java
@@ -0,0 +1,46 @@
+package com.ruoyi.system.domain.enums;
+
+/**
+ * 浠诲姟杞﹁締鍏宠仈鐘舵�佹灇涓�
+ *
+ * @author ruoyi
+ */
+public enum TaskVehicleStatus {
+
+ /** 宸插垎閰� */
+ ASSIGNED("ASSIGNED", "宸插垎閰�"),
+
+ /** 鎵ц涓� */
+ ACTIVE("ACTIVE", "鎵ц涓�"),
+
+ /** 宸插畬鎴� */
+ COMPLETED("COMPLETED", "宸插畬鎴�"),
+
+ /** 宸插彇娑� */
+ CANCELLED("CANCELLED", "宸插彇娑�");
+
+ private final String code;
+ private final String info;
+
+ TaskVehicleStatus(String code, String info) {
+ this.code = code;
+ this.info = info;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getInfo() {
+ return info;
+ }
+
+ public static TaskVehicleStatus getByCode(String code) {
+ for (TaskVehicleStatus status : values()) {
+ if (status.getCode().equals(code)) {
+ return status;
+ }
+ }
+ return null;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/VehicleType.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/VehicleType.java
new file mode 100644
index 0000000..36d8fb9
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/VehicleType.java
@@ -0,0 +1,43 @@
+package com.ruoyi.system.domain.enums;
+
+/**
+ * 杞﹁締绫诲瀷鏋氫妇
+ *
+ * @author ruoyi
+ */
+public enum VehicleType {
+
+ /** 鏁戞姢杞� */
+ AMBULANCE("AMBULANCE", "鏁戞姢杞�"),
+
+ /** 杞繍杞� */
+ TRANSFER("TRANSFER", "杞繍杞�"),
+
+ /** 缁翠慨杞� */
+ MAINTENANCE("MAINTENANCE", "缁翠慨杞�");
+
+ private final String code;
+ private final String info;
+
+ VehicleType(String code, String info) {
+ this.code = code;
+ this.info = info;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getInfo() {
+ return info;
+ }
+
+ public static VehicleType getByCode(String code) {
+ for (VehicleType type : values()) {
+ if (type.getCode().equals(code)) {
+ return type;
+ }
+ }
+ return null;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java
new file mode 100644
index 0000000..c1f60e4
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java
@@ -0,0 +1,103 @@
+package com.ruoyi.system.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 浠诲姟鍒涘缓瀵硅薄
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class TaskCreateVO {
+
+ /** 浠诲姟绫诲瀷 */
+ private String taskType;
+
+ /** 浠诲姟鎻忚堪 */
+ private String taskDescription;
+
+ /** 鍑哄彂鍦板潃 */
+ private String departureAddress;
+
+ /** 鐩殑鍦板潃 */
+ private String destinationAddress;
+
+ /** 璁″垝寮�濮嬫椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedStartTime;
+
+ /** 璁″垝缁撴潫鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedEndTime;
+
+ /** 鎵ц浜篒D */
+ private Long assigneeId;
+
+ /** 澶囨敞 */
+ private String remark;
+
+ public String getTaskType() {
+ return taskType;
+ }
+
+ public void setTaskType(String taskType) {
+ this.taskType = taskType;
+ }
+
+ public String getTaskDescription() {
+ return taskDescription;
+ }
+
+ public void setTaskDescription(String taskDescription) {
+ this.taskDescription = taskDescription;
+ }
+
+ public String getDepartureAddress() {
+ return departureAddress;
+ }
+
+ public void setDepartureAddress(String departureAddress) {
+ this.departureAddress = departureAddress;
+ }
+
+ public String getDestinationAddress() {
+ return destinationAddress;
+ }
+
+ public void setDestinationAddress(String destinationAddress) {
+ this.destinationAddress = destinationAddress;
+ }
+
+ public Date getPlannedStartTime() {
+ return plannedStartTime;
+ }
+
+ public void setPlannedStartTime(Date plannedStartTime) {
+ this.plannedStartTime = plannedStartTime;
+ }
+
+ public Date getPlannedEndTime() {
+ return plannedEndTime;
+ }
+
+ public void setPlannedEndTime(Date plannedEndTime) {
+ this.plannedEndTime = plannedEndTime;
+ }
+
+ public Long getAssigneeId() {
+ return assigneeId;
+ }
+
+ public void setAssigneeId(Long assigneeId) {
+ this.assigneeId = assigneeId;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskQueryVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskQueryVO.java
new file mode 100644
index 0000000..d300330
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskQueryVO.java
@@ -0,0 +1,140 @@
+package com.ruoyi.system.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 浠诲姟鏌ヨ瀵硅薄 sys_task
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class TaskQueryVO extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 浠诲姟缂栧彿 */
+ private String taskCode;
+
+ /** 浠诲姟绫诲瀷 */
+ private String taskType;
+
+ /** 浠诲姟鐘舵�� */
+ private String taskStatus;
+
+ /** 鍒涘缓浜篒D */
+ private Long creatorId;
+
+ /** 鎵ц浜篒D */
+ private Long assigneeId;
+
+ /** 褰掑睘閮ㄩ棬ID */
+ private Long deptId;
+
+ /** 璁″垝寮�濮嬫椂闂�-寮�濮� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedStartTimeBegin;
+
+ /** 璁″垝寮�濮嬫椂闂�-缁撴潫 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedStartTimeEnd;
+
+ /** 璁″垝缁撴潫鏃堕棿-寮�濮� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedEndTimeBegin;
+
+ /** 璁″垝缁撴潫鏃堕棿-缁撴潫 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedEndTimeEnd;
+
+ /** 鏄惁瓒呮椂 */
+ private Boolean overdue;
+
+ public String getTaskCode() {
+ return taskCode;
+ }
+
+ public void setTaskCode(String taskCode) {
+ this.taskCode = taskCode;
+ }
+
+ public String getTaskType() {
+ return taskType;
+ }
+
+ public void setTaskType(String taskType) {
+ this.taskType = taskType;
+ }
+
+ public String getTaskStatus() {
+ return taskStatus;
+ }
+
+ public void setTaskStatus(String taskStatus) {
+ this.taskStatus = taskStatus;
+ }
+
+ public Long getCreatorId() {
+ return creatorId;
+ }
+
+ public void setCreatorId(Long creatorId) {
+ this.creatorId = creatorId;
+ }
+
+ public Long getAssigneeId() {
+ return assigneeId;
+ }
+
+ public void setAssigneeId(Long assigneeId) {
+ this.assigneeId = assigneeId;
+ }
+
+ public Long getDeptId() {
+ return deptId;
+ }
+
+ public void setDeptId(Long deptId) {
+ this.deptId = deptId;
+ }
+
+ public Date getPlannedStartTimeBegin() {
+ return plannedStartTimeBegin;
+ }
+
+ public void setPlannedStartTimeBegin(Date plannedStartTimeBegin) {
+ this.plannedStartTimeBegin = plannedStartTimeBegin;
+ }
+
+ public Date getPlannedStartTimeEnd() {
+ return plannedStartTimeEnd;
+ }
+
+ public void setPlannedStartTimeEnd(Date plannedStartTimeEnd) {
+ this.plannedStartTimeEnd = plannedStartTimeEnd;
+ }
+
+ public Date getPlannedEndTimeBegin() {
+ return plannedEndTimeBegin;
+ }
+
+ public void setPlannedEndTimeBegin(Date plannedEndTimeBegin) {
+ this.plannedEndTimeBegin = plannedEndTimeBegin;
+ }
+
+ public Date getPlannedEndTimeEnd() {
+ return plannedEndTimeEnd;
+ }
+
+ public void setPlannedEndTimeEnd(Date plannedEndTimeEnd) {
+ this.plannedEndTimeEnd = plannedEndTimeEnd;
+ }
+
+ public Boolean getOverdue() {
+ return overdue;
+ }
+
+ public void setOverdue(Boolean overdue) {
+ this.overdue = overdue;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskStatisticsVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskStatisticsVO.java
new file mode 100644
index 0000000..25c4f20
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskStatisticsVO.java
@@ -0,0 +1,98 @@
+package com.ruoyi.system.domain.vo;
+
+/**
+ * 浠诲姟缁熻瀵硅薄
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class TaskStatisticsVO {
+
+ /** 鎬讳换鍔℃暟 */
+ private Long totalTasks;
+
+ /** 寰呭紑濮嬩换鍔℃暟 */
+ private Long pendingTasks;
+
+ /** 浠诲姟涓暟閲� */
+ private Long inProgressTasks;
+
+ /** 宸插畬鎴愪换鍔℃暟 */
+ private Long completedTasks;
+
+ /** 宸插彇娑堜换鍔℃暟 */
+ private Long cancelledTasks;
+
+ /** 浠婃棩浠诲姟鏁� */
+ private Long todayTasks;
+
+ /** 瓒呮椂浠诲姟鏁� */
+ private Long overdueTasks;
+
+ /** 杞﹁締鍒╃敤鐜� */
+ private Double vehicleUtilization;
+
+ public Long getTotalTasks() {
+ return totalTasks;
+ }
+
+ public void setTotalTasks(Long totalTasks) {
+ this.totalTasks = totalTasks;
+ }
+
+ public Long getPendingTasks() {
+ return pendingTasks;
+ }
+
+ public void setPendingTasks(Long pendingTasks) {
+ this.pendingTasks = pendingTasks;
+ }
+
+ public Long getInProgressTasks() {
+ return inProgressTasks;
+ }
+
+ public void setInProgressTasks(Long inProgressTasks) {
+ this.inProgressTasks = inProgressTasks;
+ }
+
+ public Long getCompletedTasks() {
+ return completedTasks;
+ }
+
+ public void setCompletedTasks(Long completedTasks) {
+ this.completedTasks = completedTasks;
+ }
+
+ public Long getCancelledTasks() {
+ return cancelledTasks;
+ }
+
+ public void setCancelledTasks(Long cancelledTasks) {
+ this.cancelledTasks = cancelledTasks;
+ }
+
+ public Long getTodayTasks() {
+ return todayTasks;
+ }
+
+ public void setTodayTasks(Long todayTasks) {
+ this.todayTasks = todayTasks;
+ }
+
+ public Long getOverdueTasks() {
+ return overdueTasks;
+ }
+
+ public void setOverdueTasks(Long overdueTasks) {
+ this.overdueTasks = overdueTasks;
+ }
+
+ public Double getVehicleUtilization() {
+ return vehicleUtilization;
+ }
+
+ public void setVehicleUtilization(Double vehicleUtilization) {
+ this.vehicleUtilization = vehicleUtilization;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java
new file mode 100644
index 0000000..155b5ed
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java
@@ -0,0 +1,103 @@
+package com.ruoyi.system.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 浠诲姟鏇存柊瀵硅薄
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public class TaskUpdateVO {
+
+ /** 浠诲姟ID */
+ private Long taskId;
+
+ /** 浠诲姟鎻忚堪 */
+ private String taskDescription;
+
+ /** 鍑哄彂鍦板潃 */
+ private String departureAddress;
+
+ /** 鐩殑鍦板潃 */
+ private String destinationAddress;
+
+ /** 璁″垝寮�濮嬫椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedStartTime;
+
+ /** 璁″垝缁撴潫鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date plannedEndTime;
+
+ /** 鎵ц浜篒D */
+ private Long assigneeId;
+
+ /** 澶囨敞 */
+ private String remark;
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public String getTaskDescription() {
+ return taskDescription;
+ }
+
+ public void setTaskDescription(String taskDescription) {
+ this.taskDescription = taskDescription;
+ }
+
+ public String getDepartureAddress() {
+ return departureAddress;
+ }
+
+ public void setDepartureAddress(String departureAddress) {
+ this.departureAddress = departureAddress;
+ }
+
+ public String getDestinationAddress() {
+ return destinationAddress;
+ }
+
+ public void setDestinationAddress(String destinationAddress) {
+ this.destinationAddress = destinationAddress;
+ }
+
+ public Date getPlannedStartTime() {
+ return plannedStartTime;
+ }
+
+ public void setPlannedStartTime(Date plannedStartTime) {
+ this.plannedStartTime = plannedStartTime;
+ }
+
+ public Date getPlannedEndTime() {
+ return plannedEndTime;
+ }
+
+ public void setPlannedEndTime(Date plannedEndTime) {
+ this.plannedEndTime = plannedEndTime;
+ }
+
+ public Long getAssigneeId() {
+ return assigneeId;
+ }
+
+ public void setAssigneeId(Long assigneeId) {
+ this.assigneeId = assigneeId;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAttachmentMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAttachmentMapper.java
new file mode 100644
index 0000000..69b7200
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAttachmentMapper.java
@@ -0,0 +1,77 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysTaskAttachment;
+
+/**
+ * 浠诲姟闄勪欢Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public interface SysTaskAttachmentMapper {
+
+ /**
+ * 鏌ヨ浠诲姟闄勪欢
+ *
+ * @param attachmentId 浠诲姟闄勪欢涓婚敭
+ * @return 浠诲姟闄勪欢
+ */
+ public SysTaskAttachment selectSysTaskAttachmentByAttachmentId(Long attachmentId);
+
+ /**
+ * 鏌ヨ浠诲姟闄勪欢鍒楄〃
+ *
+ * @param sysTaskAttachment 浠诲姟闄勪欢
+ * @return 浠诲姟闄勪欢闆嗗悎
+ */
+ public List<SysTaskAttachment> selectSysTaskAttachmentList(SysTaskAttachment sysTaskAttachment);
+
+ /**
+ * 鏍规嵁浠诲姟ID鏌ヨ闄勪欢鍒楄〃
+ *
+ * @param taskId 浠诲姟ID
+ * @return 浠诲姟闄勪欢闆嗗悎
+ */
+ public List<SysTaskAttachment> selectSysTaskAttachmentByTaskId(Long taskId);
+
+ /**
+ * 鏂板浠诲姟闄勪欢
+ *
+ * @param sysTaskAttachment 浠诲姟闄勪欢
+ * @return 缁撴灉
+ */
+ public int insertSysTaskAttachment(SysTaskAttachment sysTaskAttachment);
+
+ /**
+ * 淇敼浠诲姟闄勪欢
+ *
+ * @param sysTaskAttachment 浠诲姟闄勪欢
+ * @return 缁撴灉
+ */
+ public int updateSysTaskAttachment(SysTaskAttachment sysTaskAttachment);
+
+ /**
+ * 鍒犻櫎浠诲姟闄勪欢
+ *
+ * @param attachmentId 浠诲姟闄勪欢涓婚敭
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskAttachmentByAttachmentId(Long attachmentId);
+
+ /**
+ * 鎵归噺鍒犻櫎浠诲姟闄勪欢
+ *
+ * @param attachmentIds 闇�瑕佸垹闄ょ殑鏁版嵁涓婚敭闆嗗悎
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskAttachmentByAttachmentIds(Long[] attachmentIds);
+
+ /**
+ * 鏍规嵁浠诲姟ID鍒犻櫎闄勪欢
+ *
+ * @param taskId 浠诲姟ID
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskAttachmentByTaskId(Long taskId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskLogMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskLogMapper.java
new file mode 100644
index 0000000..bc90d4e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskLogMapper.java
@@ -0,0 +1,77 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysTaskLog;
+
+/**
+ * 浠诲姟鎿嶄綔鏃ュ織Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public interface SysTaskLogMapper {
+
+ /**
+ * 鏌ヨ浠诲姟鎿嶄綔鏃ュ織
+ *
+ * @param logId 浠诲姟鎿嶄綔鏃ュ織涓婚敭
+ * @return 浠诲姟鎿嶄綔鏃ュ織
+ */
+ public SysTaskLog selectSysTaskLogByLogId(Long logId);
+
+ /**
+ * 鏌ヨ浠诲姟鎿嶄綔鏃ュ織鍒楄〃
+ *
+ * @param sysTaskLog 浠诲姟鎿嶄綔鏃ュ織
+ * @return 浠诲姟鎿嶄綔鏃ュ織闆嗗悎
+ */
+ public List<SysTaskLog> selectSysTaskLogList(SysTaskLog sysTaskLog);
+
+ /**
+ * 鏍规嵁浠诲姟ID鏌ヨ鎿嶄綔鏃ュ織鍒楄〃
+ *
+ * @param taskId 浠诲姟ID
+ * @return 浠诲姟鎿嶄綔鏃ュ織闆嗗悎
+ */
+ public List<SysTaskLog> selectSysTaskLogByTaskId(Long taskId);
+
+ /**
+ * 鏂板浠诲姟鎿嶄綔鏃ュ織
+ *
+ * @param sysTaskLog 浠诲姟鎿嶄綔鏃ュ織
+ * @return 缁撴灉
+ */
+ public int insertSysTaskLog(SysTaskLog sysTaskLog);
+
+ /**
+ * 淇敼浠诲姟鎿嶄綔鏃ュ織
+ *
+ * @param sysTaskLog 浠诲姟鎿嶄綔鏃ュ織
+ * @return 缁撴灉
+ */
+ public int updateSysTaskLog(SysTaskLog sysTaskLog);
+
+ /**
+ * 鍒犻櫎浠诲姟鎿嶄綔鏃ュ織
+ *
+ * @param logId 浠诲姟鎿嶄綔鏃ュ織涓婚敭
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskLogByLogId(Long logId);
+
+ /**
+ * 鎵归噺鍒犻櫎浠诲姟鎿嶄綔鏃ュ織
+ *
+ * @param logIds 闇�瑕佸垹闄ょ殑鏁版嵁涓婚敭闆嗗悎
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskLogByLogIds(Long[] logIds);
+
+ /**
+ * 鏍规嵁浠诲姟ID鍒犻櫎鎿嶄綔鏃ュ織
+ *
+ * @param taskId 浠诲姟ID
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskLogByTaskId(Long taskId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java
new file mode 100644
index 0000000..6a914c5
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java
@@ -0,0 +1,109 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.vo.TaskQueryVO;
+import com.ruoyi.system.domain.vo.TaskStatisticsVO;
+
+/**
+ * 浠诲姟绠$悊Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public interface SysTaskMapper {
+
+ /**
+ * 鏌ヨ浠诲姟绠$悊
+ *
+ * @param taskId 浠诲姟绠$悊涓婚敭
+ * @return 浠诲姟绠$悊
+ */
+ public SysTask selectSysTaskByTaskId(Long taskId);
+
+ /**
+ * 鏌ヨ浠诲姟绠$悊鍒楄〃
+ *
+ * @param sysTask 浠诲姟绠$悊
+ * @return 浠诲姟绠$悊闆嗗悎
+ */
+ public List<SysTask> selectSysTaskList(TaskQueryVO queryVO);
+
+ /**
+ * 鏂板浠诲姟绠$悊
+ *
+ * @param sysTask 浠诲姟绠$悊
+ * @return 缁撴灉
+ */
+ public int insertSysTask(SysTask sysTask);
+
+ /**
+ * 淇敼浠诲姟绠$悊
+ *
+ * @param sysTask 浠诲姟绠$悊
+ * @return 缁撴灉
+ */
+ public int updateSysTask(SysTask sysTask);
+
+ /**
+ * 鍒犻櫎浠诲姟绠$悊
+ *
+ * @param taskId 浠诲姟绠$悊涓婚敭
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskByTaskId(Long taskId);
+
+ /**
+ * 鎵归噺鍒犻櫎浠诲姟绠$悊
+ *
+ * @param taskIds 闇�瑕佸垹闄ょ殑鏁版嵁涓婚敭闆嗗悎
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskByTaskIds(Long[] taskIds);
+
+ /**
+ * 鏌ヨ浠诲姟缁熻淇℃伅
+ *
+ * @return 浠诲姟缁熻淇℃伅
+ */
+ public TaskStatisticsVO selectTaskStatistics();
+
+ /**
+ * 鏌ヨ瓒呮椂浠诲姟鍒楄〃
+ *
+ * @return 瓒呮椂浠诲姟闆嗗悎
+ */
+ public List<SysTask> selectOverdueTasks();
+
+ /**
+ * 鏌ヨ鎴戠殑浠诲姟鍒楄〃
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鎴戠殑浠诲姟闆嗗悎
+ */
+ public List<SysTask> selectMyTasks(Long userId);
+
+ /**
+ * 鏍规嵁浠诲姟缂栧彿鏌ヨ浠诲姟
+ *
+ * @param taskCode 浠诲姟缂栧彿
+ * @return 浠诲姟淇℃伅
+ */
+ public SysTask selectSysTaskByTaskCode(String taskCode);
+
+ /**
+ * 鏇存柊浠诲姟鐘舵��
+ *
+ * @param sysTask 浠诲姟淇℃伅
+ * @return 缁撴灉
+ */
+ public int updateTaskStatus(SysTask sysTask);
+
+ /**
+ * 鍒嗛厤浠诲姟
+ *
+ * @param sysTask 浠诲姟淇℃伅
+ * @return 缁撴灉
+ */
+ public int assignTask(SysTask sysTask);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskVehicleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskVehicleMapper.java
new file mode 100644
index 0000000..1969629
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskVehicleMapper.java
@@ -0,0 +1,103 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysTaskVehicle;
+
+/**
+ * 浠诲姟杞﹁締鍏宠仈Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public interface SysTaskVehicleMapper {
+
+ /**
+ * 鏌ヨ浠诲姟杞﹁締鍏宠仈
+ *
+ * @param id 浠诲姟杞﹁締鍏宠仈涓婚敭
+ * @return 浠诲姟杞﹁締鍏宠仈
+ */
+ public SysTaskVehicle selectSysTaskVehicleById(Long id);
+
+ /**
+ * 鏌ヨ浠诲姟杞﹁締鍏宠仈鍒楄〃
+ *
+ * @param sysTaskVehicle 浠诲姟杞﹁締鍏宠仈
+ * @return 浠诲姟杞﹁締鍏宠仈闆嗗悎
+ */
+ public List<SysTaskVehicle> selectSysTaskVehicleList(SysTaskVehicle sysTaskVehicle);
+
+ /**
+ * 鏍规嵁浠诲姟ID鏌ヨ鍏宠仈杞﹁締鍒楄〃
+ *
+ * @param taskId 浠诲姟ID
+ * @return 浠诲姟杞﹁締鍏宠仈闆嗗悎
+ */
+ public List<SysTaskVehicle> selectSysTaskVehicleByTaskId(Long taskId);
+
+ /**
+ * 鏂板浠诲姟杞﹁締鍏宠仈
+ *
+ * @param sysTaskVehicle 浠诲姟杞﹁締鍏宠仈
+ * @return 缁撴灉
+ */
+ public int insertSysTaskVehicle(SysTaskVehicle sysTaskVehicle);
+
+ /**
+ * 淇敼浠诲姟杞﹁締鍏宠仈
+ *
+ * @param sysTaskVehicle 浠诲姟杞﹁締鍏宠仈
+ * @return 缁撴灉
+ */
+ public int updateSysTaskVehicle(SysTaskVehicle sysTaskVehicle);
+
+ /**
+ * 鍒犻櫎浠诲姟杞﹁締鍏宠仈
+ *
+ * @param id 浠诲姟杞﹁締鍏宠仈涓婚敭
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskVehicleById(Long id);
+
+ /**
+ * 鎵归噺鍒犻櫎浠诲姟杞﹁締鍏宠仈
+ *
+ * @param ids 闇�瑕佸垹闄ょ殑鏁版嵁涓婚敭闆嗗悎
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskVehicleByIds(Long[] ids);
+
+ /**
+ * 鏍规嵁浠诲姟ID鍒犻櫎杞﹁締鍏宠仈
+ *
+ * @param taskId 浠诲姟ID
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskVehicleByTaskId(Long taskId);
+
+ /**
+ * 鏍规嵁浠诲姟ID鍜岃溅杈咺D鍒犻櫎鍏宠仈
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleId 杞﹁締ID
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskVehicleByTaskIdAndVehicleId(Long taskId, Long vehicleId);
+
+ /**
+ * 妫�鏌ヤ换鍔¤溅杈嗗叧鑱旀槸鍚﹀瓨鍦�
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleId 杞﹁締ID
+ * @return 缁撴灉
+ */
+ public int checkTaskVehicleExists(Long taskId, Long vehicleId);
+
+ /**
+ * 鎵归噺鏂板浠诲姟杞﹁締鍏宠仈
+ *
+ * @param sysTaskVehicleList 浠诲姟杞﹁締鍏宠仈鍒楄〃
+ * @return 缁撴灉
+ */
+ public int batchInsertSysTaskVehicle(List<SysTaskVehicle> sysTaskVehicleList);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
new file mode 100644
index 0000000..c56fa71
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
@@ -0,0 +1,174 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.SysTaskVehicle;
+import com.ruoyi.system.domain.SysTaskAttachment;
+import com.ruoyi.system.domain.vo.TaskQueryVO;
+import com.ruoyi.system.domain.vo.TaskCreateVO;
+import com.ruoyi.system.domain.vo.TaskUpdateVO;
+import com.ruoyi.system.domain.vo.TaskStatisticsVO;
+import com.ruoyi.system.domain.enums.TaskStatus;
+
+/**
+ * 浠诲姟绠$悊Service鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+public interface ISysTaskService {
+
+ /**
+ * 鏌ヨ浠诲姟绠$悊
+ *
+ * @param taskId 浠诲姟绠$悊涓婚敭
+ * @return 浠诲姟绠$悊
+ */
+ public SysTask selectSysTaskByTaskId(Long taskId);
+
+ /**
+ * 鏌ヨ浠诲姟绠$悊鍒楄〃
+ *
+ * @param queryVO 浠诲姟鏌ヨ瀵硅薄
+ * @return 浠诲姟绠$悊闆嗗悎
+ */
+ public List<SysTask> selectSysTaskList(TaskQueryVO queryVO);
+
+ /**
+ * 鏂板浠诲姟绠$悊
+ *
+ * @param createVO 浠诲姟鍒涘缓瀵硅薄
+ * @return 缁撴灉
+ */
+ public int insertSysTask(TaskCreateVO createVO);
+
+ /**
+ * 淇敼浠诲姟绠$悊
+ *
+ * @param updateVO 浠诲姟鏇存柊瀵硅薄
+ * @return 缁撴灉
+ */
+ public int updateSysTask(TaskUpdateVO updateVO);
+
+ /**
+ * 鎵归噺鍒犻櫎浠诲姟绠$悊
+ *
+ * @param taskIds 闇�瑕佸垹闄ょ殑浠诲姟绠$悊涓婚敭闆嗗悎
+ * @return 缁撴灉
+ */
+ public int deleteSysTaskByTaskIds(Long[] taskIds);
+
+ /**
+ * 鍒嗛厤浠诲姟
+ *
+ * @param taskId 浠诲姟ID
+ * @param assigneeId 鎵ц浜篒D
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ public int assignTask(Long taskId, Long assigneeId, String remark);
+
+ /**
+ * 鍙樻洿浠诲姟鐘舵��
+ *
+ * @param taskId 浠诲姟ID
+ * @param newStatus 鏂扮姸鎬�
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ public int changeTaskStatus(Long taskId, TaskStatus newStatus, String remark);
+
+ /**
+ * 涓婁紶浠诲姟闄勪欢
+ *
+ * @param taskId 浠诲姟ID
+ * @param file 鏂囦欢
+ * @return 缁撴灉
+ */
+ public int uploadAttachment(Long taskId, MultipartFile file);
+
+ /**
+ * 鍒犻櫎浠诲姟闄勪欢
+ *
+ * @param attachmentId 闄勪欢ID
+ * @return 缁撴灉
+ */
+ public int deleteAttachment(Long attachmentId);
+
+ /**
+ * 鍒嗛厤杞﹁締缁欎换鍔�
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleId 杞﹁締ID
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ public int assignVehicleToTask(Long taskId, Long vehicleId, String remark);
+
+ /**
+ * 鍙栨秷浠诲姟杞﹁締鍒嗛厤
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleId 杞﹁締ID
+ * @return 缁撴灉
+ */
+ public int unassignVehicleFromTask(Long taskId, Long vehicleId);
+
+ /**
+ * 鎵归噺鍒嗛厤杞﹁締缁欎换鍔�
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleIds 杞﹁締ID鍒楄〃
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark);
+
+ /**
+ * 鏌ヨ浠诲姟鍏宠仈鐨勮溅杈�
+ *
+ * @param taskId 浠诲姟ID
+ * @return 浠诲姟杞﹁締鍏宠仈鍒楄〃
+ */
+ public List<SysTaskVehicle> getTaskVehicles(Long taskId);
+
+ /**
+ * 鏌ヨ鍙敤杞﹁締
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @param taskType 浠诲姟绫诲瀷
+ * @return 鍙敤杞﹁締鍒楄〃
+ */
+ public List<SysTaskVehicle> getAvailableVehicles(Long deptId, String taskType);
+
+ /**
+ * 鏌ヨ浠诲姟缁熻淇℃伅
+ *
+ * @return 浠诲姟缁熻淇℃伅
+ */
+ public TaskStatisticsVO getTaskStatistics();
+
+ /**
+ * 鏌ヨ瓒呮椂浠诲姟鍒楄〃
+ *
+ * @return 瓒呮椂浠诲姟鍒楄〃
+ */
+ public List<SysTask> selectOverdueTasks();
+
+ /**
+ * 鏌ヨ鎴戠殑浠诲姟鍒楄〃
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鎴戠殑浠诲姟鍒楄〃
+ */
+ public List<SysTask> selectMyTasks(Long userId);
+
+ /**
+ * 鑾峰彇浠诲姟璇︽儏锛堝寘鍚叧鑱旀暟鎹級
+ *
+ * @param taskId 浠诲姟ID
+ * @return 浠诲姟璇︽儏
+ */
+ public SysTask getTaskDetail(Long taskId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
new file mode 100644
index 0000000..093a6a4
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -0,0 +1,567 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.File;
+import java.io.IOException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.FileUploadUtils;
+import com.ruoyi.common.utils.file.FileUtils;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.SysTaskVehicle;
+import com.ruoyi.system.domain.SysTaskAttachment;
+import com.ruoyi.system.domain.SysTaskLog;
+import com.ruoyi.system.domain.vo.TaskQueryVO;
+import com.ruoyi.system.domain.vo.TaskCreateVO;
+import com.ruoyi.system.domain.vo.TaskUpdateVO;
+import com.ruoyi.system.domain.vo.TaskStatisticsVO;
+import com.ruoyi.system.domain.enums.TaskStatus;
+import com.ruoyi.system.mapper.SysTaskMapper;
+import com.ruoyi.system.mapper.SysTaskVehicleMapper;
+import com.ruoyi.system.mapper.SysTaskAttachmentMapper;
+import com.ruoyi.system.mapper.SysTaskLogMapper;
+import com.ruoyi.system.service.ISysTaskService;
+
+/**
+ * 浠诲姟绠$悊Service涓氬姟灞傚鐞�
+ *
+ * @author ruoyi
+ * @date 2024-01-15
+ */
+@Service
+public class SysTaskServiceImpl implements ISysTaskService {
+
+ @Autowired
+ private SysTaskMapper sysTaskMapper;
+
+ @Autowired
+ private SysTaskVehicleMapper sysTaskVehicleMapper;
+
+ @Autowired
+ private SysTaskAttachmentMapper sysTaskAttachmentMapper;
+
+ @Autowired
+ private SysTaskLogMapper sysTaskLogMapper;
+
+ /**
+ * 鏌ヨ浠诲姟绠$悊
+ *
+ * @param taskId 浠诲姟绠$悊涓婚敭
+ * @return 浠诲姟绠$悊
+ */
+ @Override
+ public SysTask selectSysTaskByTaskId(Long taskId) {
+ return sysTaskMapper.selectSysTaskByTaskId(taskId);
+ }
+
+ /**
+ * 鏌ヨ浠诲姟绠$悊鍒楄〃
+ *
+ * @param queryVO 浠诲姟鏌ヨ瀵硅薄
+ * @return 浠诲姟绠$悊
+ */
+ @Override
+ public List<SysTask> selectSysTaskList(TaskQueryVO queryVO) {
+ return sysTaskMapper.selectSysTaskList(queryVO);
+ }
+
+ /**
+ * 鏂板浠诲姟绠$悊
+ *
+ * @param createVO 浠诲姟鍒涘缓瀵硅薄
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int insertSysTask(TaskCreateVO createVO) {
+ SysTask task = new SysTask();
+ task.setTaskCode(generateTaskCode());
+ task.setTaskType(createVO.getTaskType());
+ task.setTaskStatus(TaskStatus.PENDING.getCode());
+ task.setTaskDescription(createVO.getTaskDescription());
+ task.setDepartureAddress(createVO.getDepartureAddress());
+ task.setDestinationAddress(createVO.getDestinationAddress());
+ task.setPlannedStartTime(createVO.getPlannedStartTime());
+ task.setPlannedEndTime(createVO.getPlannedEndTime());
+ task.setAssigneeId(createVO.getAssigneeId());
+ task.setCreatorId(SecurityUtils.getUserId());
+ task.setDeptId(SecurityUtils.getDeptId());
+ task.setCreateBy(SecurityUtils.getUsername());
+ task.setCreateTime(DateUtils.getNowDate());
+ task.setRemark(createVO.getRemark());
+ task.setDelFlag("0");
+
+ int result = sysTaskMapper.insertSysTask(task);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(task.getTaskId(), "CREATE", "鍒涘缓浠诲姟", null,
+ "浠诲姟绫诲瀷锛�" + createVO.getTaskType(), SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 淇敼浠诲姟绠$悊
+ *
+ * @param updateVO 浠诲姟鏇存柊瀵硅薄
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int updateSysTask(TaskUpdateVO updateVO) {
+ SysTask oldTask = sysTaskMapper.selectSysTaskByTaskId(updateVO.getTaskId());
+ if (oldTask == null) {
+ throw new RuntimeException("浠诲姟涓嶅瓨鍦�");
+ }
+
+ SysTask task = new SysTask();
+ task.setTaskId(updateVO.getTaskId());
+ task.setTaskDescription(updateVO.getTaskDescription());
+ task.setDepartureAddress(updateVO.getDepartureAddress());
+ task.setDestinationAddress(updateVO.getDestinationAddress());
+ task.setPlannedStartTime(updateVO.getPlannedStartTime());
+ task.setPlannedEndTime(updateVO.getPlannedEndTime());
+ task.setAssigneeId(updateVO.getAssigneeId());
+ task.setUpdateBy(SecurityUtils.getUsername());
+ task.setUpdateTime(DateUtils.getNowDate());
+ task.setRemark(updateVO.getRemark());
+
+ int result = sysTaskMapper.updateSysTask(task);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(updateVO.getTaskId(), "UPDATE", "鏇存柊浠诲姟",
+ buildTaskDescription(oldTask), buildTaskDescription(task),
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 鎵归噺鍒犻櫎浠诲姟绠$悊
+ *
+ * @param taskIds 闇�瑕佸垹闄ょ殑浠诲姟绠$悊涓婚敭
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int deleteSysTaskByTaskIds(Long[] taskIds) {
+ int result = 0;
+ for (Long taskId : taskIds) {
+ // 璁板綍鍒犻櫎鏃ュ織
+ recordTaskLog(taskId, "DELETE", "鍒犻櫎浠诲姟", null, null,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ result += sysTaskMapper.deleteSysTaskByTaskId(taskId);
+ }
+ return result;
+ }
+
+ /**
+ * 鍒嗛厤浠诲姟
+ *
+ * @param taskId 浠诲姟ID
+ * @param assigneeId 鎵ц浜篒D
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int assignTask(Long taskId, Long assigneeId, String remark) {
+ SysTask task = new SysTask();
+ task.setTaskId(taskId);
+ task.setAssigneeId(assigneeId);
+ task.setUpdateBy(SecurityUtils.getUsername());
+
+ int result = sysTaskMapper.assignTask(task);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(taskId, "ASSIGN", "鍒嗛厤浠诲姟", null,
+ "鍒嗛厤缁欑敤鎴稩D锛�" + assigneeId + "锛屽娉細" + remark,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 鍙樻洿浠诲姟鐘舵��
+ *
+ * @param taskId 浠诲姟ID
+ * @param newStatus 鏂扮姸鎬�
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int changeTaskStatus(Long taskId, TaskStatus newStatus, String remark) {
+ SysTask oldTask = sysTaskMapper.selectSysTaskByTaskId(taskId);
+ if (oldTask == null) {
+ throw new RuntimeException("浠诲姟涓嶅瓨鍦�");
+ }
+
+ // 楠岃瘉鐘舵�佹祦杞槸鍚﹀悎娉�
+ TaskStatus oldTaskStatus = TaskStatus.getByCode(oldTask.getTaskStatus());
+ if (!oldTask.canChangeStatus(newStatus)) {
+ throw new RuntimeException("鐘舵�佹祦杞笉鍚堟硶锛氫粠 " + oldTaskStatus.getInfo() + " 鍒� " + newStatus.getInfo());
+ }
+
+ SysTask task = new SysTask();
+ task.setTaskId(taskId);
+ task.setTaskStatus(newStatus.getCode());
+ task.setUpdateBy(SecurityUtils.getUsername());
+
+ // 鏍规嵁鐘舵�佽缃浉搴旂殑鏃堕棿
+ if (newStatus == TaskStatus.IN_PROGRESS && oldTask.getActualStartTime() == null) {
+ task.setActualStartTime(DateUtils.getNowDate());
+ } else if (newStatus == TaskStatus.COMPLETED) {
+ task.setActualEndTime(DateUtils.getNowDate());
+ }
+
+ int result = sysTaskMapper.updateTaskStatus(task);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(taskId, "STATUS_CHANGE", "鐘舵�佸彉鏇�",
+ "鐘舵�侊細" + oldTaskStatus.getInfo(),
+ "鐘舵�侊細" + newStatus.getInfo() + "锛屽娉細" + remark,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 涓婁紶浠诲姟闄勪欢
+ *
+ * @param taskId 浠诲姟ID
+ * @param file 鏂囦欢
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int uploadAttachment(Long taskId, MultipartFile file) {
+ try {
+ // 涓婁紶鏂囦欢
+ String fileName = FileUploadUtils.upload("/task", file);
+ String filePath = FileUploadUtils.getDefaultBaseDir() + fileName;
+
+ SysTaskAttachment attachment = new SysTaskAttachment();
+ attachment.setTaskId(taskId);
+ attachment.setFileName(file.getOriginalFilename());
+ attachment.setFilePath(filePath);
+ attachment.setFileSize(file.getSize());
+ attachment.setFileType(getFileType(file.getOriginalFilename()));
+ attachment.setUploadTime(DateUtils.getNowDate());
+ attachment.setUploadBy(SecurityUtils.getUsername());
+
+ int result = sysTaskAttachmentMapper.insertSysTaskAttachment(attachment);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(taskId, "UPDATE", "涓婁紶闄勪欢", null,
+ "涓婁紶鏂囦欢锛�" + file.getOriginalFilename(),
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ } catch (IOException e) {
+ throw new RuntimeException("鏂囦欢涓婁紶澶辫触锛�" + e.getMessage());
+ }
+ }
+
+ /**
+ * 鍒犻櫎浠诲姟闄勪欢
+ *
+ * @param attachmentId 闄勪欢ID
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int deleteAttachment(Long attachmentId) {
+ SysTaskAttachment attachment = sysTaskAttachmentMapper.selectSysTaskAttachmentByAttachmentId(attachmentId);
+ if (attachment == null) {
+ throw new RuntimeException("闄勪欢涓嶅瓨鍦�");
+ }
+
+ // 鍒犻櫎鐗╃悊鏂囦欢
+ try {
+ FileUtils.deleteFile(attachment.getFilePath());
+ } catch (Exception e) {
+ // 蹇界暐鏂囦欢鍒犻櫎澶辫触
+ }
+
+ int result = sysTaskAttachmentMapper.deleteSysTaskAttachmentByAttachmentId(attachmentId);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(attachment.getTaskId(), "UPDATE", "鍒犻櫎闄勪欢",
+ "鍒犻櫎鏂囦欢锛�" + attachment.getFileName(), null,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 鍒嗛厤杞﹁締缁欎换鍔�
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleId 杞﹁締ID
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int assignVehicleToTask(Long taskId, Long vehicleId, String remark) {
+ // 妫�鏌ユ槸鍚﹀凡缁忓垎閰�
+ int exists = sysTaskVehicleMapper.checkTaskVehicleExists(taskId, vehicleId);
+ if (exists > 0) {
+ throw new RuntimeException("杞﹁締宸茬粡鍒嗛厤缁欒浠诲姟");
+ }
+
+ SysTaskVehicle taskVehicle = new SysTaskVehicle();
+ taskVehicle.setTaskId(taskId);
+ taskVehicle.setVehicleId(vehicleId);
+ taskVehicle.setAssignTime(DateUtils.getNowDate());
+ taskVehicle.setAssignBy(SecurityUtils.getUsername());
+ taskVehicle.setStatus("ASSIGNED");
+ taskVehicle.setRemark(remark);
+
+ int result = sysTaskVehicleMapper.insertSysTaskVehicle(taskVehicle);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(taskId, "ASSIGN", "鍒嗛厤杞﹁締", null,
+ "鍒嗛厤杞﹁締ID锛�" + vehicleId + "锛屽娉細" + remark,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 鍙栨秷浠诲姟杞﹁締鍒嗛厤
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleId 杞﹁締ID
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int unassignVehicleFromTask(Long taskId, Long vehicleId) {
+ int result = sysTaskVehicleMapper.deleteSysTaskVehicleByTaskIdAndVehicleId(taskId, vehicleId);
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(taskId, "ASSIGN", "鍙栨秷杞﹁締鍒嗛厤",
+ "鍙栨秷杞﹁締ID锛�" + vehicleId, null,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 鎵归噺鍒嗛厤杞﹁締缁欎换鍔�
+ *
+ * @param taskId 浠诲姟ID
+ * @param vehicleIds 杞﹁締ID鍒楄〃
+ * @param remark 澶囨敞
+ * @return 缁撴灉
+ */
+ @Override
+ @Transactional
+ public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark) {
+ List<SysTaskVehicle> taskVehicles = new ArrayList<>();
+ Date now = DateUtils.getNowDate();
+ String assignBy = SecurityUtils.getUsername();
+
+ for (Long vehicleId : vehicleIds) {
+ // 妫�鏌ユ槸鍚﹀凡缁忓垎閰�
+ int exists = sysTaskVehicleMapper.checkTaskVehicleExists(taskId, vehicleId);
+ if (exists == 0) {
+ SysTaskVehicle taskVehicle = new SysTaskVehicle();
+ taskVehicle.setTaskId(taskId);
+ taskVehicle.setVehicleId(vehicleId);
+ taskVehicle.setAssignTime(now);
+ taskVehicle.setAssignBy(assignBy);
+ taskVehicle.setStatus("ASSIGNED");
+ taskVehicle.setRemark(remark);
+ taskVehicles.add(taskVehicle);
+ }
+ }
+
+ int result = 0;
+ if (!taskVehicles.isEmpty()) {
+ result = sysTaskVehicleMapper.batchInsertSysTaskVehicle(taskVehicles);
+ }
+
+ // 璁板綍鎿嶄綔鏃ュ織
+ if (result > 0) {
+ recordTaskLog(taskId, "ASSIGN", "鎵归噺鍒嗛厤杞﹁締", null,
+ "鍒嗛厤杞﹁締鏁伴噺锛�" + result + "锛屽娉細" + remark,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ }
+
+ return result;
+ }
+
+ /**
+ * 鏌ヨ浠诲姟鍏宠仈鐨勮溅杈�
+ *
+ * @param taskId 浠诲姟ID
+ * @return 浠诲姟杞﹁締鍏宠仈鍒楄〃
+ */
+ @Override
+ public List<SysTaskVehicle> getTaskVehicles(Long taskId) {
+ return sysTaskVehicleMapper.selectSysTaskVehicleByTaskId(taskId);
+ }
+
+ /**
+ * 鏌ヨ鍙敤杞﹁締
+ *
+ * @param deptId 閮ㄩ棬ID
+ * @param taskType 浠诲姟绫诲瀷
+ * @return 鍙敤杞﹁締鍒楄〃
+ */
+ @Override
+ public List<SysTaskVehicle> getAvailableVehicles(Long deptId, String taskType) {
+ // 杩欓噷闇�瑕佹牴鎹笟鍔¢�昏緫鏌ヨ鍙敤杞﹁締
+ // 鏆傛椂杩斿洖绌哄垪琛紝瀹為檯瀹炵幇闇�瑕佹煡璇㈣溅杈嗚〃
+ return new ArrayList<>();
+ }
+
+ /**
+ * 鏌ヨ浠诲姟缁熻淇℃伅
+ *
+ * @return 浠诲姟缁熻淇℃伅
+ */
+ @Override
+ public TaskStatisticsVO getTaskStatistics() {
+ return sysTaskMapper.selectTaskStatistics();
+ }
+
+ /**
+ * 鏌ヨ瓒呮椂浠诲姟鍒楄〃
+ *
+ * @return 瓒呮椂浠诲姟鍒楄〃
+ */
+ @Override
+ public List<SysTask> selectOverdueTasks() {
+ return sysTaskMapper.selectOverdueTasks();
+ }
+
+ /**
+ * 鏌ヨ鎴戠殑浠诲姟鍒楄〃
+ *
+ * @param userId 鐢ㄦ埛ID
+ * @return 鎴戠殑浠诲姟鍒楄〃
+ */
+ @Override
+ public List<SysTask> selectMyTasks(Long userId) {
+ return sysTaskMapper.selectMyTasks(userId);
+ }
+
+ /**
+ * 鑾峰彇浠诲姟璇︽儏锛堝寘鍚叧鑱旀暟鎹級
+ *
+ * @param taskId 浠诲姟ID
+ * @return 浠诲姟璇︽儏
+ */
+ @Override
+ public SysTask getTaskDetail(Long taskId) {
+ SysTask task = sysTaskMapper.selectSysTaskByTaskId(taskId);
+ if (task != null) {
+ // 鏌ヨ鍏宠仈杞﹁締
+ task.setAssignedVehicles(sysTaskVehicleMapper.selectSysTaskVehicleByTaskId(taskId));
+ // 鏌ヨ闄勪欢
+ task.setAttachments(sysTaskAttachmentMapper.selectSysTaskAttachmentByTaskId(taskId));
+ // 鏌ヨ鎿嶄綔鏃ュ織
+ task.setOperationLogs(sysTaskLogMapper.selectSysTaskLogByTaskId(taskId));
+ }
+ return task;
+ }
+
+ /**
+ * 鐢熸垚浠诲姟缂栧彿
+ *
+ * @return 浠诲姟缂栧彿
+ */
+ private String generateTaskCode() {
+ String dateStr = DateUtils.dateTimeNow("yyyyMMdd");
+ return "TASK" + dateStr + "0001";
+ }
+
+ /**
+ * 璁板綍浠诲姟鎿嶄綔鏃ュ織
+ *
+ * @param taskId 浠诲姟ID
+ * @param operationType 鎿嶄綔绫诲瀷
+ * @param operationDesc 鎿嶄綔鎻忚堪
+ * @param oldValue 鎿嶄綔鍓嶅��
+ * @param newValue 鎿嶄綔鍚庡��
+ * @param operatorId 鎿嶄綔浜篒D
+ * @param operatorName 鎿嶄綔浜哄鍚�
+ */
+ private void recordTaskLog(Long taskId, String operationType, String operationDesc,
+ String oldValue, String newValue, Long operatorId, String operatorName) {
+ SysTaskLog log = new SysTaskLog();
+ log.setTaskId(taskId);
+ log.setOperationType(operationType);
+ log.setOperationDesc(operationDesc);
+ log.setOldValue(oldValue);
+ log.setNewValue(newValue);
+ log.setOperatorId(operatorId);
+ log.setOperatorName(operatorName);
+ log.setOperationTime(DateUtils.getNowDate());
+ // 杩欓噷鍙互鑾峰彇IP鍦板潃
+ log.setIpAddress("127.0.0.1");
+
+ sysTaskLogMapper.insertSysTaskLog(log);
+ }
+
+ /**
+ * 鏋勫缓浠诲姟鎻忚堪
+ *
+ * @param task 浠诲姟瀵硅薄
+ * @return 浠诲姟鎻忚堪
+ */
+ private String buildTaskDescription(SysTask task) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("浠诲姟缂栧彿锛�").append(task.getTaskCode()).append("锛�");
+ sb.append("浠诲姟绫诲瀷锛�").append(task.getTaskType()).append("锛�");
+ sb.append("浠诲姟鐘舵�侊細").append(task.getTaskStatus()).append("锛�");
+ if (StringUtils.isNotEmpty(task.getTaskDescription())) {
+ sb.append("浠诲姟鎻忚堪锛�").append(task.getTaskDescription()).append("锛�");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 鑾峰彇鏂囦欢绫诲瀷
+ *
+ * @param fileName 鏂囦欢鍚�
+ * @return 鏂囦欢绫诲瀷
+ */
+ private String getFileType(String fileName) {
+ if (StringUtils.isEmpty(fileName)) {
+ return "";
+ }
+ int lastDotIndex = fileName.lastIndexOf(".");
+ if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
+ return fileName.substring(lastDotIndex + 1).toLowerCase();
+ }
+ return "";
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskCodeGenerator.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskCodeGenerator.java
new file mode 100644
index 0000000..6f73f74
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskCodeGenerator.java
@@ -0,0 +1,38 @@
+package com.ruoyi.system.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.DateUtils;
+
+/**
+ * 浠诲姟缂栧彿鐢熸垚鍣�
+ *
+ * @author ruoyi
+ */
+@Component
+public class TaskCodeGenerator {
+
+ /**
+ * 鐢熸垚浠诲姟缂栧彿
+ * 鏍煎紡锛歍ASK + YYYYMMDD + 4浣嶅簭鍙�
+ *
+ * @return 浠诲姟缂栧彿
+ */
+ public String generateTaskCode() {
+ String dateStr = DateUtils.dateTimeNow("yyyyMMdd");
+ return "TASK" + dateStr + "0001";
+ }
+
+ /**
+ * 鏍规嵁鏃ユ湡鐢熸垚浠诲姟缂栧彿
+ *
+ * @param date 鏃ユ湡
+ * @return 浠诲姟缂栧彿
+ */
+ public String generateTaskCode(Date date) {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+ String dateStr = sdf.format(date);
+ return "TASK" + dateStr + "0001";
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskStatusValidator.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskStatusValidator.java
new file mode 100644
index 0000000..b62b5d6
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/TaskStatusValidator.java
@@ -0,0 +1,66 @@
+package com.ruoyi.system.utils;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.springframework.stereotype.Component;
+import com.ruoyi.system.domain.enums.TaskStatus;
+
+/**
+ * 浠诲姟鐘舵�佹祦杞獙璇佸櫒
+ *
+ * @author ruoyi
+ */
+@Component
+public class TaskStatusValidator {
+
+ private static final Map<TaskStatus, Set<TaskStatus>> ALLOWED_TRANSITIONS = new HashMap<>();
+
+ static {
+ // PENDING -> IN_PROGRESS, CANCELLED
+ Set<TaskStatus> pendingTransitions = new HashSet<>();
+ pendingTransitions.add(TaskStatus.IN_PROGRESS);
+ pendingTransitions.add(TaskStatus.CANCELLED);
+ ALLOWED_TRANSITIONS.put(TaskStatus.PENDING, pendingTransitions);
+
+ // IN_PROGRESS -> COMPLETED, CANCELLED, PENDING
+ Set<TaskStatus> inProgressTransitions = new HashSet<>();
+ inProgressTransitions.add(TaskStatus.COMPLETED);
+ inProgressTransitions.add(TaskStatus.CANCELLED);
+ inProgressTransitions.add(TaskStatus.PENDING);
+ ALLOWED_TRANSITIONS.put(TaskStatus.IN_PROGRESS, inProgressTransitions);
+
+ // COMPLETED -> 涓嶅厑璁镐换浣曠姸鎬佸彉鏇�
+ ALLOWED_TRANSITIONS.put(TaskStatus.COMPLETED, new HashSet<>());
+ // CANCELLED -> 涓嶅厑璁镐换浣曠姸鎬佸彉鏇�
+ ALLOWED_TRANSITIONS.put(TaskStatus.CANCELLED, new HashSet<>());
+ }
+
+ /**
+ * 楠岃瘉鐘舵�佹祦杞槸鍚﹀悎娉�
+ *
+ * @param from 鍘熺姸鎬�
+ * @param to 鐩爣鐘舵��
+ * @return 鏄惁鍏佽娴佽浆
+ */
+ public boolean canTransition(TaskStatus from, TaskStatus to) {
+ if (from == null || to == null) {
+ return false;
+ }
+ return ALLOWED_TRANSITIONS.get(from).contains(to);
+ }
+
+ /**
+ * 鑾峰彇鍏佽鐨勭姸鎬佹祦杞垪琛�
+ *
+ * @param from 鍘熺姸鎬�
+ * @return 鍏佽鐨勭洰鏍囩姸鎬侀泦鍚�
+ */
+ public Set<TaskStatus> getAllowedTransitions(TaskStatus from) {
+ if (from == null) {
+ return new HashSet<>();
+ }
+ return ALLOWED_TRANSITIONS.get(from);
+ }
+}
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskAttachmentMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskAttachmentMapper.xml
new file mode 100644
index 0000000..dcda30a
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskAttachmentMapper.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTaskAttachmentMapper">
+
+ <resultMap type="SysTaskAttachment" id="SysTaskAttachmentResult">
+ <result property="attachmentId" column="attachment_id" />
+ <result property="taskId" column="task_id" />
+ <result property="fileName" column="file_name" />
+ <result property="filePath" column="file_path" />
+ <result property="fileSize" column="file_size" />
+ <result property="fileType" column="file_type" />
+ <result property="uploadTime" column="upload_time" />
+ <result property="uploadBy" column="upload_by" />
+ </resultMap>
+
+ <sql id="selectSysTaskAttachmentVo">
+ select attachment_id, task_id, file_name, file_path, file_size, file_type, upload_time, upload_by
+ from sys_task_attachment
+ </sql>
+
+ <select id="selectSysTaskAttachmentList" parameterType="SysTaskAttachment" resultMap="SysTaskAttachmentResult">
+ <include refid="selectSysTaskAttachmentVo"/>
+ <where>
+ <if test="taskId != null "> and task_id = #{taskId}</if>
+ <if test="fileName != null and fileName != ''"> and file_name like concat('%', #{fileName}, '%')</if>
+ <if test="fileType != null and fileType != ''"> and file_type = #{fileType}</if>
+ <if test="uploadBy != null and uploadBy != ''"> and upload_by like concat('%', #{uploadBy}, '%')</if>
+ </where>
+ order by upload_time desc
+ </select>
+
+ <select id="selectSysTaskAttachmentByAttachmentId" parameterType="Long" resultMap="SysTaskAttachmentResult">
+ <include refid="selectSysTaskAttachmentVo"/>
+ where attachment_id = #{attachmentId}
+ </select>
+
+ <select id="selectSysTaskAttachmentByTaskId" parameterType="Long" resultMap="SysTaskAttachmentResult">
+ <include refid="selectSysTaskAttachmentVo"/>
+ where task_id = #{taskId}
+ order by upload_time desc
+ </select>
+
+ <insert id="insertSysTaskAttachment" parameterType="SysTaskAttachment" useGeneratedKeys="true" keyProperty="attachmentId">
+ insert into sys_task_attachment
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">task_id,</if>
+ <if test="fileName != null and fileName != ''">file_name,</if>
+ <if test="filePath != null and filePath != ''">file_path,</if>
+ <if test="fileSize != null">file_size,</if>
+ <if test="fileType != null">file_type,</if>
+ <if test="uploadTime != null">upload_time,</if>
+ <if test="uploadBy != null and uploadBy != ''">upload_by,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">#{taskId},</if>
+ <if test="fileName != null and fileName != ''">#{fileName},</if>
+ <if test="filePath != null and filePath != ''">#{filePath},</if>
+ <if test="fileSize != null">#{fileSize},</if>
+ <if test="fileType != null">#{fileType},</if>
+ <if test="uploadTime != null">#{uploadTime},</if>
+ <if test="uploadBy != null and uploadBy != ''">#{uploadBy},</if>
+ </trim>
+ </insert>
+
+ <update id="updateSysTaskAttachment" parameterType="SysTaskAttachment">
+ update sys_task_attachment
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="taskId != null">task_id = #{taskId},</if>
+ <if test="fileName != null and fileName != ''">file_name = #{fileName},</if>
+ <if test="filePath != null and filePath != ''">file_path = #{filePath},</if>
+ <if test="fileSize != null">file_size = #{fileSize},</if>
+ <if test="fileType != null">file_type = #{fileType},</if>
+ <if test="uploadTime != null">upload_time = #{uploadTime},</if>
+ <if test="uploadBy != null and uploadBy != ''">upload_by = #{uploadBy},</if>
+ </trim>
+ where attachment_id = #{attachmentId}
+ </update>
+
+ <delete id="deleteSysTaskAttachmentByAttachmentId" parameterType="Long">
+ delete from sys_task_attachment where attachment_id = #{attachmentId}
+ </delete>
+
+ <delete id="deleteSysTaskAttachmentByAttachmentIds" parameterType="String">
+ delete from sys_task_attachment where attachment_id in
+ <foreach item="attachmentId" collection="array" open="(" separator="," close=")">
+ #{attachmentId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteSysTaskAttachmentByTaskId" parameterType="Long">
+ delete from sys_task_attachment where task_id = #{taskId}
+ </delete>
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskLogMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskLogMapper.xml
new file mode 100644
index 0000000..0144d40
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskLogMapper.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTaskLogMapper">
+
+ <resultMap type="SysTaskLog" id="SysTaskLogResult">
+ <result property="logId" column="log_id" />
+ <result property="taskId" column="task_id" />
+ <result property="operationType" column="operation_type" />
+ <result property="operationDesc" column="operation_desc" />
+ <result property="oldValue" column="old_value" />
+ <result property="newValue" column="new_value" />
+ <result property="operatorId" column="operator_id" />
+ <result property="operatorName" column="operator_name" />
+ <result property="operationTime" column="operation_time" />
+ <result property="ipAddress" column="ip_address" />
+ </resultMap>
+
+ <sql id="selectSysTaskLogVo">
+ select log_id, task_id, operation_type, operation_desc, old_value, new_value,
+ operator_id, operator_name, operation_time, ip_address
+ from sys_task_log
+ </sql>
+
+ <select id="selectSysTaskLogList" parameterType="SysTaskLog" resultMap="SysTaskLogResult">
+ <include refid="selectSysTaskLogVo"/>
+ <where>
+ <if test="taskId != null "> and task_id = #{taskId}</if>
+ <if test="operationType != null and operationType != ''"> and operation_type = #{operationType}</if>
+ <if test="operatorId != null "> and operator_id = #{operatorId}</if>
+ <if test="operatorName != null and operatorName != ''"> and operator_name like concat('%', #{operatorName}, '%')</if>
+ </where>
+ order by operation_time desc
+ </select>
+
+ <select id="selectSysTaskLogByLogId" parameterType="Long" resultMap="SysTaskLogResult">
+ <include refid="selectSysTaskLogVo"/>
+ where log_id = #{logId}
+ </select>
+
+ <select id="selectSysTaskLogByTaskId" parameterType="Long" resultMap="SysTaskLogResult">
+ <include refid="selectSysTaskLogVo"/>
+ where task_id = #{taskId}
+ order by operation_time desc
+ </select>
+
+ <insert id="insertSysTaskLog" parameterType="SysTaskLog" useGeneratedKeys="true" keyProperty="logId">
+ insert into sys_task_log
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">task_id,</if>
+ <if test="operationType != null and operationType != ''">operation_type,</if>
+ <if test="operationDesc != null">operation_desc,</if>
+ <if test="oldValue != null">old_value,</if>
+ <if test="newValue != null">new_value,</if>
+ <if test="operatorId != null">operator_id,</if>
+ <if test="operatorName != null and operatorName != ''">operator_name,</if>
+ <if test="operationTime != null">operation_time,</if>
+ <if test="ipAddress != null">ip_address,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">#{taskId},</if>
+ <if test="operationType != null and operationType != ''">#{operationType},</if>
+ <if test="operationDesc != null">#{operationDesc},</if>
+ <if test="oldValue != null">#{oldValue},</if>
+ <if test="newValue != null">#{newValue},</if>
+ <if test="operatorId != null">#{operatorId},</if>
+ <if test="operatorName != null and operatorName != ''">#{operatorName},</if>
+ <if test="operationTime != null">#{operationTime},</if>
+ <if test="ipAddress != null">#{ipAddress},</if>
+ </trim>
+ </insert>
+
+ <update id="updateSysTaskLog" parameterType="SysTaskLog">
+ update sys_task_log
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="taskId != null">task_id = #{taskId},</if>
+ <if test="operationType != null and operationType != ''">operation_type = #{operationType},</if>
+ <if test="operationDesc != null">operation_desc = #{operationDesc},</if>
+ <if test="oldValue != null">old_value = #{oldValue},</if>
+ <if test="newValue != null">new_value = #{newValue},</if>
+ <if test="operatorId != null">operator_id = #{operatorId},</if>
+ <if test="operatorName != null and operatorName != ''">operator_name = #{operatorName},</if>
+ <if test="operationTime != null">operation_time = #{operationTime},</if>
+ <if test="ipAddress != null">ip_address = #{ipAddress},</if>
+ </trim>
+ where log_id = #{logId}
+ </update>
+
+ <delete id="deleteSysTaskLogByLogId" parameterType="Long">
+ delete from sys_task_log where log_id = #{logId}
+ </delete>
+
+ <delete id="deleteSysTaskLogByLogIds" parameterType="String">
+ delete from sys_task_log where log_id in
+ <foreach item="logId" collection="array" open="(" separator="," close=")">
+ #{logId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteSysTaskLogByTaskId" parameterType="Long">
+ delete from sys_task_log where task_id = #{taskId}
+ </delete>
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml
new file mode 100644
index 0000000..359443d
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTaskMapper">
+
+ <resultMap type="SysTask" id="SysTaskResult">
+ <result property="taskId" column="task_id" />
+ <result property="taskCode" column="task_code" />
+ <result property="taskType" column="task_type" />
+ <result property="taskStatus" column="task_status" />
+ <result property="taskDescription" column="task_description" />
+ <result property="departureAddress" column="departure_address" />
+ <result property="destinationAddress" column="destination_address" />
+ <result property="plannedStartTime" column="planned_start_time" />
+ <result property="plannedEndTime" column="planned_end_time" />
+ <result property="actualStartTime" column="actual_start_time" />
+ <result property="actualEndTime" column="actual_end_time" />
+ <result property="creatorId" column="creator_id" />
+ <result property="assigneeId" column="assignee_id" />
+ <result property="deptId" column="dept_id" />
+ <result property="createTime" column="create_time" />
+ <result property="updateTime" column="update_time" />
+ <result property="createBy" column="create_by" />
+ <result property="updateBy" column="update_by" />
+ <result property="remark" column="remark" />
+ <result property="delFlag" column="del_flag" />
+ <result property="creatorName" column="creator_name" />
+ <result property="assigneeName" column="assignee_name" />
+ <result property="deptName" column="dept_name" />
+ </resultMap>
+
+ <sql id="selectSysTaskVo">
+ select t.task_id, t.task_code, t.task_type, t.task_status, t.task_description,
+ t.departure_address, t.destination_address, t.planned_start_time, t.planned_end_time,
+ t.actual_start_time, t.actual_end_time, t.creator_id, t.assignee_id, t.dept_id,
+ t.create_time, t.update_time, t.create_by, t.update_by, t.remark, t.del_flag,
+ u1.nick_name as creator_name, u2.nick_name as assignee_name, d.dept_name
+ from sys_task t
+ left join sys_user u1 on t.creator_id = u1.user_id
+ left join sys_user u2 on t.assignee_id = u2.user_id
+ left join sys_dept d on t.dept_id = d.dept_id
+ </sql>
+
+ <select id="selectSysTaskList" parameterType="TaskQueryVO" resultMap="SysTaskResult">
+ <include refid="selectSysTaskVo"/>
+ <where>
+ t.del_flag = '0'
+ <if test="taskCode != null and taskCode != ''"> and t.task_code like concat('%', #{taskCode}, '%')</if>
+ <if test="taskType != null and taskType != ''"> and t.task_type = #{taskType}</if>
+ <if test="taskStatus != null and taskStatus != ''"> and t.task_status = #{taskStatus}</if>
+ <if test="creatorId != null "> and t.creator_id = #{creatorId}</if>
+ <if test="assigneeId != null "> and t.assignee_id = #{assigneeId}</if>
+ <if test="deptId != null "> and t.dept_id = #{deptId}</if>
+ <if test="plannedStartTimeBegin != null "> and t.planned_start_time >= #{plannedStartTimeBegin}</if>
+ <if test="plannedStartTimeEnd != null "> and t.planned_start_time <= #{plannedStartTimeEnd}</if>
+ <if test="plannedEndTimeBegin != null "> and t.planned_end_time >= #{plannedEndTimeBegin}</if>
+ <if test="plannedEndTimeEnd != null "> and t.planned_end_time <= #{plannedEndTimeEnd}</if>
+ <if test="overdue != null and overdue == true"> and t.planned_end_time < now() and t.task_status != 'COMPLETED'</if>
+ </where>
+ order by t.create_time desc
+ </select>
+
+ <select id="selectSysTaskByTaskId" parameterType="Long" resultMap="SysTaskResult">
+ <include refid="selectSysTaskVo"/>
+ where t.task_id = #{taskId} and t.del_flag = '0'
+ </select>
+
+ <select id="selectSysTaskByTaskCode" parameterType="String" resultMap="SysTaskResult">
+ <include refid="selectSysTaskVo"/>
+ where t.task_code = #{taskCode} and t.del_flag = '0'
+ </select>
+
+ <select id="selectOverdueTasks" resultMap="SysTaskResult">
+ <include refid="selectSysTaskVo"/>
+ where t.del_flag = '0' and t.planned_end_time < now() and t.task_status != 'COMPLETED'
+ order by t.planned_end_time asc
+ </select>
+
+ <select id="selectMyTasks" parameterType="Long" resultMap="SysTaskResult">
+ <include refid="selectSysTaskVo"/>
+ where t.del_flag = '0' and (t.creator_id = #{userId} or t.assignee_id = #{userId})
+ order by t.create_time desc
+ </select>
+
+ <select id="selectTaskStatistics" resultType="TaskStatisticsVO">
+ select
+ count(*) as totalTasks,
+ sum(case when task_status = 'PENDING' then 1 else 0 end) as pendingTasks,
+ sum(case when task_status = 'IN_PROGRESS' then 1 else 0 end) as inProgressTasks,
+ sum(case when task_status = 'COMPLETED' then 1 else 0 end) as completedTasks,
+ sum(case when task_status = 'CANCELLED' then 1 else 0 end) as cancelledTasks,
+ sum(case when date(create_time) = curdate() then 1 else 0 end) as todayTasks,
+ sum(case when planned_end_time < now() and task_status != 'COMPLETED' then 1 else 0 end) as overdueTasks,
+ round(sum(case when task_status = 'IN_PROGRESS' then 1 else 0 end) * 100.0 / count(*), 2) as vehicleUtilization
+ from sys_task
+ where del_flag = '0'
+ </select>
+
+ <insert id="insertSysTask" parameterType="SysTask" useGeneratedKeys="true" keyProperty="taskId">
+ insert into sys_task
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="taskCode != null and taskCode != ''">task_code,</if>
+ <if test="taskType != null and taskType != ''">task_type,</if>
+ <if test="taskStatus != null and taskStatus != ''">task_status,</if>
+ <if test="taskDescription != null">task_description,</if>
+ <if test="departureAddress != null">departure_address,</if>
+ <if test="destinationAddress != null">destination_address,</if>
+ <if test="plannedStartTime != null">planned_start_time,</if>
+ <if test="plannedEndTime != null">planned_end_time,</if>
+ <if test="actualStartTime != null">actual_start_time,</if>
+ <if test="actualEndTime != null">actual_end_time,</if>
+ <if test="creatorId != null">creator_id,</if>
+ <if test="assigneeId != null">assignee_id,</if>
+ <if test="deptId != null">dept_id,</if>
+ <if test="createTime != null">create_time,</if>
+ <if test="updateTime != null">update_time,</if>
+ <if test="createBy != null">create_by,</if>
+ <if test="updateBy != null">update_by,</if>
+ <if test="remark != null">remark,</if>
+ <if test="delFlag != null">del_flag,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="taskCode != null and taskCode != ''">#{taskCode},</if>
+ <if test="taskType != null and taskType != ''">#{taskType},</if>
+ <if test="taskStatus != null and taskStatus != ''">#{taskStatus},</if>
+ <if test="taskDescription != null">#{taskDescription},</if>
+ <if test="departureAddress != null">#{departureAddress},</if>
+ <if test="destinationAddress != null">#{destinationAddress},</if>
+ <if test="plannedStartTime != null">#{plannedStartTime},</if>
+ <if test="plannedEndTime != null">#{plannedEndTime},</if>
+ <if test="actualStartTime != null">#{actualStartTime},</if>
+ <if test="actualEndTime != null">#{actualEndTime},</if>
+ <if test="creatorId != null">#{creatorId},</if>
+ <if test="assigneeId != null">#{assigneeId},</if>
+ <if test="deptId != null">#{deptId},</if>
+ <if test="createTime != null">#{createTime},</if>
+ <if test="updateTime != null">#{updateTime},</if>
+ <if test="createBy != null">#{createBy},</if>
+ <if test="updateBy != null">#{updateBy},</if>
+ <if test="remark != null">#{remark},</if>
+ <if test="delFlag != null">#{delFlag},</if>
+ </trim>
+ </insert>
+
+ <update id="updateSysTask" parameterType="SysTask">
+ update sys_task
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="taskCode != null and taskCode != ''">task_code = #{taskCode},</if>
+ <if test="taskType != null and taskType != ''">task_type = #{taskType},</if>
+ <if test="taskStatus != null and taskStatus != ''">task_status = #{taskStatus},</if>
+ <if test="taskDescription != null">task_description = #{taskDescription},</if>
+ <if test="departureAddress != null">departure_address = #{departureAddress},</if>
+ <if test="destinationAddress != null">destination_address = #{destinationAddress},</if>
+ <if test="plannedStartTime != null">planned_start_time = #{plannedStartTime},</if>
+ <if test="plannedEndTime != null">planned_end_time = #{plannedEndTime},</if>
+ <if test="actualStartTime != null">actual_start_time = #{actualStartTime},</if>
+ <if test="actualEndTime != null">actual_end_time = #{actualEndTime},</if>
+ <if test="creatorId != null">creator_id = #{creatorId},</if>
+ <if test="assigneeId != null">assignee_id = #{assigneeId},</if>
+ <if test="deptId != null">dept_id = #{deptId},</if>
+ <if test="updateTime != null">update_time = #{updateTime},</if>
+ <if test="updateBy != null">update_by = #{updateBy},</if>
+ <if test="remark != null">remark = #{remark},</if>
+ <if test="delFlag != null">del_flag = #{delFlag},</if>
+ </trim>
+ where task_id = #{taskId}
+ </update>
+
+ <update id="updateTaskStatus" parameterType="SysTask">
+ update sys_task set
+ task_status = #{taskStatus},
+ <if test="actualStartTime != null">actual_start_time = #{actualStartTime},</if>
+ <if test="actualEndTime != null">actual_end_time = #{actualEndTime},</if>
+ update_time = now(),
+ update_by = #{updateBy}
+ where task_id = #{taskId}
+ </update>
+
+ <update id="assignTask" parameterType="SysTask">
+ update sys_task set
+ assignee_id = #{assigneeId},
+ update_time = now(),
+ update_by = #{updateBy}
+ where task_id = #{taskId}
+ </update>
+
+ <delete id="deleteSysTaskByTaskId" parameterType="Long">
+ update sys_task set del_flag = '2' where task_id = #{taskId}
+ </delete>
+
+ <delete id="deleteSysTaskByTaskIds" parameterType="String">
+ update sys_task set del_flag = '2' where task_id in
+ <foreach item="taskId" collection="array" open="(" separator="," close=")">
+ #{taskId}
+ </foreach>
+ </delete>
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskVehicleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskVehicleMapper.xml
new file mode 100644
index 0000000..e8a3a0f
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskVehicleMapper.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTaskVehicleMapper">
+
+ <resultMap type="SysTaskVehicle" id="SysTaskVehicleResult">
+ <result property="id" column="id" />
+ <result property="taskId" column="task_id" />
+ <result property="vehicleId" column="vehicle_id" />
+ <result property="assignTime" column="assign_time" />
+ <result property="assignBy" column="assign_by" />
+ <result property="status" column="status" />
+ <result property="remark" column="remark" />
+ <result property="vehicleNo" column="vehicle_no" />
+ <result property="vehicleType" column="vehicle_type" />
+ <result property="vehicleBrand" column="vehicle_brand" />
+ <result property="vehicleModel" column="vehicle_model" />
+ </resultMap>
+
+ <sql id="selectSysTaskVehicleVo">
+ select tv.id, tv.task_id, tv.vehicle_id, tv.assign_time, tv.assign_by, tv.status, tv.remark,
+ v.vehicle_no, v.vehicle_type, v.vehicle_brand, v.vehicle_model
+ from sys_task_vehicle tv
+ left join tb_vehicle_info v on tv.vehicle_id = v.vehicle_id
+ </sql>
+
+ <select id="selectSysTaskVehicleList" parameterType="SysTaskVehicle" resultMap="SysTaskVehicleResult">
+ <include refid="selectSysTaskVehicleVo"/>
+ <where>
+ <if test="taskId != null "> and tv.task_id = #{taskId}</if>
+ <if test="vehicleId != null "> and tv.vehicle_id = #{vehicleId}</if>
+ <if test="status != null and status != ''"> and tv.status = #{status}</if>
+ <if test="assignBy != null and assignBy != ''"> and tv.assign_by like concat('%', #{assignBy}, '%')</if>
+ </where>
+ order by tv.assign_time desc
+ </select>
+
+ <select id="selectSysTaskVehicleById" parameterType="Long" resultMap="SysTaskVehicleResult">
+ <include refid="selectSysTaskVehicleVo"/>
+ where tv.id = #{id}
+ </select>
+
+ <select id="selectSysTaskVehicleByTaskId" parameterType="Long" resultMap="SysTaskVehicleResult">
+ <include refid="selectSysTaskVehicleVo"/>
+ where tv.task_id = #{taskId}
+ order by tv.assign_time desc
+ </select>
+
+ <select id="checkTaskVehicleExists" resultType="int">
+ select count(1) from sys_task_vehicle
+ where task_id = #{taskId} and vehicle_id = #{vehicleId}
+ </select>
+
+ <insert id="insertSysTaskVehicle" parameterType="SysTaskVehicle" useGeneratedKeys="true" keyProperty="id">
+ insert into sys_task_vehicle
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">task_id,</if>
+ <if test="vehicleId != null">vehicle_id,</if>
+ <if test="assignTime != null">assign_time,</if>
+ <if test="assignBy != null and assignBy != ''">assign_by,</if>
+ <if test="status != null and status != ''">status,</if>
+ <if test="remark != null">remark,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">#{taskId},</if>
+ <if test="vehicleId != null">#{vehicleId},</if>
+ <if test="assignTime != null">#{assignTime},</if>
+ <if test="assignBy != null and assignBy != ''">#{assignBy},</if>
+ <if test="status != null and status != ''">#{status},</if>
+ <if test="remark != null">#{remark},</if>
+ </trim>
+ </insert>
+
+ <insert id="batchInsertSysTaskVehicle" parameterType="java.util.List">
+ insert into sys_task_vehicle (task_id, vehicle_id, assign_time, assign_by, status, remark)
+ values
+ <foreach collection="list" item="item" separator=",">
+ (#{item.taskId}, #{item.vehicleId}, #{item.assignTime}, #{item.assignBy}, #{item.status}, #{item.remark})
+ </foreach>
+ </insert>
+
+ <update id="updateSysTaskVehicle" parameterType="SysTaskVehicle">
+ update sys_task_vehicle
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="taskId != null">task_id = #{taskId},</if>
+ <if test="vehicleId != null">vehicle_id = #{vehicleId},</if>
+ <if test="assignTime != null">assign_time = #{assignTime},</if>
+ <if test="assignBy != null and assignBy != ''">assign_by = #{assignBy},</if>
+ <if test="status != null and status != ''">status = #{status},</if>
+ <if test="remark != null">remark = #{remark},</if>
+ </trim>
+ where id = #{id}
+ </update>
+
+ <delete id="deleteSysTaskVehicleById" parameterType="Long">
+ delete from sys_task_vehicle where id = #{id}
+ </delete>
+
+ <delete id="deleteSysTaskVehicleByIds" parameterType="String">
+ delete from sys_task_vehicle where id in
+ <foreach item="id" collection="array" open="(" separator="," close=")">
+ #{id}
+ </foreach>
+ </delete>
+
+ <delete id="deleteSysTaskVehicleByTaskId" parameterType="Long">
+ delete from sys_task_vehicle where task_id = #{taskId}
+ </delete>
+
+ <delete id="deleteSysTaskVehicleByTaskIdAndVehicleId">
+ delete from sys_task_vehicle where task_id = #{taskId} and vehicle_id = #{vehicleId}
+ </delete>
+</mapper>
diff --git a/ruoyi-ui/src/api/task.js b/ruoyi-ui/src/api/task.js
new file mode 100644
index 0000000..abeb5fb
--- /dev/null
+++ b/ruoyi-ui/src/api/task.js
@@ -0,0 +1,159 @@
+import request from '@/utils/request'
+
+// 鏌ヨ浠诲姟绠$悊鍒楄〃
+export function listTask(query) {
+ return request({
+ url: '/task/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鏌ヨ浠诲姟绠$悊璇︾粏
+export function getTask(taskId) {
+ return request({
+ url: '/task/' + taskId,
+ method: 'get'
+ })
+}
+
+// 鏂板浠诲姟绠$悊
+export function addTask(data) {
+ return request({
+ url: '/task',
+ method: 'post',
+ data: data
+ })
+}
+
+// 淇敼浠诲姟绠$悊
+export function updateTask(data) {
+ return request({
+ url: '/task',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鍒犻櫎浠诲姟绠$悊
+export function delTask(taskIds) {
+ return request({
+ url: '/task/' + taskIds,
+ method: 'delete'
+ })
+}
+
+// 鍒嗛厤浠诲姟
+export function assignTask(taskId, data) {
+ return request({
+ url: '/task/' + taskId + '/assign',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鏇存柊浠诲姟鐘舵��
+export function changeTaskStatus(taskId, data) {
+ return request({
+ url: '/task/' + taskId + '/status',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鏌ヨ浠诲姟缁熻淇℃伅
+export function getTaskStatistics() {
+ return request({
+ url: '/task/statistics',
+ method: 'get'
+ })
+}
+
+// 鏌ヨ瓒呮椂浠诲姟鍒楄〃
+export function getOverdueTasks() {
+ return request({
+ url: '/task/overdue',
+ method: 'get'
+ })
+}
+
+// 鏌ヨ鎴戠殑浠诲姟鍒楄〃
+export function getMyTasks() {
+ return request({
+ url: '/task/my',
+ method: 'get'
+ })
+}
+
+// 涓婁紶浠诲姟闄勪欢
+export function uploadAttachment(taskId, file) {
+ const formData = new FormData()
+ formData.append('file', file)
+ return request({
+ url: '/task/attachment/upload/' + taskId,
+ method: 'post',
+ data: formData,
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ })
+}
+
+// 鍒犻櫎浠诲姟闄勪欢
+export function deleteAttachment(attachmentId) {
+ return request({
+ url: '/task/attachment/' + attachmentId,
+ method: 'delete'
+ })
+}
+
+// 鏌ヨ浠诲姟闄勪欢鍒楄〃
+export function getTaskAttachments(taskId) {
+ return request({
+ url: '/task/attachment/list/' + taskId,
+ method: 'get'
+ })
+}
+
+// 鏌ヨ浠诲姟鍏宠仈鐨勮溅杈嗗垪琛�
+export function getTaskVehicles(taskId) {
+ return request({
+ url: '/task/vehicle/list/' + taskId,
+ method: 'get'
+ })
+}
+
+// 鏌ヨ鍙敤杞﹁締鍒楄〃
+export function getAvailableVehicles(deptId, taskType) {
+ return request({
+ url: '/task/vehicle/available',
+ method: 'get',
+ params: { deptId, taskType }
+ })
+}
+
+// 鍒嗛厤杞﹁締缁欎换鍔�
+export function assignVehicleToTask(taskId, data) {
+ return request({
+ url: '/task/vehicle/assign/' + taskId,
+ method: 'post',
+ data: data
+ })
+}
+
+// 鎵归噺鍒嗛厤杞﹁締缁欎换鍔�
+export function assignVehiclesToTask(taskId, data) {
+ return request({
+ url: '/task/vehicle/assign-batch/' + taskId,
+ method: 'post',
+ data: data
+ })
+}
+
+// 鍙栨秷浠诲姟杞﹁締鍒嗛厤
+export function unassignVehicleFromTask(taskId, vehicleId) {
+ return request({
+ url: '/task/vehicle/' + taskId + '/' + vehicleId,
+ method: 'delete'
+ })
+}
diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js
index bf8f5be..41e0f28 100644
--- a/ruoyi-ui/src/router/index.js
+++ b/ruoyi-ui/src/router/index.js
@@ -197,6 +197,20 @@
meta: { title: '淇敼鐢熸垚閰嶇疆', activeMenu: '/tool/gen' }
}
]
+ },
+ {
+ path: '/task/general-detail',
+ component: Layout,
+ hidden: true,
+ permissions: ['task:general:query'],
+ children: [
+ {
+ path: 'index/:taskId(\\d+)',
+ component: () => import('@/views/task/general/detail'),
+ name: 'TaskDetail',
+ meta: { title: '浠诲姟璇︽儏', activeMenu: '/task/general' }
+ }
+ ]
}
]
diff --git a/ruoyi-ui/src/views/task/general/detail.vue b/ruoyi-ui/src/views/task/general/detail.vue
new file mode 100644
index 0000000..99fd176
--- /dev/null
+++ b/ruoyi-ui/src/views/task/general/detail.vue
@@ -0,0 +1,564 @@
+<template>
+ <div class="app-container">
+ <el-card class="box-card">
+ <div slot="header" class="clearfix">
+ <span>浠诲姟璇︽儏</span>
+ <el-button style="float: right; padding: 3px 0" type="text" @click="goBack">杩斿洖</el-button>
+ </div>
+
+ <!-- 鍩烘湰淇℃伅 -->
+ <el-descriptions title="鍩烘湰淇℃伅" :column="2" border>
+ <el-descriptions-item label="浠诲姟缂栧彿">{{ taskDetail.taskCode }}</el-descriptions-item>
+ <el-descriptions-item label="浠诲姟绫诲瀷">
+ <dict-tag :options="dict.type.sys_task_type" :value="taskDetail.taskType"/>
+ </el-descriptions-item>
+ <el-descriptions-item label="浠诲姟鐘舵��">
+ <dict-tag :options="dict.type.sys_task_status" :value="taskDetail.taskStatus"/>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓浜�">{{ taskDetail.creatorName }}</el-descriptions-item>
+ <el-descriptions-item label="鎵ц浜�">{{ taskDetail.assigneeName }}</el-descriptions-item>
+ <el-descriptions-item label="閮ㄩ棬">{{ taskDetail.deptName }}</el-descriptions-item>
+ <el-descriptions-item label="浠诲姟鎻忚堪" :span="2">{{ taskDetail.taskDescription }}</el-descriptions-item>
+ <el-descriptions-item label="鍑哄彂鍦板潃" :span="2">{{ taskDetail.departureAddress }}</el-descriptions-item>
+ <el-descriptions-item label="鐩殑鍦板潃" :span="2">{{ taskDetail.destinationAddress }}</el-descriptions-item>
+ <el-descriptions-item label="璁″垝寮�濮嬫椂闂�">{{ parseTime(taskDetail.plannedStartTime) }}</el-descriptions-item>
+ <el-descriptions-item label="璁″垝缁撴潫鏃堕棿">{{ parseTime(taskDetail.plannedEndTime) }}</el-descriptions-item>
+ <el-descriptions-item label="瀹為檯寮�濮嬫椂闂�">{{ parseTime(taskDetail.actualStartTime) }}</el-descriptions-item>
+ <el-descriptions-item label="瀹為檯缁撴潫鏃堕棿">{{ parseTime(taskDetail.actualEndTime) }}</el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓鏃堕棿">{{ parseTime(taskDetail.createTime) }}</el-descriptions-item>
+ <el-descriptions-item label="鏇存柊鏃堕棿">{{ parseTime(taskDetail.updateTime) }}</el-descriptions-item>
+ <el-descriptions-item label="澶囨敞" :span="2">{{ taskDetail.remark }}</el-descriptions-item>
+ </el-descriptions>
+
+ <!-- 鎿嶄綔鎸夐挳 -->
+ <div style="margin-top: 20px; text-align: center;">
+ <el-button type="primary" @click="handleEdit" v-hasPermi="['task:general:edit']">缂栬緫浠诲姟</el-button>
+ <el-button type="success" @click="handleAssign" v-hasPermi="['task:general:assign']">鍒嗛厤浠诲姟</el-button>
+ <el-button type="warning" @click="handleStatusChange" v-hasPermi="['task:general:status']">鐘舵�佸彉鏇�</el-button>
+ <el-button type="info" @click="handleVehicleAssign" v-hasPermi="['task:general:assign']">鍒嗛厤杞﹁締</el-button>
+ </div>
+ </el-card>
+
+ <!-- 鍏宠仈杞﹁締 -->
+ <el-card class="box-card" style="margin-top: 20px;">
+ <div slot="header" class="clearfix">
+ <span>鍏宠仈杞﹁締</span>
+ <el-button style="float: right; padding: 3px 0" type="text" @click="handleVehicleAssign" v-hasPermi="['task:general:assign']">鍒嗛厤杞﹁締</el-button>
+ </div>
+
+ <el-table :data="taskDetail.assignedVehicles" v-loading="vehicleLoading">
+ <el-table-column label="杞︾墝鍙�" align="center" prop="vehicleNo" />
+ <el-table-column label="杞﹁締绫诲瀷" align="center" prop="vehicleType">
+ <template slot-scope="scope">
+ <dict-tag :options="dict.type.sys_vehicle_type" :value="scope.row.vehicleType"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="杞﹁締鍝佺墝" align="center" prop="vehicleBrand" />
+ <el-table-column label="杞﹁締鍨嬪彿" align="center" prop="vehicleModel" />
+ <el-table-column label="鍒嗛厤鏃堕棿" align="center" prop="assignTime" width="180">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.assignTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒嗛厤浜�" align="center" prop="assignBy" />
+ <el-table-column label="鐘舵��" align="center" prop="status">
+ <template slot-scope="scope">
+ <dict-tag :options="dict.type.sys_task_vehicle_status" :value="scope.row.status"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ @click="handleUnassignVehicle(scope.row)"
+ v-hasPermi="['task:general:assign']"
+ >鍙栨秷鍒嗛厤</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 浠诲姟闄勪欢 -->
+ <el-card class="box-card" style="margin-top: 20px;">
+ <div slot="header" class="clearfix">
+ <span>浠诲姟闄勪欢</span>
+ <el-button style="float: right; padding: 3px 0" type="text" @click="handleUpload" v-hasPermi="['task:general:edit']">涓婁紶闄勪欢</el-button>
+ </div>
+
+ <el-table :data="taskDetail.attachments" v-loading="attachmentLoading">
+ <el-table-column label="鏂囦欢鍚�" align="center" prop="fileName" />
+ <el-table-column label="鏂囦欢绫诲瀷" align="center" prop="fileType" />
+ <el-table-column label="鏂囦欢澶у皬" align="center" prop="fileSize">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婁紶鏃堕棿" align="center" prop="uploadTime" width="180">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.uploadTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婁紶鑰�" align="center" prop="uploadBy" />
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-download"
+ @click="handleDownload(scope.row)"
+ >涓嬭浇</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ @click="handleDeleteAttachment(scope.row)"
+ v-hasPermi="['task:general:edit']"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 鎿嶄綔鏃ュ織 -->
+ <el-card class="box-card" style="margin-top: 20px;">
+ <div slot="header" class="clearfix">
+ <span>鎿嶄綔鏃ュ織</span>
+ </div>
+
+ <el-timeline>
+ <el-timeline-item
+ v-for="log in taskDetail.operationLogs"
+ :key="log.logId"
+ :timestamp="parseTime(log.operationTime)"
+ placement="top"
+ >
+ <el-card>
+ <h4>{{ log.operationDesc }}</h4>
+ <p>鎿嶄綔浜猴細{{ log.operatorName }}</p>
+ <p v-if="log.oldValue">鎿嶄綔鍓嶏細{{ log.oldValue }}</p>
+ <p v-if="log.newValue">鎿嶄綔鍚庯細{{ log.newValue }}</p>
+ </el-card>
+ </el-timeline-item>
+ </el-timeline>
+ </el-card>
+
+ <!-- 缂栬緫浠诲姟瀵硅瘽妗� -->
+ <el-dialog :title="'缂栬緫浠诲姟'" :visible.sync="editOpen" width="600px" append-to-body>
+ <el-form ref="editForm" :model="editForm" :rules="editRules" label-width="80px">
+ <el-form-item label="浠诲姟鎻忚堪" prop="taskDescription">
+ <el-input v-model="editForm.taskDescription" type="textarea" placeholder="璇疯緭鍏ヤ换鍔℃弿杩�" />
+ </el-form-item>
+ <el-form-item label="鍑哄彂鍦板潃" prop="departureAddress">
+ <el-input v-model="editForm.departureAddress" placeholder="璇疯緭鍏ュ嚭鍙戝湴鍧�" />
+ </el-form-item>
+ <el-form-item label="鐩殑鍦板潃" prop="destinationAddress">
+ <el-input v-model="editForm.destinationAddress" placeholder="璇疯緭鍏ョ洰鐨勫湴鍧�" />
+ </el-form-item>
+ <el-form-item label="璁″垝寮�濮嬫椂闂�" prop="plannedStartTime">
+ <el-date-picker clearable
+ v-model="editForm.plannedStartTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ placeholder="璇烽�夋嫨璁″垝寮�濮嬫椂闂�">
+ </el-date-picker>
+ </el-form-item>
+ <el-form-item label="璁″垝缁撴潫鏃堕棿" prop="plannedEndTime">
+ <el-date-picker clearable
+ v-model="editForm.plannedEndTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ placeholder="璇烽�夋嫨璁″垝缁撴潫鏃堕棿">
+ </el-date-picker>
+ </el-form-item>
+ <el-form-item label="鎵ц浜�" prop="assigneeId">
+ <el-select v-model="editForm.assigneeId" placeholder="璇烽�夋嫨鎵ц浜�" clearable>
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="editForm.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitEdit">纭� 瀹�</el-button>
+ <el-button @click="cancelEdit">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 鍒嗛厤浠诲姟瀵硅瘽妗� -->
+ <el-dialog title="鍒嗛厤浠诲姟" :visible.sync="assignOpen" width="500px" append-to-body>
+ <el-form ref="assignForm" :model="assignForm" :rules="assignRules" label-width="80px">
+ <el-form-item label="鎵ц浜�" prop="assigneeId">
+ <el-select v-model="assignForm.assigneeId" placeholder="璇烽�夋嫨鎵ц浜�" clearable>
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="assignForm.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitAssign">纭� 瀹�</el-button>
+ <el-button @click="cancelAssign">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 鐘舵�佸彉鏇村璇濇 -->
+ <el-dialog title="鐘舵�佸彉鏇�" :visible.sync="statusOpen" width="500px" append-to-body>
+ <el-form ref="statusForm" :model="statusForm" :rules="statusRules" label-width="80px">
+ <el-form-item label="浠诲姟鐘舵��" prop="taskStatus">
+ <el-select v-model="statusForm.taskStatus" placeholder="璇烽�夋嫨浠诲姟鐘舵��">
+ <el-option
+ v-for="dict in dict.type.sys_task_status"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="statusForm.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitStatusChange">纭� 瀹�</el-button>
+ <el-button @click="cancelStatusChange">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 鍒嗛厤杞﹁締瀵硅瘽妗� -->
+ <el-dialog title="鍒嗛厤杞﹁締" :visible.sync="vehicleAssignOpen" width="600px" append-to-body>
+ <el-form ref="vehicleAssignForm" :model="vehicleAssignForm" :rules="vehicleAssignRules" label-width="80px">
+ <el-form-item label="杞﹁締" prop="vehicleIds">
+ <el-select v-model="vehicleAssignForm.vehicleIds" placeholder="璇烽�夋嫨杞﹁締" multiple clearable>
+ <el-option
+ v-for="vehicle in availableVehicles"
+ :key="vehicle.vehicleId"
+ :label="vehicle.vehicleNo + ' (' + vehicle.vehicleType + ')'"
+ :value="vehicle.vehicleId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="vehicleAssignForm.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitVehicleAssign">纭� 瀹�</el-button>
+ <el-button @click="cancelVehicleAssign">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 涓婁紶闄勪欢瀵硅瘽妗� -->
+ <el-dialog title="涓婁紶闄勪欢" :visible.sync="uploadOpen" width="500px" append-to-body>
+ <el-upload
+ class="upload-demo"
+ drag
+ :action="uploadUrl"
+ :headers="uploadHeaders"
+ :data="uploadData"
+ :on-success="handleUploadSuccess"
+ :on-error="handleUploadError"
+ :before-upload="beforeUpload"
+ multiple>
+ <i class="el-icon-upload"></i>
+ <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <div class="el-upload__tip" slot="tip">鍙兘涓婁紶jpg/png/pdf/doc/docx鏂囦欢锛屼笖涓嶈秴杩�10MB</div>
+ </el-upload>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask } from "@/api/task";
+import { listUser } from "@/api/system/user";
+import { getToken } from "@/utils/auth";
+
+export default {
+ name: "TaskDetail",
+ dicts: ['sys_task_type', 'sys_task_status', 'sys_vehicle_type', 'sys_task_vehicle_status'],
+ data() {
+ return {
+ // 浠诲姟璇︽儏
+ taskDetail: {
+ assignedVehicles: [],
+ attachments: [],
+ operationLogs: []
+ },
+ // 鏄惁鏄剧ず缂栬緫瀵硅瘽妗�
+ editOpen: false,
+ // 鏄惁鏄剧ず鍒嗛厤瀵硅瘽妗�
+ assignOpen: false,
+ // 鏄惁鏄剧ず鐘舵�佸彉鏇村璇濇
+ statusOpen: false,
+ // 鏄惁鏄剧ず杞﹁締鍒嗛厤瀵硅瘽妗�
+ vehicleAssignOpen: false,
+ // 鏄惁鏄剧ず涓婁紶瀵硅瘽妗�
+ uploadOpen: false,
+ // 缂栬緫琛ㄥ崟
+ editForm: {},
+ // 鍒嗛厤琛ㄥ崟
+ assignForm: {},
+ // 鐘舵�佸彉鏇磋〃鍗�
+ statusForm: {},
+ // 杞﹁締鍒嗛厤琛ㄥ崟
+ vehicleAssignForm: {},
+ // 鐢ㄦ埛鍒楄〃
+ userList: [],
+ // 鍙敤杞﹁締鍒楄〃
+ availableVehicles: [],
+ // 鍔犺浇鐘舵��
+ vehicleLoading: false,
+ attachmentLoading: false,
+ // 涓婁紶鐩稿叧
+ uploadUrl: process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + this.$route.params.taskId,
+ uploadHeaders: {
+ Authorization: "Bearer " + getToken()
+ },
+ uploadData: {},
+ // 琛ㄥ崟鏍¢獙
+ editRules: {
+ taskDescription: [
+ { required: true, message: "浠诲姟鎻忚堪涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ plannedStartTime: [
+ { required: true, message: "璁″垝寮�濮嬫椂闂翠笉鑳戒负绌�", trigger: "blur" }
+ ],
+ plannedEndTime: [
+ { required: true, message: "璁″垝缁撴潫鏃堕棿涓嶈兘涓虹┖", trigger: "blur" }
+ ]
+ },
+ assignRules: {
+ assigneeId: [
+ { required: true, message: "鎵ц浜轰笉鑳戒负绌�", trigger: "change" }
+ ]
+ },
+ statusRules: {
+ taskStatus: [
+ { required: true, message: "浠诲姟鐘舵�佷笉鑳戒负绌�", trigger: "change" }
+ ]
+ },
+ vehicleAssignRules: {
+ vehicleIds: [
+ { required: true, message: "杞﹁締涓嶈兘涓虹┖", trigger: "change" }
+ ]
+ }
+ };
+ },
+ created() {
+ this.getTaskDetail();
+ this.getUserList();
+ },
+ methods: {
+ /** 鑾峰彇浠诲姟璇︽儏 */
+ getTaskDetail() {
+ getTask(this.$route.params.taskId).then(response => {
+ this.taskDetail = response.data;
+ });
+ },
+ /** 鑾峰彇鐢ㄦ埛鍒楄〃 */
+ getUserList() {
+ listUser().then(response => {
+ this.userList = response.rows;
+ });
+ },
+ /** 鑾峰彇鍙敤杞﹁締鍒楄〃 */
+ getAvailableVehicleList() {
+ getAvailableVehicles(this.taskDetail.deptId, this.taskDetail.taskType).then(response => {
+ this.availableVehicles = response.data;
+ });
+ },
+ /** 杩斿洖 */
+ goBack() {
+ this.$router.go(-1);
+ },
+ /** 缂栬緫浠诲姟 */
+ handleEdit() {
+ this.editForm = {
+ taskId: this.taskDetail.taskId,
+ taskDescription: this.taskDetail.taskDescription,
+ departureAddress: this.taskDetail.departureAddress,
+ destinationAddress: this.taskDetail.destinationAddress,
+ plannedStartTime: this.taskDetail.plannedStartTime,
+ plannedEndTime: this.taskDetail.plannedEndTime,
+ assigneeId: this.taskDetail.assigneeId,
+ remark: this.taskDetail.remark
+ };
+ this.editOpen = true;
+ },
+ /** 鍒嗛厤浠诲姟 */
+ handleAssign() {
+ this.assignForm = {
+ taskId: this.taskDetail.taskId,
+ assigneeId: this.taskDetail.assigneeId,
+ remark: null
+ };
+ this.assignOpen = true;
+ },
+ /** 鐘舵�佸彉鏇� */
+ handleStatusChange() {
+ this.statusForm = {
+ taskId: this.taskDetail.taskId,
+ taskStatus: this.taskDetail.taskStatus,
+ remark: null
+ };
+ this.statusOpen = true;
+ },
+ /** 鍒嗛厤杞﹁締 */
+ handleVehicleAssign() {
+ this.getAvailableVehicleList();
+ this.vehicleAssignForm = {
+ taskId: this.taskDetail.taskId,
+ vehicleIds: [],
+ remark: null
+ };
+ this.vehicleAssignOpen = true;
+ },
+ /** 涓婁紶闄勪欢 */
+ handleUpload() {
+ this.uploadOpen = true;
+ },
+ /** 鍙栨秷杞﹁締鍒嗛厤 */
+ handleUnassignVehicle(row) {
+ this.$modal.confirm('鏄惁纭鍙栨秷杞﹁締"' + row.vehicleNo + '"鐨勫垎閰嶏紵').then(() => {
+ return unassignVehicleFromTask(this.taskDetail.taskId, row.vehicleId);
+ }).then(() => {
+ this.$modal.msgSuccess("鍙栨秷鍒嗛厤鎴愬姛");
+ this.getTaskDetail();
+ }).catch(() => {});
+ },
+ /** 涓嬭浇闄勪欢 */
+ handleDownload(row) {
+ window.open(row.filePath);
+ },
+ /** 鍒犻櫎闄勪欢 */
+ handleDeleteAttachment(row) {
+ this.$modal.confirm('鏄惁纭鍒犻櫎闄勪欢"' + row.fileName + '"锛�').then(() => {
+ return deleteAttachment(row.attachmentId);
+ }).then(() => {
+ this.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ this.getTaskDetail();
+ }).catch(() => {});
+ },
+ /** 鎻愪氦缂栬緫 */
+ submitEdit() {
+ this.$refs["editForm"].validate(valid => {
+ if (valid) {
+ updateTask(this.editForm).then(response => {
+ this.$modal.msgSuccess("淇敼鎴愬姛");
+ this.editOpen = false;
+ this.getTaskDetail();
+ });
+ }
+ });
+ },
+ /** 鎻愪氦鍒嗛厤 */
+ submitAssign() {
+ this.$refs["assignForm"].validate(valid => {
+ if (valid) {
+ assignTask(this.assignForm.taskId, this.assignForm).then(response => {
+ this.$modal.msgSuccess("鍒嗛厤鎴愬姛");
+ this.assignOpen = false;
+ this.getTaskDetail();
+ });
+ }
+ });
+ },
+ /** 鎻愪氦鐘舵�佸彉鏇� */
+ submitStatusChange() {
+ this.$refs["statusForm"].validate(valid => {
+ if (valid) {
+ changeTaskStatus(this.statusForm.taskId, this.statusForm).then(response => {
+ this.$modal.msgSuccess("鐘舵�佸彉鏇存垚鍔�");
+ this.statusOpen = false;
+ this.getTaskDetail();
+ });
+ }
+ });
+ },
+ /** 鎻愪氦杞﹁締鍒嗛厤 */
+ submitVehicleAssign() {
+ this.$refs["vehicleAssignForm"].validate(valid => {
+ if (valid) {
+ assignVehiclesToTask(this.vehicleAssignForm.taskId, this.vehicleAssignForm).then(response => {
+ this.$modal.msgSuccess("鍒嗛厤鎴愬姛");
+ this.vehicleAssignOpen = false;
+ this.getTaskDetail();
+ });
+ }
+ });
+ },
+ /** 鍙栨秷缂栬緫 */
+ cancelEdit() {
+ this.editOpen = false;
+ this.editForm = {};
+ },
+ /** 鍙栨秷鍒嗛厤 */
+ cancelAssign() {
+ this.assignOpen = false;
+ this.assignForm = {};
+ },
+ /** 鍙栨秷鐘舵�佸彉鏇� */
+ cancelStatusChange() {
+ this.statusOpen = false;
+ this.statusForm = {};
+ },
+ /** 鍙栨秷杞﹁締鍒嗛厤 */
+ cancelVehicleAssign() {
+ this.vehicleAssignOpen = false;
+ this.vehicleAssignForm = {};
+ },
+ /** 涓婁紶鍓嶆鏌� */
+ beforeUpload(file) {
+ const isValidType = ['image/jpeg', 'image/png', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'].includes(file.type);
+ const isLt10M = file.size / 1024 / 1024 < 10;
+
+ if (!isValidType) {
+ this.$message.error('鍙兘涓婁紶 JPG/PNG/PDF/DOC/DOCX 鏍煎紡鐨勬枃浠�!');
+ }
+ if (!isLt10M) {
+ this.$message.error('涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!');
+ }
+ return isValidType && isLt10M;
+ },
+ /** 涓婁紶鎴愬姛 */
+ handleUploadSuccess(response, file, fileList) {
+ this.$modal.msgSuccess("涓婁紶鎴愬姛");
+ this.uploadOpen = false;
+ this.getTaskDetail();
+ },
+ /** 涓婁紶澶辫触 */
+ handleUploadError(err, file, fileList) {
+ this.$modal.msgError("涓婁紶澶辫触");
+ },
+ /** 鏍煎紡鍖栨枃浠跺ぇ灏� */
+ formatFileSize(size) {
+ if (size < 1024) {
+ return size + ' B';
+ } else if (size < 1024 * 1024) {
+ return (size / 1024).toFixed(2) + ' KB';
+ } else {
+ return (size / 1024 / 1024).toFixed(2) + ' MB';
+ }
+ }
+ }
+};
+</script>
+
+<style scoped>
+.box-card {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/ruoyi-ui/src/views/task/general/index.vue b/ruoyi-ui/src/views/task/general/index.vue
new file mode 100644
index 0000000..9feb3cb
--- /dev/null
+++ b/ruoyi-ui/src/views/task/general/index.vue
@@ -0,0 +1,535 @@
+<template>
+ <div class="app-container">
+ <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+ <el-form-item label="浠诲姟缂栧彿" prop="taskCode">
+ <el-input
+ v-model="queryParams.taskCode"
+ placeholder="璇疯緭鍏ヤ换鍔$紪鍙�"
+ clearable
+ @keyup.enter.native="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="浠诲姟绫诲瀷" prop="taskType">
+ <el-select v-model="queryParams.taskType" placeholder="璇烽�夋嫨浠诲姟绫诲瀷" clearable>
+ <el-option
+ v-for="dict in dict.type.sys_task_type"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠诲姟鐘舵��" prop="taskStatus">
+ <el-select v-model="queryParams.taskStatus" placeholder="璇烽�夋嫨浠诲姟鐘舵��" clearable>
+ <el-option
+ v-for="dict in dict.type.sys_task_status"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁″垝寮�濮嬫椂闂�">
+ <el-date-picker
+ v-model="dateRange"
+ style="width: 240px"
+ value-format="yyyy-MM-dd"
+ type="daterange"
+ range-separator="-"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ ></el-date-picker>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button
+ type="primary"
+ plain
+ icon="el-icon-plus"
+ size="mini"
+ @click="handleAdd"
+ v-hasPermi="['task:general:add']"
+ >鏂板</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="success"
+ plain
+ icon="el-icon-edit"
+ size="mini"
+ :disabled="single"
+ @click="handleUpdate"
+ v-hasPermi="['task:general:edit']"
+ >淇敼</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="danger"
+ plain
+ icon="el-icon-delete"
+ size="mini"
+ :disabled="multiple"
+ @click="handleDelete"
+ v-hasPermi="['task:general:remove']"
+ >鍒犻櫎</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button
+ type="warning"
+ plain
+ icon="el-icon-download"
+ size="mini"
+ @click="handleExport"
+ v-hasPermi="['task:general:export']"
+ >瀵煎嚭</el-button>
+ </el-col>
+ <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+ </el-row>
+
+ <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column label="浠诲姟缂栧彿" align="center" prop="taskCode" />
+ <el-table-column label="浠诲姟绫诲瀷" align="center" prop="taskType">
+ <template slot-scope="scope">
+ <dict-tag :options="dict.type.sys_task_type" :value="scope.row.taskType"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="浠诲姟鐘舵��" align="center" prop="taskStatus">
+ <template slot-scope="scope">
+ <dict-tag :options="dict.type.sys_task_status" :value="scope.row.taskStatus"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="浠诲姟鎻忚堪" align="center" prop="taskDescription" show-overflow-tooltip />
+ <el-table-column label="鍑哄彂鍦板潃" align="center" prop="departureAddress" show-overflow-tooltip />
+ <el-table-column label="鐩殑鍦板潃" align="center" prop="destinationAddress" show-overflow-tooltip />
+ <el-table-column label="璁″垝寮�濮嬫椂闂�" align="center" prop="plannedStartTime" width="180">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.plannedStartTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="璁″垝缁撴潫鏃堕棿" align="center" prop="plannedEndTime" width="180">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.plannedEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒涘缓浜�" align="center" prop="creatorName" />
+ <el-table-column label="鎵ц浜�" align="center" prop="assigneeName" />
+ <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleView(scope.row)"
+ v-hasPermi="['task:general:query']"
+ >鏌ョ湅</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-edit"
+ @click="handleUpdate(scope.row)"
+ v-hasPermi="['task:general:edit']"
+ >淇敼</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-user"
+ @click="handleAssign(scope.row)"
+ v-hasPermi="['task:general:assign']"
+ >鍒嗛厤</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-refresh"
+ @click="handleStatusChange(scope.row)"
+ v-hasPermi="['task:general:status']"
+ >鐘舵��</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ @click="handleDelete(scope.row)"
+ v-hasPermi="['task:general:remove']"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ v-show="total>0"
+ :total="total"
+ :page.sync="queryParams.pageNum"
+ :limit.sync="queryParams.pageSize"
+ @pagination="getList"
+ />
+
+ <!-- 娣诲姞鎴栦慨鏀逛换鍔$鐞嗗璇濇 -->
+ <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+ <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+ <el-form-item label="浠诲姟绫诲瀷" prop="taskType">
+ <el-select v-model="form.taskType" placeholder="璇烽�夋嫨浠诲姟绫诲瀷">
+ <el-option
+ v-for="dict in dict.type.sys_task_type"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠诲姟鎻忚堪" prop="taskDescription">
+ <el-input v-model="form.taskDescription" type="textarea" placeholder="璇疯緭鍏ヤ换鍔℃弿杩�" />
+ </el-form-item>
+ <el-form-item label="鍑哄彂鍦板潃" prop="departureAddress">
+ <el-input v-model="form.departureAddress" placeholder="璇疯緭鍏ュ嚭鍙戝湴鍧�" />
+ </el-form-item>
+ <el-form-item label="鐩殑鍦板潃" prop="destinationAddress">
+ <el-input v-model="form.destinationAddress" placeholder="璇疯緭鍏ョ洰鐨勫湴鍧�" />
+ </el-form-item>
+ <el-form-item label="璁″垝寮�濮嬫椂闂�" prop="plannedStartTime">
+ <el-date-picker clearable
+ v-model="form.plannedStartTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ placeholder="璇烽�夋嫨璁″垝寮�濮嬫椂闂�">
+ </el-date-picker>
+ </el-form-item>
+ <el-form-item label="璁″垝缁撴潫鏃堕棿" prop="plannedEndTime">
+ <el-date-picker clearable
+ v-model="form.plannedEndTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ placeholder="璇烽�夋嫨璁″垝缁撴潫鏃堕棿">
+ </el-date-picker>
+ </el-form-item>
+ <el-form-item label="鎵ц浜�" prop="assigneeId">
+ <el-select v-model="form.assigneeId" placeholder="璇烽�夋嫨鎵ц浜�" clearable>
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+ <el-button @click="cancel">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+
+ <!-- 浠诲姟鍒嗛厤瀵硅瘽妗� -->
+ <el-dialog title="浠诲姟鍒嗛厤" :visible.sync="assignOpen" width="500px" append-to-body>
+ <el-form ref="assignForm" :model="assignForm" :rules="assignRules" label-width="80px">
+ <el-form-item label="鎵ц浜�" prop="assigneeId">
+ <el-select v-model="assignForm.assigneeId" placeholder="璇烽�夋嫨鎵ц浜�" clearable>
+ <el-option
+ v-for="user in userList"
+ :key="user.userId"
+ :label="user.nickName"
+ :value="user.userId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="assignForm.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitAssign">纭� 瀹�</el-button>
+ <el-button @click="cancelAssign">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 鐘舵�佸彉鏇村璇濇 -->
+ <el-dialog title="鐘舵�佸彉鏇�" :visible.sync="statusOpen" width="500px" append-to-body>
+ <el-form ref="statusForm" :model="statusForm" :rules="statusRules" label-width="80px">
+ <el-form-item label="浠诲姟鐘舵��" prop="taskStatus">
+ <el-select v-model="statusForm.taskStatus" placeholder="璇烽�夋嫨浠诲姟鐘舵��">
+ <el-option
+ v-for="dict in dict.type.sys_task_status"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="statusForm.remark" type="textarea" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitStatusChange">纭� 瀹�</el-button>
+ <el-button @click="cancelStatusChange">鍙� 娑�</el-button>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import { listTask, getTask, delTask, addTask, updateTask, assignTask, changeTaskStatus } from "@/api/task";
+import { listUser } from "@/api/system/user";
+
+export default {
+ name: "Task",
+ dicts: ['sys_task_type', 'sys_task_status'],
+ data() {
+ return {
+ // 閬僵灞�
+ loading: true,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鏄剧ず鎼滅储鏉′欢
+ showSearch: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 浠诲姟绠$悊琛ㄦ牸鏁版嵁
+ taskList: [],
+ // 寮瑰嚭灞傛爣棰�
+ title: "",
+ // 鏄惁鏄剧ず寮瑰嚭灞�
+ open: false,
+ // 鏄惁鏄剧ず鍒嗛厤寮瑰嚭灞�
+ assignOpen: false,
+ // 鏄惁鏄剧ず鐘舵�佸彉鏇村脊鍑哄眰
+ statusOpen: false,
+ // 鏃ユ湡鑼冨洿
+ dateRange: [],
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ taskCode: null,
+ taskType: null,
+ taskStatus: null,
+ plannedStartTimeBegin: null,
+ plannedStartTimeEnd: null,
+ },
+ // 琛ㄥ崟鍙傛暟
+ form: {},
+ // 鍒嗛厤琛ㄥ崟
+ assignForm: {},
+ // 鐘舵�佸彉鏇磋〃鍗�
+ statusForm: {},
+ // 鐢ㄦ埛鍒楄〃
+ userList: [],
+ // 琛ㄥ崟鏍¢獙
+ rules: {
+ taskType: [
+ { required: true, message: "浠诲姟绫诲瀷涓嶈兘涓虹┖", trigger: "change" }
+ ],
+ taskDescription: [
+ { required: true, message: "浠诲姟鎻忚堪涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ plannedStartTime: [
+ { required: true, message: "璁″垝寮�濮嬫椂闂翠笉鑳戒负绌�", trigger: "blur" }
+ ],
+ plannedEndTime: [
+ { required: true, message: "璁″垝缁撴潫鏃堕棿涓嶈兘涓虹┖", trigger: "blur" }
+ ]
+ },
+ // 鍒嗛厤琛ㄥ崟鏍¢獙
+ assignRules: {
+ assigneeId: [
+ { required: true, message: "鎵ц浜轰笉鑳戒负绌�", trigger: "change" }
+ ]
+ },
+ // 鐘舵�佸彉鏇磋〃鍗曟牎楠�
+ statusRules: {
+ taskStatus: [
+ { required: true, message: "浠诲姟鐘舵�佷笉鑳戒负绌�", trigger: "change" }
+ ]
+ }
+ };
+ },
+ created() {
+ this.getList();
+ this.getUserList();
+ },
+ methods: {
+ /** 鏌ヨ浠诲姟绠$悊鍒楄〃 */
+ getList() {
+ this.loading = true;
+ if (this.dateRange != null && this.dateRange.length === 2) {
+ this.queryParams.plannedStartTimeBegin = this.dateRange[0];
+ this.queryParams.plannedStartTimeEnd = this.dateRange[1];
+ }
+ listTask(this.queryParams).then(response => {
+ this.taskList = response.rows;
+ this.total = response.total;
+ this.loading = false;
+ });
+ },
+ /** 鏌ヨ鐢ㄦ埛鍒楄〃 */
+ getUserList() {
+ listUser().then(response => {
+ this.userList = response.rows;
+ });
+ },
+ // 鍙栨秷鎸夐挳
+ cancel() {
+ this.open = false;
+ this.reset();
+ },
+ // 琛ㄥ崟閲嶇疆
+ reset() {
+ this.form = {
+ taskId: null,
+ taskType: null,
+ taskDescription: null,
+ departureAddress: null,
+ destinationAddress: null,
+ plannedStartTime: null,
+ plannedEndTime: null,
+ assigneeId: null,
+ remark: null
+ };
+ this.resetForm("form");
+ },
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ /** 閲嶇疆鎸夐挳鎿嶄綔 */
+ resetQuery() {
+ this.dateRange = [];
+ this.resetForm("queryForm");
+ this.handleQuery();
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.taskId)
+ this.single = selection.length!==1
+ this.multiple = !selection.length
+ },
+ /** 鏂板鎸夐挳鎿嶄綔 */
+ handleAdd() {
+ this.reset();
+ this.open = true;
+ this.title = "娣诲姞浠诲姟绠$悊";
+ },
+ /** 淇敼鎸夐挳鎿嶄綔 */
+ handleUpdate(row) {
+ this.reset();
+ const taskId = row.taskId || this.ids
+ getTask(taskId).then(response => {
+ this.form = response.data;
+ this.open = true;
+ this.title = "淇敼浠诲姟绠$悊";
+ });
+ },
+ /** 鏌ョ湅鎸夐挳鎿嶄綔 */
+ handleView(row) {
+ this.$router.push('/task/general-detail/index/' + row.taskId);
+ },
+ /** 鍒嗛厤鎸夐挳鎿嶄綔 */
+ handleAssign(row) {
+ this.assignForm = {
+ taskId: row.taskId,
+ assigneeId: row.assigneeId,
+ remark: null
+ };
+ this.assignOpen = true;
+ },
+ /** 鐘舵�佸彉鏇存寜閽搷浣� */
+ handleStatusChange(row) {
+ this.statusForm = {
+ taskId: row.taskId,
+ taskStatus: row.taskStatus,
+ remark: null
+ };
+ this.statusOpen = true;
+ },
+ /** 鎻愪氦鎸夐挳 */
+ submitForm() {
+ this.$refs["form"].validate(valid => {
+ if (valid) {
+ if (this.form.taskId != null) {
+ updateTask(this.form).then(response => {
+ this.$modal.msgSuccess("淇敼鎴愬姛");
+ this.open = false;
+ this.getList();
+ });
+ } else {
+ addTask(this.form).then(response => {
+ this.$modal.msgSuccess("鏂板鎴愬姛");
+ this.open = false;
+ this.getList();
+ });
+ }
+ }
+ });
+ },
+ /** 鎻愪氦鍒嗛厤 */
+ submitAssign() {
+ this.$refs["assignForm"].validate(valid => {
+ if (valid) {
+ assignTask(this.assignForm.taskId, this.assignForm).then(response => {
+ this.$modal.msgSuccess("鍒嗛厤鎴愬姛");
+ this.assignOpen = false;
+ this.getList();
+ });
+ }
+ });
+ },
+ /** 鎻愪氦鐘舵�佸彉鏇� */
+ submitStatusChange() {
+ this.$refs["statusForm"].validate(valid => {
+ if (valid) {
+ changeTaskStatus(this.statusForm.taskId, this.statusForm).then(response => {
+ this.$modal.msgSuccess("鐘舵�佸彉鏇存垚鍔�");
+ this.statusOpen = false;
+ this.getList();
+ });
+ }
+ });
+ },
+ /** 鍒犻櫎鎸夐挳鎿嶄綔 */
+ handleDelete(row) {
+ const taskIds = row.taskId || this.ids;
+ this.$modal.confirm('鏄惁纭鍒犻櫎浠诲姟绠$悊缂栧彿涓�"' + taskIds + '"鐨勬暟鎹」锛�').then(function() {
+ return delTask(taskIds);
+ }).then(() => {
+ this.getList();
+ this.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ }).catch(() => {});
+ },
+ /** 瀵煎嚭鎸夐挳鎿嶄綔 */
+ handleExport() {
+ this.download('task/export', {
+ ...this.queryParams
+ }, `task_${new Date().getTime()}.xlsx`)
+ },
+ /** 鍙栨秷鍒嗛厤 */
+ cancelAssign() {
+ this.assignOpen = false;
+ this.assignForm = {};
+ },
+ /** 鍙栨秷鐘舵�佸彉鏇� */
+ cancelStatusChange() {
+ this.statusOpen = false;
+ this.statusForm = {};
+ }
+ }
+};
+</script>
diff --git a/sql/task_dict_data.sql b/sql/task_dict_data.sql
new file mode 100644
index 0000000..cad5c34
--- /dev/null
+++ b/sql/task_dict_data.sql
@@ -0,0 +1,39 @@
+-- ----------------------------
+-- 閫氱敤浠诲姟绠$悊鏁版嵁瀛楀吀
+-- ----------------------------
+
+-- 1. 浠诲姟绫诲瀷瀛楀吀
+-- ----------------------------
+INSERT INTO sys_dict_type VALUES ('sys_task_type', '浠诲姟绫诲瀷', '0', 'admin', sysdate(), '', null, '浠诲姟绫诲瀷鍒楄〃');
+INSERT INTO sys_dict_data VALUES (1, 1, '缁翠慨淇濆吇', 'MAINTENANCE', 'sys_task_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '缁翠慨淇濆吇浠诲姟');
+INSERT INTO sys_dict_data VALUES (2, 2, '鍔犳补浠诲姟', 'FUEL', 'sys_task_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '鍔犳补浠诲姟');
+INSERT INTO sys_dict_data VALUES (3, 3, '鍏朵粬', 'OTHER', 'sys_task_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '鍏朵粬绫诲瀷浠诲姟');
+
+-- 2. 浠诲姟鐘舵�佸瓧鍏�
+-- ----------------------------
+INSERT INTO sys_dict_type VALUES ('sys_task_status', '浠诲姟鐘舵��', '0', 'admin', sysdate(), '', null, '浠诲姟鐘舵�佸垪琛�');
+INSERT INTO sys_dict_data VALUES (4, 1, '寰呭紑濮�', 'PENDING', 'sys_task_status', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸插垱寤猴紝绛夊緟寮�濮�');
+INSERT INTO sys_dict_data VALUES (5, 2, '浠诲姟涓�', 'IN_PROGRESS', 'sys_task_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸茬粡寮�濮�');
+INSERT INTO sys_dict_data VALUES (6, 3, '宸插畬鎴�', 'COMPLETED', 'sys_task_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸插畬鎴�');
+INSERT INTO sys_dict_data VALUES (7, 4, '宸插彇娑�', 'CANCELLED', 'sys_task_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '浠诲姟宸插彇娑�');
+
+-- 3. 杞﹁締绫诲瀷瀛楀吀
+-- ----------------------------
+INSERT INTO sys_dict_type VALUES ('sys_vehicle_type', '杞﹁締绫诲瀷', '0', 'admin', sysdate(), '', null, '杞﹁締绫诲瀷鍒楄〃');
+INSERT INTO sys_dict_data VALUES (8, 1, '鏁戞姢杞�', 'AMBULANCE', 'sys_vehicle_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '鏁戞姢杞�');
+INSERT INTO sys_dict_data VALUES (9, 2, '杞繍杞�', 'TRANSFER', 'sys_vehicle_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '杞繍杞�');
+INSERT INTO sys_dict_data VALUES (10, 3, '缁翠慨杞�', 'MAINTENANCE', 'sys_vehicle_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '缁翠慨杞�');
+
+-- 4. 杞﹁締鐘舵�佸瓧鍏�
+-- ----------------------------
+INSERT INTO sys_dict_type VALUES ('sys_vehicle_status', '杞﹁締鐘舵��', '0', 'admin', sysdate(), '', null, '杞﹁締鐘舵�佸垪琛�');
+INSERT INTO sys_dict_data VALUES (11, 1, '姝e父', '0', 'sys_vehicle_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締姝e父浣跨敤');
+INSERT INTO sys_dict_data VALUES (12, 2, '鍋滅敤', '1', 'sys_vehicle_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締鍋滅敤');
+
+-- 5. 浠诲姟杞﹁締鍏宠仈鐘舵�佸瓧鍏�
+-- ----------------------------
+INSERT INTO sys_dict_type VALUES ('sys_task_vehicle_status', '浠诲姟杞﹁締鍏宠仈鐘舵��', '0', 'admin', sysdate(), '', null, '浠诲姟杞﹁締鍏宠仈鐘舵�佸垪琛�');
+INSERT INTO sys_dict_data VALUES (15, 1, '宸插垎閰�', 'ASSIGNED', 'sys_task_vehicle_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締宸插垎閰嶇粰浠诲姟');
+INSERT INTO sys_dict_data VALUES (16, 2, '鎵ц涓�', 'ACTIVE', 'sys_task_vehicle_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締姝e湪鎵ц浠诲姟');
+INSERT INTO sys_dict_data VALUES (17, 3, '宸插畬鎴�', 'COMPLETED', 'sys_task_vehicle_status', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締浠诲姟宸插畬鎴�');
+INSERT INTO sys_dict_data VALUES (18, 4, '宸插彇娑�', 'CANCELLED', 'sys_task_vehicle_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '杞﹁締浠诲姟宸插彇娑�');
diff --git a/sql/task_menu.sql b/sql/task_menu.sql
new file mode 100644
index 0000000..2c254b9
--- /dev/null
+++ b/sql/task_menu.sql
@@ -0,0 +1,27 @@
+-- ----------------------------
+-- 閫氱敤浠诲姟绠$悊鑿滃崟鏉冮檺閰嶇疆
+-- ----------------------------
+
+-- 浠诲姟绠$悊鑿滃崟
+INSERT INTO sys_menu VALUES (2000, '浠诲姟绠$悊', 0, 5, 'task', null, '', 1, 0, 'M', '0', '0', '', 'task', 'admin', sysdate(), '', null, '浠诲姟绠$悊鐩綍');
+
+-- 閫氱敤浠诲姟鑿滃崟
+INSERT INTO sys_menu VALUES (2001, '閫氱敤浠诲姟', 2000, 1, 'general', 'task/general/index', '', 1, 0, 'C', '0', '0', 'task:general:view', 'list', 'admin', sysdate(), '', null, '閫氱敤浠诲姟鑿滃崟');
+
+-- 浠诲姟绠$悊鎸夐挳鏉冮檺
+INSERT INTO sys_menu VALUES (2002, '浠诲姟鏌ヨ', 2001, 1, '', '', '', 1, 0, 'F', '0', '0', 'task:general:query', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2003, '浠诲姟鏂板', 2001, 2, '', '', '', 1, 0, 'F', '0', '0', 'task:general:add', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2004, '浠诲姟淇敼', 2001, 3, '', '', '', 1, 0, 'F', '0', '0', 'task:general:edit', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2005, '浠诲姟鍒犻櫎', 2001, 4, '', '', '', 1, 0, 'F', '0', '0', 'task:general:remove', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2006, '浠诲姟鍒嗛厤', 2001, 5, '', '', '', 1, 0, 'F', '0', '0', 'task:general:assign', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2007, '鐘舵�佸彉鏇�', 2001, 6, '', '', '', 1, 0, 'F', '0', '0', 'task:general:status', '#', 'admin', sysdate(), '', null, '');
+
+-- 杞﹁締绠$悊鑿滃崟
+INSERT INTO sys_menu VALUES (2008, '杞﹁締绠$悊', 2000, 2, 'vehicle', 'task/vehicle/index', '', 1, 0, 'C', '0', '0', 'task:vehicle:view', 'car', 'admin', sysdate(), '', null, '杞﹁締绠$悊鑿滃崟');
+
+-- 杞﹁締绠$悊鎸夐挳鏉冮檺
+INSERT INTO sys_menu VALUES (2009, '杞﹁締鏌ヨ', 2008, 1, '', '', '', 1, 0, 'F', '0', '0', 'task:vehicle:query', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2010, '杞﹁締鏂板', 2008, 2, '', '', '', 1, 0, 'F', '0', '0', 'task:vehicle:add', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2011, '杞﹁締淇敼', 2008, 3, '', '', '', 1, 0, 'F', '0', '0', 'task:vehicle:edit', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2012, '杞﹁締鍒犻櫎', 2008, 4, '', '', '', 1, 0, 'F', '0', '0', 'task:vehicle:remove', '#', 'admin', sysdate(), '', null, '');
+INSERT INTO sys_menu VALUES (2013, '杞﹁締鍒嗛厤', 2008, 5, '', '', '', 1, 0, 'F', '0', '0', 'task:vehicle:assign', '#', 'admin', sysdate(), '', null, '');
diff --git a/sql/task_tables.sql b/sql/task_tables.sql
new file mode 100644
index 0000000..4983473
--- /dev/null
+++ b/sql/task_tables.sql
@@ -0,0 +1,113 @@
+-- ----------------------------
+-- 閫氱敤浠诲姟绠$悊鐩稿叧琛ㄧ粨鏋�
+-- ----------------------------
+
+-- 1. 浠诲姟涓昏〃
+-- ----------------------------
+DROP TABLE IF EXISTS sys_task;
+CREATE TABLE sys_task (
+ task_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '浠诲姟ID',
+ task_code VARCHAR(50) NOT NULL UNIQUE COMMENT '浠诲姟缂栧彿',
+ task_type VARCHAR(20) NOT NULL COMMENT '浠诲姟绫诲瀷锛歁AINTENANCE-缁翠慨淇濆吇锛孎UEL-鍔犳补浠诲姟锛孫THER-鍏朵粬',
+ task_status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '浠诲姟鐘舵�侊細PENDING-寰呭紑濮嬶紝IN_PROGRESS-浠诲姟涓紝COMPLETED-宸插畬鎴愶紝CANCELLED-宸插彇娑�',
+ task_description VARCHAR(1000) COMMENT '浠诲姟鎻忚堪',
+
+ -- 鍦板潃淇℃伅
+ departure_address VARCHAR(500) COMMENT '鍑哄彂鍦板潃',
+ destination_address VARCHAR(500) COMMENT '鐩殑鍦板潃',
+
+ -- 鏃堕棿淇℃伅
+ planned_start_time DATETIME COMMENT '璁″垝寮�濮嬫椂闂�',
+ planned_end_time DATETIME COMMENT '璁″垝缁撴潫鏃堕棿',
+ actual_start_time DATETIME COMMENT '瀹為檯寮�濮嬫椂闂�',
+ actual_end_time DATETIME COMMENT '瀹為檯缁撴潫鏃堕棿',
+
+ -- 浜哄憳淇℃伅
+ creator_id BIGINT NOT NULL COMMENT '鍒涘缓浜篒D',
+ assignee_id BIGINT COMMENT '鎵ц浜篒D',
+ dept_id BIGINT NOT NULL COMMENT '褰掑睘閮ㄩ棬ID',
+
+ -- 绯荤粺瀛楁
+ create_time DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ update_time DATETIME NOT NULL COMMENT '鏇存柊鏃堕棿',
+ create_by VARCHAR(64) NOT NULL COMMENT '鍒涘缓鑰�',
+ update_by VARCHAR(64) COMMENT '鏇存柊鑰�',
+ remark VARCHAR(500) COMMENT '澶囨敞',
+ del_flag CHAR(1) DEFAULT '0' COMMENT '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+
+ INDEX idx_task_code (task_code),
+ INDEX idx_task_type (task_type),
+ INDEX idx_task_status (task_status),
+ INDEX idx_creator_id (creator_id),
+ INDEX idx_assignee_id (assignee_id),
+ INDEX idx_dept_id (dept_id),
+ INDEX idx_planned_start_time (planned_start_time),
+ INDEX idx_create_time (create_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟绠$悊琛�';
+
+-- 2. 浠诲姟杞﹁締鍏宠仈琛�
+-- ----------------------------
+DROP TABLE IF EXISTS sys_task_vehicle;
+CREATE TABLE sys_task_vehicle (
+ id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '鍏宠仈ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ vehicle_id BIGINT NOT NULL COMMENT '杞﹁締ID',
+ assign_time DATETIME NOT NULL COMMENT '鍒嗛厤鏃堕棿',
+ assign_by VARCHAR(64) NOT NULL COMMENT '鍒嗛厤浜�',
+ status VARCHAR(20) DEFAULT 'ASSIGNED' COMMENT '鍏宠仈鐘舵�侊細ASSIGNED-宸插垎閰嶏紝ACTIVE-鎵ц涓紝COMPLETED-宸插畬鎴愶紝CANCELLED-宸插彇娑�',
+ remark VARCHAR(500) COMMENT '澶囨敞',
+
+ INDEX idx_task_id (task_id),
+ INDEX idx_vehicle_id (vehicle_id),
+ INDEX idx_status (status),
+ INDEX idx_assign_time (assign_time),
+
+ UNIQUE KEY uk_task_vehicle (task_id, vehicle_id),
+ FOREIGN KEY (task_id) REFERENCES sys_task(task_id) ON DELETE CASCADE,
+ FOREIGN KEY (vehicle_id) REFERENCES tb_vehicle_info(vehicle_id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟杞﹁締鍏宠仈琛�';
+
+-- 3. 浠诲姟闄勪欢琛�
+-- ----------------------------
+DROP TABLE IF EXISTS sys_task_attachment;
+CREATE TABLE sys_task_attachment (
+ attachment_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '闄勪欢ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ file_name VARCHAR(255) NOT NULL COMMENT '鏂囦欢鍚�',
+ file_path VARCHAR(500) NOT NULL COMMENT '鏂囦欢璺緞',
+ file_size BIGINT COMMENT '鏂囦欢澶у皬锛堝瓧鑺傦級',
+ file_type VARCHAR(50) COMMENT '鏂囦欢绫诲瀷',
+ upload_time DATETIME NOT NULL COMMENT '涓婁紶鏃堕棿',
+ upload_by VARCHAR(64) NOT NULL COMMENT '涓婁紶鑰�',
+
+ INDEX idx_task_id (task_id),
+ FOREIGN KEY (task_id) REFERENCES sys_task(task_id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟闄勪欢琛�';
+
+-- 4. 浠诲姟鎿嶄綔鏃ュ織琛�
+-- ----------------------------
+DROP TABLE IF EXISTS sys_task_log;
+CREATE TABLE sys_task_log (
+ log_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '鏃ュ織ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ operation_type VARCHAR(20) NOT NULL COMMENT '鎿嶄綔绫诲瀷锛欳REATE-鍒涘缓锛孶PDATE-鏇存柊锛孉SSIGN-鍒嗛厤锛孲TATUS_CHANGE-鐘舵�佸彉鏇达紝DELETE-鍒犻櫎',
+ operation_desc VARCHAR(500) COMMENT '鎿嶄綔鎻忚堪',
+ old_value TEXT COMMENT '鎿嶄綔鍓嶅��',
+ new_value TEXT COMMENT '鎿嶄綔鍚庡��',
+ operator_id BIGINT NOT NULL COMMENT '鎿嶄綔浜篒D',
+ operator_name VARCHAR(64) NOT NULL COMMENT '鎿嶄綔浜哄鍚�',
+ operation_time DATETIME NOT NULL COMMENT '鎿嶄綔鏃堕棿',
+ ip_address VARCHAR(128) COMMENT 'IP鍦板潃',
+
+ INDEX idx_task_id (task_id),
+ INDEX idx_operation_type (operation_type),
+ INDEX idx_operator_id (operator_id),
+ INDEX idx_operation_time (operation_time),
+ FOREIGN KEY (task_id) REFERENCES sys_task(task_id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟鎿嶄綔鏃ュ織琛�';
+
+-- 5. 鏇存柊杞﹁締淇℃伅琛紝娣诲姞鏈烘瀯鍏宠仈瀛楁
+-- ----------------------------
+ALTER TABLE tb_vehicle_info ADD COLUMN dept_id BIGINT(20) DEFAULT NULL COMMENT '褰掑睘鏈烘瀯ID';
+ALTER TABLE tb_vehicle_info ADD INDEX idx_dept_id (dept_id);
+ALTER TABLE tb_vehicle_info ADD FOREIGN KEY (dept_id) REFERENCES sys_dept(dept_id) ON DELETE SET NULL;
diff --git a/sql/vehicle_info.sql b/sql/vehicle_info.sql
index 6434538..cf24d47 100644
--- a/sql/vehicle_info.sql
+++ b/sql/vehicle_info.sql
@@ -19,5 +19,10 @@
-- 鍦╰b_vehicle_info琛ㄤ腑娣诲姞device_id瀛楁
ALTER TABLE tb_vehicle_info ADD COLUMN device_id VARCHAR(50) DEFAULT NULL COMMENT '璁惧ID';
+-- 鍦╰b_vehicle_info琛ㄤ腑娣诲姞褰掑睘鏈烘瀯瀛楁
+ALTER TABLE tb_vehicle_info ADD COLUMN dept_id BIGINT(20) DEFAULT NULL COMMENT '褰掑睘鏈烘瀯ID';
+ALTER TABLE tb_vehicle_info ADD INDEX idx_dept_id (dept_id);
+ALTER TABLE tb_vehicle_info ADD CONSTRAINT fk_vehicle_dept FOREIGN KEY (dept_id) REFERENCES sys_dept(dept_id) ON DELETE SET NULL;
+
-- 鍦╰b_vehicle_gps琛ㄤ腑娣诲姞device_id瀛楁
ALTER TABLE tb_vehicle_gps ADD COLUMN device_id VARCHAR(50) DEFAULT NULL COMMENT '璁惧ID';
\ No newline at end of file
--
Gitblit v1.9.1