From 5d34bdd3c426bb2ca5f6627424ae67984f745603 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期三, 21 十月 2026 18:28:59 +0800
Subject: [PATCH] feat: 新增资产/用车/采购模块,完善课题、会员、财务与企微功能

---
 docs/project-business/data/roles.json                                                                                                                      |    4 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/requisition/AssetRequisitionDO.java                                          |   79 
 yudao-server/pom.xml                                                                                                                                       |   21 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/ResearchApplicationController.java                       |   33 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityService.java                                                     |   42 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/mysql/application/ResearchApplicationMemberMapper.java                            |   20 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/VehicleNoRedisDAO.java                                                        |   32 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationRespVO.java                        |    6 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/mysql/member/UmsMemberProfileChangeMapper.java                                              |   38 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/UmsMemberProfileChangeStatusEnum.java                                                     |   29 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementServiceImpl.java                              |   57 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberSaveReqVO.java        |   41 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionSaveReqVO.java                              |   50 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/notice/WxCpNoticeServiceImpl.java                                                     |   98 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/RedisKeyConstants.java                                                            |   16 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/RedisKeyConstants.java                                                      |   15 
 docs/project-business/data/flows.json                                                                                                                      |   43 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java                                                  |    3 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java                               |  125 +
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/AssetRequisitionController.java                                |  102 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeSaveReqVO.java                         |   59 
 docs/wxcp_activity_integration.md                                                                                                                          |    8 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberProfileChangeDO.java                                             |   91 
 docs/project-business/mermaid/14-research-flow.mmd                                                                                                         |    4 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderItemDO.java                                         |   48 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemRespVO.java              |    3 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldRespVO.java                                     |   31 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivitySignupValueMapper.java                                         |   35 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/mysql/application/VehicleApplicationMapper.java                                     |   27 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemRespVO.java                                |   36 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/FinanceReimbursementAiController.java                 |   39 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemSaveReqVO.java                             |   41 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldSaveReqVO.java                                  |   39 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderRespVO.java                                    |   45 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationService.java                                      |   35 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivityFieldMapper.java                                               |   30 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionReturnReqVO.java                            |   29 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderLineMapper.java                                          |   23 
 docs/project-business/data/modules.json                                                                                                                    |   30 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/PurchaseNoRedisDAO.java                                                     |   32 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/dal/dataobject/reimbursement/FinanceReimbursementItemDO.java                            |    3 
 pom.xml                                                                                                                                                    |    3 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemPageReqVO.java                                            |   25 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/PurchaseOrderController.java                                   |   84 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityFieldDO.java                                              |   74 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemService.java                                                          |   36 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberDO.java                                                          |    6 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/listener/VehicleApplicationStatusListener.java                      |   47 
 yudao-module-vehicle/pom.xml                                                                                                                               |   68 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupPublicReqVO.java                                       |    6 
 yudao-server/src/main/resources/application-local.yaml                                                                                                     |   20 
 yudao-module-purchase/pom.xml                                                                                                                              |   61 
 yudao-module-asset/pom.xml                                                                                                                                 |   61 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionRespVO.java                                 |   79 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetReturnStatusEnum.java                                                            |   29 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java                                                               |   25 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueReqVO.java                                        |   20 
 docs/project-business/mermaid/12-finance-flow.mmd                                                                                                          |    8 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationSaveReqVO.java                        |   54 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/application/ResearchApplicationMemberDO.java                           |   48 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/VehicleApplicationStatusEnum.java                                                 |   30 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationService.java                                   |    9 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/VehicleApplicationController.java                          |  104 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiServiceImpl.java                         |  297 ++
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeRespVO.java                            |   78 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeRespVO.java                                              |   24 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityController.java                                         |   16 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupValueDO.java                                        |   58 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/ErrorCodeConstants.java                                                                   |    9 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetCategoryEnum.java                                                                |   29 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderSaveReqVO.java                                 |   42 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/dataobject/application/VehicleApplicationDO.java                                    |   69 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/requisition/AssetRequisitionMapper.java                                           |   29 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrReqVO.java                  |   20 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeService.java                                               |   32 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderService.java                                               |   28 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/ErrorCodeConstants.java                                                           |   20 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemServiceImpl.java                                                      |  154 +
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderPageReqVO.java                                 |   26 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/RedisKeyConstants.java                                                        |   15 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupRespVO.java                                            |   10 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueRespVO.java                                       |   22 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/activity/WxCpActivityTypeEnum.java                                                      |   52 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationRespVO.java                           |   63 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApiImpl.java                                      |    7 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/notice/WxCpNoticeTypeEnum.java                                                          |   38 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderServiceImpl.java                                           |  152 +
 yudao-module-finance/pom.xml                                                                                                                               |   14 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/enums/ErrorCodeConstants.java                                                         |   19 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionPageReqVO.java                              |   28 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsRespVO.java |   39 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberServiceImpl.java                                                        |   29 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/FinanceReimbursementSaveReqVO.java                    |    4 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationServiceImpl.java                                  |  218 ++
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangePageReqVO.java                         |   22 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeServiceImpl.java                                           |  167 +
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/item/AssetItemMapper.java                                                         |   36 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderMainMapper.java                                          |   25 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemSaveReqVO.java           |    5 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberSaveReqVO.java                                              |   10 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsReqVO.java  |   19 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiService.java                             |   23 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionService.java                                            |   27 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java                                                           |    1 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/AssetItemController.java                                              |   93 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionServiceImpl.java                                        |  141 +
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupDO.java                                             |    4 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushServiceImpl.java                                                  |   73 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberRespVO.java                                                 |    6 
 docs/project-business/mermaid/13-ums-membership-flow.mmd                                                                                                   |    7 
 yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrRespVO.java                 |   46 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeSaveReqVO.java                                           |   32 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemSaveReqVO.java                                            |   66 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivitySaveReqVO.java                                       |   37 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/AssetNoRedisDAO.java                                                              |   35 
 docs/sql/wxcp_module.sql                                                                                                                                   |   67 
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemRespVO.java                                               |   62 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityPublicController.java                                   |  258 ++
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/WxCpNoticeController.java                                             |    3 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/member/WxCpMemberServiceImpl.java                                                     |   18 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityServiceImpl.java                                                 |  433 ++++
 yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/item/AssetItemDO.java                                                        |   67 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityDO.java                                                   |   22 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationSaveReqVO.java                     |   15 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/notice/WxCpNoticeDO.java                                                       |   31 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/vo/WxCpPushReqVO.java                                                        |   27 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/storage/StorageFileUtil.java                                                         |    6 
 docs/project-business/data/objects.json                                                                                                                    |   15 
 yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationPageReqVO.java                        |   32 
 yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberRespVO.java           |   34 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/ErrorCodeConstants.java                                                                 |   19 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushService.java                                                      |   37 
 yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderDO.java                                             |   49 
 yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/UmsMemberProfileChangeController.java                                   |   80 
 yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityRespVO.java                                          |   30 
 135 files changed, 6,179 insertions(+), 81 deletions(-)

diff --git a/docs/project-business/data/flows.json b/docs/project-business/data/flows.json
index f3ce9c9..9cd2498 100644
--- a/docs/project-business/data/flows.json
+++ b/docs/project-business/data/flows.json
@@ -54,13 +54,14 @@
        {"stage": "缁翠慨", "action": "鐜板満/杩斿巶/杩滅▼缁翠慨锛岃垂鐢ㄤ笌缁撴灉璁板綍锛屽畬鎴愯仈鍔ㄥ伐鍗曞畬缁�", "roles": ["缁翠慨宸ョ▼甯�"]},
        {"stage": "閫�璐�", "action": "閫�璐х敵璇峰鏍糕啋鑷姩寤� MES 閿�鍞��璐у崟(RQC)鈫掕川妫�/鍏ュ簱鍥炶皟鈫掗��娆惧洖璋冣啋鍏抽棴", "roles": ["鍞悗", "璐ㄦ", "璐㈠姟"]}]},
     {"id": "finance", "name": "璐㈠姟鎶ラ攢娴佺▼", "mermaid": "mermaid/12-finance-flow.mmd", "color": "#3ec9a7",
-     "summary": "鐙珛璐㈠姟妯″潡锛坹udao-module-finance锛夛細鎶ラ攢鍗�(涓昏〃 + 1..n 鏄庣粏琛� + 鍙戠エ闄勪欢) 涓�娆℃�т繚瀛� 鈫� 鎻愪氦 BPM 瀹℃壒 鈫� 閫氳繃鍚庡綊妗g粓鎬侊紱椹冲洖鍙敼鍙戠エ閲嶆柊鎻愪氦銆傞檮浠惰蛋 system 闄勪欢涓績鎸� recordType=finance_reimbursement 缁戝畾锛屾ā鍧椾笉涓庡叾浠栦笟鍔℃ā鍧椾骇鐢熶换浣曞崟鎹叧鑱旀垨瀛楁甯﹀叆銆�",
+     "summary": "鐙珛璐㈠姟妯″潡锛坹udao-module-finance锛夛細鎶ラ攢鍗�(涓昏〃 + 1..n 鏄庣粏琛� + 鍙戠エ闄勪欢) 涓�娆℃�т繚瀛� 鈫� 鎻愪氦 BPM 瀹℃壒 鈫� 閫氳繃鍚庡綊妗g粓鎬侊紱椹冲洖鍙敼鍙戠エ閲嶆柊鎻愪氦銆傛敮鎸佸彂绁� OCR 鑷姩鍥炲~銆佸彂绁ㄧ被鍨嬫灇涓俱�佹湰浜�/浠d粬浜哄彂璧凤紙瀹為檯鐢宠浜� + 浠e綍浜猴級銆傞檮浠惰蛋 system 闄勪欢涓績鎸� recordType=finance_reimbursement 缁戝畾锛屾ā鍧椾笉涓庡叾浠栦笟鍔℃ā鍧椾骇鐢熶换浣曞崟鎹叧鑱旀垨瀛楁甯﹀叆銆�",
      "steps": [
-       {"stage": "濉姤", "action": "涓昏〃(鎶ラ攢鏍囬/鎶ラ攢鎬婚噾棰�/鐢宠鏃ユ湡/澶囨敞) + 鑷冲皯 1 琛屾槑缁�(璐圭敤绫诲瀷/鍙戠エ鍙风爜/鍙戠エ閲戦/鍙戠エ鏃ユ湡/寮�绁ㄦ柟/璐圭敤璇存槑)锛涙�婚噾棰濈敱鏄庣粏姹傚拰鍥炲~锛岀敤鎴峰彲鎵嬫敼锛屽悗绔笉鏍¢獙涓�鑷存��", "roles": ["鎶ラ攢浜�"]},
-       {"stage": "闄勪欢", "action": "鍙戠エ鍏堣皟 /system/storage-blob/upload 鍙� blobId锛涗繚瀛樻椂鎶婅鍗曞綋鍓嶅叏閮� blobId 鏀捐繘 blobIds 涓�璧锋彁浜わ紙鍏ㄩ噺鏇挎崲璇箟锛屼紶绌烘暟缁�=娓呯┖锛屼笉浼�=涓嶅姩锛�", "roles": ["鎶ラ攢浜�"]},
-       {"stage": "淇濆瓨", "action": "create 鐢� Redis 鐢熸垚鍗曞彿 BX+yyyyMMdd+6浣嶆祦姘�(姣忔棩閲嶇疆)锛涙槑缁嗗厛鍒犲悗澧炲叏閲忚鐩栦笖鎻愪氦鍓ユ帀琛� id锛泂tatus=0 鑽夌", "roles": ["鎶ラ攢浜�"]},
-       {"stage": "淇敼", "action": "update 浠呮斁琛� status=0 鑽夌 涓� status=35 宸查┏鍥烇紱delete 浠呮斁琛岃崏绋匡紙杩炲甫娓呯悊鏄庣粏涓庡彂绁ㄩ檮浠讹級", "roles": ["鎶ラ攢浜�"]},
-       {"stage": "瀹℃壒", "action": "submit 浼� id 涓� processDefinitionKey锛坬uery 鍙傛暟锛岃姹備綋涓虹┖锛夆啋 status=15 瀹℃壒涓� 骞跺洖鍐� processInstanceId锛汢PM 鐩戝惉鍣ㄦ寜缁撴灉鍥炲啓锛氶�氳繃鈫�20 宸查�氳繃銆侀┏鍥炩啋35 宸查┏鍥炪�佸彇娑堚啋閫�鍥� 0 鑽夌锛涙祦绋� key 鍙� approve-process-list 瀹炴椂鍒楄〃涓嶅啓姝�", "roles": ["鎶ラ攢浜�", "瀹℃壒浜�"]},
+       {"stage": "濉姤", "action": "涓昏〃(鐢宠浜�/鎶ラ攢鏍囬/鎶ラ攢鎬婚噾棰�/鐢宠鏃ユ湡/澶囨敞) + 鑷冲皯 1 琛屾槑缁�(璐圭敤绫诲瀷/鍙戠エ绫诲瀷/鍙戠エ鍙风爜/鍙戠エ閲戦/鍙戠エ鏃ユ湡/寮�绁ㄦ柟/璐圭敤璇存槑)锛涚敵璇蜂汉鏈汉鍙戣捣鏃跺彧璇诲彇鐧诲綍鐢ㄦ埛锛屼唬浠栦汉鍙戣捣鏃堕�夋嫨瀹為檯鐢宠浜猴紱鍙戠エ绫诲瀷涓嬫媺锛氬鍊肩◣涓撶敤鍙戠エ/澧炲�肩◣鏅�氬彂绁�/鐢靛瓙鍙戠エ/鏀舵嵁/鍏朵粬锛涙�婚噾棰濈敱鏄庣粏姹傚拰鍥炲~锛岀敤鎴峰彲鎵嬫敼锛屽悗绔笉鏍¢獙涓�鑷存��", "roles": ["鎶ラ攢浜�", "浠e綍浜�"]},
+       {"stage": "OCR 璇嗗埆", "action": "涓婁紶鍙戠エ鍥剧墖/PDF 鍚庤皟鐢� /finance/reimbursement/ai/ocr 鎵归噺璇嗗埆锛堝崟娆♀墹20 涓級锛孉I 澶氭ā鎬佹彁鍙栧彂绁ㄥ彿鐮�/绫诲瀷/閲戦/鏃ユ湡/閿�鍞柟骞惰拷鍔犱负鏄庣粏琛岋紱璇嗗埆缁撴灉浠呴濉紝闇�浜哄伐鏍稿锛岃垂鐢ㄧ被鍨嬩粛闇�鎵嬮��", "roles": ["鎶ラ攢浜�", "浠e綍浜�"]},
+       {"stage": "闄勪欢", "action": "鍙戠エ鍏堣皟 /system/storage-blob/upload 鍙� blobId锛涗繚瀛樻椂鎶婅鍗曞綋鍓嶅叏閮� blobId 鏀捐繘 blobIds 涓�璧锋彁浜わ紙鍏ㄩ噺鏇挎崲璇箟锛屼紶绌烘暟缁�=娓呯┖锛屼笉浼�=涓嶅姩锛夛紱OCR 涓婁紶鐨勬枃浠惰嚜鍔ㄥ苟鍏ュ彂绁ㄩ檮浠�", "roles": ["鎶ラ攢浜�", "浠e綍浜�"]},
+       {"stage": "淇濆瓨", "action": "create 鐢� Redis 鐢熸垚鍗曞彿 BX+yyyyMMdd+6浣嶆祦姘�(姣忔棩閲嶇疆)锛涚敵璇蜂汉 applicantId 涓嶄紶榛樿鐧诲綍鐢ㄦ埛锛屼唬鍙戣捣鏃舵牎楠岀敤鎴峰瓨鍦ㄤ笖鍚敤锛涘疄闄呯敵璇蜂汉瀛� applicant_id锛屼唬褰曚汉鍙� creator 瀹¤瀛楁锛涙槑缁嗗厛鍒犲悗澧炲叏閲忚鐩栦笖鎻愪氦鍓ユ帀琛� id锛泂tatus=0 鑽夌", "roles": ["鎶ラ攢浜�", "浠e綍浜�"]},
+       {"stage": "淇敼", "action": "update 浠呮斁琛� status=0 鑽夌 涓� status=35 宸查┏鍥烇紱delete 浠呮斁琛岃崏绋匡紙杩炲甫娓呯悊鏄庣粏涓庡彂绁ㄩ檮浠讹級", "roles": ["鎶ラ攢浜�", "浠e綍浜�"]},
+       {"stage": "瀹℃壒", "action": "submit 浼� id 涓� processDefinitionKey锛坬uery 鍙傛暟锛岃姹備綋涓虹┖锛夆啋 status=15 瀹℃壒涓� 骞跺洖鍐� processInstanceId锛涘垱寤烘祦绋嬪疄渚嬫椂鍐欏叆鍙橀噺 applicantId/applicantName/initiatorId/initiatorName/isProxy锛屽鎵规祦鍙睍绀哄疄闄呯敵璇蜂汉涓庝唬褰曚汉锛汢PM 鐩戝惉鍣ㄦ寜缁撴灉鍥炲啓锛氶�氳繃鈫�20 宸查�氳繃銆侀┏鍥炩啋35 宸查┏鍥炪�佸彇娑堚啋閫�鍥� 0 鑽夌锛涙祦绋� key 鍙� approve-process-list 瀹炴椂鍒楄〃涓嶅啓姝�", "roles": ["鎶ラ攢浜�", "浠e綍浜�", "瀹℃壒浜�"]},
        {"stage": "褰掓。", "action": "archive 浠呮斁琛� status=20 宸查�氳繃 鈫� status=40 宸插綊妗o紙缁堟�侊級骞跺啓鍏� archiveTime", "roles": ["璐㈠姟"]}]},
     {"id": "ums", "name": "浼氬憳鍏ヤ細娴佺▼", "mermaid": "mermaid/13-ums-membership-flow.mmd", "color": "#d6955c",
      "summary": "鐙珛浼氬憳妯″潡锛坹udao-module-ums锛夛細鍏ヤ細鐢宠鍗�(涓汉/鍗曚綅浜屽瀷 + 鐢宠绛夌骇 + 璇佹槑鏉愭枡闄勪欢) 鑽夌 鈫� 鎻愪氦 BPM 涓ょ骇瀹℃壒锛堢涔﹀鍒濆 鈫� 鐞嗕簨浼氬璁級鈫� 閫氳繃鍚庤嚜鍔ㄥ垱寤虹郴缁熺櫥褰曡处鍙蜂笌浼氬憳璁板綍 鈫� 浼氬憳杩涘叆鍒楄〃涓庨�氳褰曪紙鎸夊湴鍖�/琛屼笟妫�绱級銆傞┏鍥炲彲鏀瑰悗閲嶆彁锛屽彇娑堥��鍥炶崏绋裤�傛ā鍧椾笉涓庡叾浠栦笟鍔℃ā鍧椾骇鐢熷崟鎹叧鑱旓紝浠呭鐢� BPM 涓� system 闄勪欢涓績銆�",
@@ -72,17 +73,41 @@
        {"stage": "淇敼", "action": "update 浠呮斁琛� status=0 鑽夌 涓� status=35 宸查┏鍥烇紙椹冲洖鏃舵彁绀哄甫鐢宠缂栧彿锛夛紱delete 浠呮斁琛岃崏绋匡紝涓旇繛甯︽竻鐞嗚鍗曢檮浠�", "roles": ["鍏ヤ細鐢宠浜�", "浼氬憳绠$悊鍛�"]},
        {"stage": "瀹℃壒", "action": "submit 浼� id 涓� processDefinitionKey锛坬uery 鍙傛暟锛夆啋 status=15 瀹℃壒涓苟鍥炲啓 processInstanceId锛汢PM 鐩戝惉鍣� UmsApplicationStatusListener 鎸夌粨鏋滃洖鍐欙細閫氳繃鈫�20銆侀┏鍥炩啋35銆佸彇娑堚啋閫�鍥� 0锛涙祦绋嬪垪琛ㄧ敱 approve-process-list 瀹炴椂鍙栵紙鍏堟牎楠� BPM 鍒嗙被 ums_membership_approve 瀛樺湪锛屽啀鎸� key 鍙栨湭鎸傝捣鐨勬渶楂樼増鏈級锛屼笉鍐欐 key", "roles": ["鍏ヤ細鐢宠浜�", "绉樹功澶勶紙鍒濆锛�", "鐞嗕簨浼氾紙瀹¤锛�"]},
        {"stage": "寤烘。", "action": "瀹℃壒閫氳繃閽╁瓙鑷姩寤轰細鍛橈細鈶犵敓鎴愪細鍛樼紪鍙� HY+yyyyMMdd+6浣嶆祦姘� 鈶dminUserApi.createUser 寤虹郴缁熻处鍙凤紙鐢ㄦ埛鍚嶄笌鍒濆瀵嗙爜=鑱旂郴鐢佃瘽銆佹樀绉�=鐢宠浜哄鍚嶃�侀儴闂�=瀛︿細浼氬憳锛夆憿浼氬憳钀藉簱锛堝叆浼氭棩鏈�=褰撳ぉ銆佺姸鎬� 1 姝e父銆乤pplicationId 鍥炲~鏉ユ簮鐢宠鍗曪級锛涙墜鏈哄彿閲嶅浼氭槑纭姤閿�", "roles": ["绯荤粺鑷姩"]},
-       {"stage": "绠$悊涓庢绱�", "action": "浼氬憳鍒楄〃鏀寔鎵嬪伐鏂板/缂栬緫/鍒犻櫎锛堢瓑绾т笌绫诲瀷鏍¢獙锛氬崟浣嶄細鍛樹笉鍙�夈�庣悊浜嬮暱銆忥級锛涗細鍛橀�氳褰曚负鍙椤碉紝鎸夊湴鍖虹紪鐮佷笌琛屼笟妫�绱�", "roles": ["浼氬憳绠$悊鍛�", "鍏ㄤ綋鍙煡閫氳褰�"]}]},
+       {"stage": "璧勬枡鑷富缁存姢", "action": "浼氬憳鍦ㄣ�屾垜鐨勮祫鏂欍�嶉〉缁存姢鑱旂郴鏂瑰紡銆佸崟浣�/鑱岀О銆佺爺绌舵柟鍚戙�佸湴鍖恒�佽涓氱瓑璧勬枡锛屾彁浜ゅ悗鐢熸垚 ums_member_profile_change 寰呭鏍哥敵璇凤紝涓嶇洿鎺ヤ慨鏀逛細鍛樿祫鏂欙紱鍚屼竴浼氬憳鍚屾椂鍙厑璁镐竴鏉″緟瀹℃牳鐢宠锛岃祫鏂欐棤鍙樺寲鏃舵嫆缁濇彁浜ゃ�傜鐞嗗憳鍦ㄣ�岃祫鏂欏彉鏇村鏍搞�嶆煡鐪嬪瓧娈电骇鍙樻洿鎽樿锛屽鏍搁�氳繃鍚庡叏閲忓啓鍥� ums_member锛堝厑璁告竻绌哄瓧娈碉級锛岄┏鍥炲繀椤诲~鍐欏師鍥犲苟鍥炴樉缁欎細鍛�", "roles": ["浼氬憳", "浼氬憳绠$悊鍛�"]},
+       {"stage": "绠$悊涓庢绱�", "action": "浼氬憳鍒楄〃鏀寔鎵嬪伐鏂板/缂栬緫/鍒犻櫎锛堢瓑绾т笌绫诲瀷鏍¢獙锛氬崟浣嶄細鍛樹笉鍙�夈�庣悊浜嬮暱銆忥級锛涗細璐圭即绾虫爣璇嗚褰� fee_paid锛堟槸鍚︾即绾筹級涓� fee_year锛堢即璐瑰勾搴︼級锛屾爣璁板凡缂存椂蹇呴』濉啓骞村害锛屽彇娑堢即绾虫椂娓呯┖骞村害锛屽垪琛ㄥ睍绀恒�屽凡缂达紙骞村害锛�/鏈即銆嶏紱浼氬憳閫氳褰曚负鍙椤碉紝鎸夊湴鍖虹紪鐮佷笌琛屼笟妫�绱�", "roles": ["浼氬憳绠$悊鍛�", "鍏ㄤ綋鍙煡閫氳褰�"]}]},
     {"id": "research", "name": "璇鹃绠$悊娴佺▼", "mermaid": "mermaid/14-research-flow.mmd", "color": "#e070a8",
      "summary": "鐙珛绉戠爺妯″潡锛坹udao-module-research锛夛細瀛︿細鍙戝竷璇鹃鎸囧崡锛堥噸鐐�/涓�鑸垎绫伙級鈫� 浼氬憳鍦ㄧ嚎鎻愪氦璇鹃鐢虫姤涔� 鈫� 鎻愪氦 BPM 涓夌骇瀹℃壒锛堢涔﹀褰㈠紡瀹℃煡 鈫� 涓撳璇勫 鈫� 瀛︿細绔嬮」瀹℃壒锛夛紝涓撳璇勫垎浠ョ粨鏋勫寲鏄庣粏瀛愯〃璁板綍骞剁敱鍚庣鑷姩姹囨�诲姞鏉冨潎鍒嗐�佹帓鍚嶄笌鎺ㄨ崘缁撹 鈫� 瀹℃壒閫氳繃鐢熸垚绔嬮」缂栧彿骞跺彂甯冪郴缁熷叕鍛� 鈫� 鎸夋壒娆$櫥璁扮粡璐规嫧浠橈紙鍑瘉蹇呭~锛夛紝姣忔潯鎷ㄦ璁板綍鑷姩鐢熸垚骞舵彁浜や竴寮� 1:1 鐨勮储鍔℃姤閿�鍗曪紙finance_reimbursement锛夛紝璧� BPM 涓ょ骇瀹℃壒鍚庣敱鐩戝惉鍣ㄦ妸缁撴灉鍥炲啓鍙拌处銆傞櫌绾т簩鏈熷啀琛ョ粨椤归獙鏀朵笌鎴愭灉褰掓。銆�",
      "steps": [
        {"stage": "鍙戝竷鎸囧崡", "action": "鏂板缓鎸囧崡閫夌被鍒� 1閲嶇偣璇鹃 / 2涓�鑸棰橈紝濉敵鎶ヨ捣姝㈡椂闂翠笌鎸囧崡姝f枃锛涚紪鍙� GL+yyyyMMdd+6浣嶆祦姘达紙Redis 姣忔棩閲嶇疆锛夛紱status: 0鑽夌/20宸插彂甯�/30宸叉埅姝€�備粎鑽夌鍙敼鍙垹锛屽彂甯冨悗鍗冲喕缁擄紱宸蹭骇鐢熺敵鎶ュ崟鐨勬寚鍗楃姝㈠垹闄わ紙鎻愮ず璇ユ寚鍗椾笅宸叉湁 N 寮犵敵鎶ュ崟锛�", "roles": ["瀛︿細绉樹功澶�", "璇鹃绠$悊鍛�"]},
-       {"stage": "鍦ㄧ嚎鐢虫姤", "action": "浠庡凡鍙戝竷涓斿湪鐢虫姤鏈熷唴鐨勬寚鍗楁柊寤虹敵鎶ヤ功锛涢�変細鍛橈紙ums_member锛夎嚜鍔ㄥ甫鍑哄鍚�/宸ヤ綔鍗曚綅/鑱岀О/鑱旂郴鐢佃瘽/閭锛屽彲鏀癸紱濉棰樺悕绉般�佽棰樼粍鎴愬憳銆佺敵璇风粡璐广�佺敵鎶ヤ功姝f枃锛涚紪鍙� SB+yyyyMMdd+6浣嶆祦姘达紱绫诲埆鍐椾綑鑷寚鍗楋紝涓嶄俊浠诲墠绔紶鍊硷紱status: 0鑽夌", "roles": ["璇鹃鐢虫姤浜猴紙浼氬憳锛�", "璇鹃绠$悊鍛�"]},
+       {"stage": "鍦ㄧ嚎鐢虫姤", "action": "浠庡凡鍙戝竷涓斿湪鐢虫姤鏈熷唴鐨勬寚鍗楁柊寤虹敵鎶ヤ功锛涢�変細鍛橈紙ums_member锛夎嚜鍔ㄥ甫鍑哄鍚�/宸ヤ綔鍗曚綅/鑱岀О/鑱旂郴鐢佃瘽/閭锛屽彲鏀癸紱濉棰樺悕绉般�佽棰樼粍鎴愬憳锛堝瓙琛ㄩ�愯褰曞叆濮撳悕/鐢佃瘽/鍗曚綅/鑱岀О/瑙掕壊/鎺掑簭锛岄殢涓昏〃鍏堝垹鍚庡锛夈�佺敵璇风粡璐广�佺敵鎶ヤ功姝f枃锛岃仈绯荤數璇濅负蹇呭~椤逛笖蹇呴』涓� 11 浣嶆墜鏈哄彿锛堝悗绔悓姝ユ牎楠岋紝闃叉缁曡繃鍓嶇锛夛紱缂栧彿 SB+yyyyMMdd+6浣嶆祦姘达紱绫诲埆鍐椾綑鑷寚鍗楋紝涓嶄俊浠诲墠绔紶鍊硷紱status: 0鑽夌", "roles": ["璇鹃鐢虫姤浜猴紙浼氬憳锛�", "璇鹃绠$悊鍛�"]},
        {"stage": "鎻愪氦瀹℃壒", "action": "submit 浼� id 涓� processDefinitionKey锛坬uery 鍙傛暟锛夆啋 status=15 瀹℃壒涓苟鍥炲啓 processInstanceId銆傛彁浜ゅ墠閫愭潯鏍¢獙锛氣憼 鎸囧崡蹇呴』宸插彂甯� 鈶� 褰撳墠鏃堕棿蹇呴』鍦ㄧ敵鎶ヨ捣姝㈡湡鍐呪�斺�斾换涓�涓嶆弧瓒冲嵆鎷掔粷锛屾彁绀哄甫鎸囧崡鍚嶄笌瀹為檯鐘舵�佹垨璧锋鏃堕棿锛涗粎鑽夌涓庡凡椹冲洖鍙敼鍙噸鎻愶紝浠呰崏绋垮彲鍒狅紙杩炲甫娓呴檮浠讹級", "roles": ["璇鹃鐢虫姤浜�"]},
        {"stage": "涓撳璇勫", "action": "鐢虫姤鍗曞浜庡鎵逛腑(15)鏃跺綍鍏ヤ笓瀹惰瘎鍒嗘槑缁嗭紙涓�浣嶄笓瀹朵竴琛岋細濮撳悕/鍗曚綅/鑱岀О + 閫夐浠峰��0.3/鐮旂┒鏂规0.3/鍥㈤槦鍩虹0.2/棰勬湡鎴愭灉0.2 鍥涢」鍒嗗�� + 缁撹 + 鎰忚锛夛紱鍚庣姣忔澧炲垹鏀瑰悗閲嶇畻 compositeScore 鍔犳潈鍚堣銆佹寜鍔犳潈鍒嗛檷搴忛噸鎺� ranking锛屽苟鍥炲啓涓诲崟 reviewAvgScore锛堝叏閮ㄤ笓瀹跺潎鍒嗭級涓� reviewPass锛堝潎鍒嗚揪鏍囦笖澶氭暟缁撹涓烘帹鑽愮珛椤癸級锛涙槑缁嗗叏閮ㄥ垹闄ゆ椂姹囨�诲�兼竻绌轰负鏈眹鎬伙紱鍥涢」鍒嗗�煎潎鍦� 0~100 涔嬪鏃舵彁绀烘寚鍑哄叿浣撶淮搴︿笌瀹為檯鍊�", "roles": ["璇勫涓撳", "瀛︿細绉樹功澶�", "璇鹃绠$悊鍛�"]},
        {"stage": "绔嬮」瀹℃壒", "action": "BPM 娴佺▼ research_topic_approve 椤哄簭璧颁笁鑺傜偣锛氱涔﹀褰㈠紡瀹℃煡 鈫� 涓撳璇勫 鈫� 瀛︿細绔嬮」瀹℃壒銆傚鎵规柟娉曚笌瀹℃壒浜哄湪娴佺▼瀹氫箟鍐呴厤缃紝閮ㄧ讲鍚庣敱绠$悊鍛樺湪 BPM 娴佺▼璁捐鍣ㄦ寜瀛︿細瀹為檯缁勭粐璋冩暣锛涘鎵逛腑蹇冨唴鐨勮嚜瀹氫箟琛ㄥ崟鍙鍥炴樉鐢虫姤鍗� + 璇勫鏄庣粏 + 鎷ㄤ粯璁板綍銆傞�氳繃鈫�20 宸茬珛椤广�侀┏鍥炩啋35 宸查┏鍥炪�佸彇娑堚啋閫�鍥� 0 鑽夌", "roles": ["瀛︿細绉樹功澶勶紙褰㈠紡瀹℃煡锛�", "璇勫涓撳锛堣瘎瀹¤妭鐐癸級", "瀛︿細鐞嗕簨浼氾紙绔嬮」瀹℃壒锛�"]},
        {"stage": "绔嬮」涓庨�氱煡", "action": "鐩戝惉鍣� ResearchApplicationStatusListener 鍏堝洖鍐欏鎵圭姸鎬侊紝瀹℃壒閫氳繃鍚庡啀璋� approveApplication锛氱敓鎴愮珛椤圭紪鍙� LX+yyyyMMdd+6浣嶆祦姘� 涓� approveTime锛堝凡鏈夌紪鍙峰垯骞傜瓑璺宠繃锛夈�傜偣銆屽彂甯冪珛椤归�氱煡銆嶇敓鎴� system_notice 鍏憡锛堟爣棰樸�庡叧浜庡叕甯冪珛椤硅棰樼殑閫氱煡銆忥紝姝f枃鍚棰樺悕/绔嬮」缂栧彿/绫诲埆/鐢虫姤浜�/鍗曚綅/璧勫姪閲戦锛夛紝鍐欏洖 noticeId 闃查噸澶嶁�斺�旈噸澶嶇偣鍑绘寜宸插彂甯冩嫤鎴苟鍛婄煡涓婃鍙戝竷鏃堕棿", "roles": ["璇鹃绠$悊鍛�", "瀛︿細绉樹功澶�"]},
-     {"stage": "缁忚垂鎷ㄤ粯", "action": "浠呫�屽凡绔嬮」(20)銆嶇殑鐢虫姤鍗曞彲鐧昏鎷ㄦ璁板綍锛堟壒娆″悕绉�/鎷ㄤ粯閲戦/鎷ㄤ粯鏃ユ湡/鏀舵鍗曚綅/澶囨敞/鍑瘉闄勪欢锛夛紝鍑瘉蹇呭~銆傜櫥璁版椂涓�鏉¢緳瀹屾垚锛氣憼 璇鹃渚ц惤 research_fund_record 鍙拌处锛堝厛缃緟鎷ㄤ粯锛夆憽 鍚屼竴鎵� blob 鍙岀粦鍒拌棰樺彴璐︿笌璐㈠姟鎶ラ攢鍗� 鈶� 缁� yudao-module-finance-api 鑷姩鐢熸垚 1:1 鐨� finance_reimbursement 鎶ラ攢鍗曪紙1 琛屾槑缁嗭細璐圭敤绫诲瀷=鍏朵粬銆佸彂绁ㄩ噾棰�=鎷ㄤ粯閲戦銆佸彂绁ㄦ棩鏈�=鎷ㄤ粯鏃ユ湡銆佽垂鐢ㄨ鏄�=銆岃棰樸�旇棰樺悕銆曠N鏈�/绗琋娆$粡璐规嫧娆俱�嶏級鈶� 绔嬪嵆鎻愪氦 BPM 鎶ラ攢瀹℃壒 鈶� 鍙拌处鍥炲啓鎶ラ攢鍗曞彿骞剁疆銆屾姤閿�瀹℃壒涓�(15)銆嶃�傞噾棰濇牎楠岋細宸茬櫥璁伴噾棰� + 鏈閲戦 涓嶅緱瓒呭嚭璇ュ崟鐢宠缁忚垂锛岃秴鍑烘椂鎻愮ず鎶ュ嚭鏈銆佸凡鐧昏銆佸悎璁°�侀绠楀洓椤规暟鍊笺�傜姸鎬佺敱绯荤粺椹卞姩锛�0寰呮嫧浠�/15鎶ラ攢瀹℃壒涓�/20宸叉嫧浠�/30宸查┏鍥烇紱鍙湁銆屽緟鎷ㄤ粯銆嶅彲鍒狅紙鍒犻櫎鑱斿姩娓呯悊璐㈠姟渚ц崏绋挎姤閿�鍗曪級锛屻�屽緟鎷ㄤ粯銆嶃�屽凡椹冲洖銆嶅彲鏀瑰苟閲嶆柊鍙戣捣鎶ラ攢锛堝凡椹冲洖鏀瑰悗娓呯┖椹冲洖鍘熷洜涓庢椂闂达級锛涘彇娑堟祦绋嬪垯閫�鍥炪�屽緟鎷ㄤ粯(0)銆嶏紙鍙戣捣浜烘挙鍥炲彧鎶婂疄渚嬮��鍥炲彂璧疯妭鐐广�佹祦绋嬫湭缁撴潫锛屽彴璐︿繚鎸併�屾姤閿�瀹℃壒涓�(15)銆嶏紝闇�閲嶆柊鎻愪氦鍚庢墠缁х画璧板鎵癸級銆傝储鍔″鎵瑰垎绫讳笅娌℃湁宸插彂甯冩祦绋嬫椂鐩存帴鎶ラ敊涓斾笉寤哄崟锛屼笉鐣欏鍎胯崏绋挎姤閿�鍗�", "roles": ["瀛︿細璐㈠姟", "璇鹃绠$悊鍛�", "璐㈠姟瀹℃牳浜�", "璐㈠姟涓荤"]}]},
+     {"stage": "鎴愬憳缁熻", "action": "鍩轰簬 research_application_member 瀛愯〃鎸夈�屽鍚� + 鐢佃瘽 + 鍗曚綅銆嶈仛鍚堟垚鍛樼淮搴︽暟鎹細鍙備笌璇鹃鏁般�佽棰樺悕鍗曘�佹垚鍛樿鑹诧紱鏀寔鎸夊叧閿瓧涓庣敵鎶ョ姸鎬佺瓫閫夛紝瀵煎嚭 Excel锛屽苟鐢熸垚鍙洿鎺ュ鍒跺埌缇ゅ唴鍙戝竷鐨勫悕鍗曟枃鏈�傚巻鍙叉枃鏈瓧娈� team_members 浠呯敤浜庡吋瀹瑰睍绀猴紝涓嶅啀鍙備笌缁熻", "roles": ["璇鹃绠$悊鍛�", "瀛︿細绉樹功澶�"]},
+       {"stage": "缁忚垂鎷ㄤ粯", "action": "浠呫�屽凡绔嬮」(20)銆嶇殑鐢虫姤鍗曞彲鐧昏鎷ㄦ璁板綍锛堟壒娆″悕绉�/鎷ㄤ粯閲戦/鎷ㄤ粯鏃ユ湡/鏀舵鍗曚綅/澶囨敞/鍑瘉闄勪欢锛夛紝鍑瘉蹇呭~銆傜櫥璁版椂涓�鏉¢緳瀹屾垚锛氣憼 璇鹃渚ц惤 research_fund_record 鍙拌处锛堝厛缃緟鎷ㄤ粯锛夆憽 鍚屼竴鎵� blob 鍙岀粦鍒拌棰樺彴璐︿笌璐㈠姟鎶ラ攢鍗� 鈶� 缁� yudao-module-finance-api 鑷姩鐢熸垚 1:1 鐨� finance_reimbursement 鎶ラ攢鍗曪紙1 琛屾槑缁嗭細璐圭敤绫诲瀷=鍏朵粬銆佸彂绁ㄩ噾棰�=鎷ㄤ粯閲戦銆佸彂绁ㄦ棩鏈�=鎷ㄤ粯鏃ユ湡銆佽垂鐢ㄨ鏄�=銆岃棰樸�旇棰樺悕銆曠N鏈�/绗琋娆$粡璐规嫧娆俱�嶏級鈶� 绔嬪嵆鎻愪氦 BPM 鎶ラ攢瀹℃壒 鈶� 鍙拌处鍥炲啓鎶ラ攢鍗曞彿骞剁疆銆屾姤閿�瀹℃壒涓�(15)銆嶃�傞噾棰濇牎楠岋細宸茬櫥璁伴噾棰� + 鏈閲戦 涓嶅緱瓒呭嚭璇ュ崟鐢宠缁忚垂锛岃秴鍑烘椂鎻愮ず鎶ュ嚭鏈銆佸凡鐧昏銆佸悎璁°�侀绠楀洓椤规暟鍊笺�傜姸鎬佺敱绯荤粺椹卞姩锛�0寰呮嫧浠�/15鎶ラ攢瀹℃壒涓�/20宸叉嫧浠�/30宸查┏鍥烇紱鍙湁銆屽緟鎷ㄤ粯銆嶅彲鍒狅紙鍒犻櫎鑱斿姩娓呯悊璐㈠姟渚ц崏绋挎姤閿�鍗曪級锛屻�屽緟鎷ㄤ粯銆嶃�屽凡椹冲洖銆嶅彲鏀瑰苟閲嶆柊鍙戣捣鎶ラ攢锛堝凡椹冲洖鏀瑰悗娓呯┖椹冲洖鍘熷洜涓庢椂闂达級锛涘彇娑堟祦绋嬪垯閫�鍥炪�屽緟鎷ㄤ粯(0)銆嶏紙鍙戣捣浜烘挙鍥炲彧鎶婂疄渚嬮��鍥炲彂璧疯妭鐐广�佹祦绋嬫湭缁撴潫锛屽彴璐︿繚鎸併�屾姤閿�瀹℃壒涓�(15)銆嶏紝闇�閲嶆柊鎻愪氦鍚庢墠缁х画璧板鎵癸級銆傝储鍔″鎵瑰垎绫讳笅娌℃湁宸插彂甯冩祦绋嬫椂鐩存帴鎶ラ敊涓斾笉寤哄崟锛屼笉鐣欏鍎胯崏绋挎姤閿�鍗�", "roles": ["瀛︿細璐㈠姟", "璇鹃绠$悊鍛�", "璐㈠姟瀹℃牳浜�", "璐㈠姟涓荤"]}]},
+    {"id": "asset", "name": "璧勪骇绠$悊娴佺▼", "mermaid": "mermaid/16-asset-flow.mmd", "color": "#8e7cc3",
+     "summary": "鐙珛璧勪骇妯″潡锛坹udao-module-asset锛夛細璧勪骇鍙拌处鎸夈�屾槗鑰楀搧 / 鍔炲叕璧勪骇 / 鍥哄畾璧勪骇銆嶄笁绫诲缓妗o紝璁板綍搴撳瓨銆佸崟浠枫�佷繚绠′汉锛堢鐞嗕汉锛変笌瀛樻斁鍦扮偣锛涢鐢ㄧ櫥璁颁粠鍙拌处閫夎祫浜с�佽褰曢鐢ㄤ汉/鏃堕棿/鏁伴噺/鐢ㄩ�旓紝鑷姩鎵e噺搴撳瓨骞剁敓鎴愰鐢ㄥ崟锛涘綊杩樻寜鏁伴噺鍥炶ˉ搴撳瓨骞舵洿鏂版湭褰掕繕/閮ㄥ垎褰掕繕/宸插綊杩樼姸鎬併��",
+     "steps": [
+       {"stage": "寤烘。", "action": "璧勪骇鍙拌处鏂板锛氳祫浜у悕绉般�佸垎绫伙紙1鏄撹�楀搧/2鍔炲叕璧勪骇/3鍥哄畾璧勪骇锛夈�佽鏍煎瀷鍙枫�佽閲忓崟浣嶃�佸簱瀛樻暟閲忋�佸崟浠枫�佽喘鍏ユ棩鏈熴�佷繚绠′汉銆佸瓨鏀惧湴鐐癸紱璧勪骇缂栧彿 ZC+yyyyMMdd+6浣嶆祦姘达紙鍙墜宸ユ寚瀹氾級锛屾�讳环鍊�=鍗曚环脳鏁伴噺", "roles": ["璧勪骇绠$悊鍛�"]},
+       {"stage": "棰嗙敤", "action": "閫夋嫨璧勪骇涓庨鐢ㄤ汉锛岃褰曢鐢ㄦ椂闂淬�佹暟閲忋�佺敤閫斻�侀璁″綊杩樻棩鏈燂紱搴撳瓨涓嶈冻鏃舵嫆缁濆苟鎻愮ず銆屽綋鍓嶅彲棰嗙敤/鏈鐢宠銆嶏紱鎴愬姛鍚庢墸鍑忓簱瀛樸�佺敓鎴� LY 棰嗙敤鍗曪紝骞跺揩鐓ц祫浜х紪鍙�/鍚嶇О/鍒嗙被涓庝繚绠′汉", "roles": ["璧勪骇绠$悊鍛�", "棰嗙敤浜�"]},
+       {"stage": "褰掕繕", "action": "鎸夋暟閲忓綊杩橈細鍥炶ˉ搴撳瓨锛屾洿鏂板凡褰掕繕鏁伴噺锛涙湭褰掕繕瀹屸啋閮ㄥ垎褰掕繕锛屽叏閮ㄥ綊杩樷啋宸插綊杩樺苟璁板綍褰掕繕鏃堕棿锛涘綊杩樻暟閲忚秴杩囨湭褰掕繕鏁伴噺鏃舵嫆缁濆苟鎶ュ嚭棰嗙敤/宸插綊杩�/鏈/鍙綊杩樺洓椤规暟鍊�", "roles": ["璧勪骇绠$悊鍛�"]},
+       {"stage": "鍐查攢", "action": "鍒犻櫎棰嗙敤璁板綍浠呭湪鏈彂鐢熷綊杩樻椂鍏佽锛屽垹闄ゆ椂鎶婃湭褰掕繕鏁伴噺閫�鍥炲簱瀛橈紱瀛樺湪鏈綊杩橀鐢ㄧ殑璧勪骇绂佹鍒犻櫎", "roles": ["璧勪骇绠$悊鍛�"]}]},
+    {"id": "vehicle", "name": "鐢ㄨ溅绠$悊娴佺▼", "mermaid": "mermaid/17-vehicle-flow.mmd", "color": "#5a9bd5",
+     "summary": "鐙珛鐢ㄨ溅妯″潡锛坹udao-module-vehicle锛夛細鐢宠浜哄~鍐欑敤杞︽椂闂淬�佸嚭鍙戝湴銆佺洰鐨勫湴銆侀殢琛屼汉鏁般�佷簨鐢� 鈫� 鎻愪氦 BPM 鐢ㄨ溅瀹℃壒锛堝鏍� + 閫愮骇瀹℃壒锛夆啋 瀹℃壒缁撴灉鍥炲啓鐢宠鍗曞苟鍙嶉鐢宠浜猴紝鍙煡鐪嬮�愮骇瀹℃壒璁板綍銆�",
+     "steps": [
+       {"stage": "鐢宠", "action": "濉啓鐢ㄨ溅寮�濮�/缁撴潫鏃堕棿銆佸嚭鍙戝湴銆佺洰鐨勫湴銆侀殢琛屼汉鏁般�佺敤杞︿簨鐢便�佽溅鐗屽彿锛堥�夊~锛夛紱鐢宠鍗曞彿 YC+yyyyMMdd+6浣嶆祦姘达紱鐢宠浜�=褰撳墠鐧诲綍鐢ㄦ埛锛岀姸鎬�=鑽夌", "roles": ["鐢ㄨ溅鐢宠浜�"]},
+       {"stage": "淇敼/鍒犻櫎", "action": "鑽夌涓庡凡椹冲洖鍙慨鏀癸紱浠呰崏绋垮彲鍒犻櫎", "roles": ["鐢ㄨ溅鐢宠浜�"]},
+       {"stage": "鎻愪氦瀹℃壒", "action": "閫夋嫨 BPM 鐢ㄨ溅瀹℃壒娴佺▼鍚庢彁浜わ紝鐘舵�佺疆 15 瀹℃壒涓苟鍥炲啓 processInstanceId锛涙祦绋� key 鍙� approve-process-list 瀹炴椂鍒楄〃", "roles": ["鐢ㄨ溅鐢宠浜�"]},
+       {"stage": "瀹℃牳涓庨�愮骇瀹℃壒", "action": "BPM 娴佺▼鎸夐厤缃殑瀹℃牳/閫愮骇瀹℃壒鑺傜偣娴佽浆锛堣妭鐐逛笌瀹℃壒浜哄湪娴佺▼璁捐鍣ㄤ腑閰嶇疆锛屾敮鎸侀�愮骇瀹℃壒锛夛紱浠讳竴鑺傜偣椹冲洖鍗崇粨鏉�", "roles": ["瀹℃牳浜�", "閮ㄩ棬璐熻矗浜�", "鍒嗙棰嗗"]},
+       {"stage": "缁撴灉鍙嶉", "action": "瀹℃壒閫氳繃鈫�20 宸查�氳繃骞跺洖鍐� approveTime锛涢┏鍥炩啋35 宸查┏鍥烇紱鍙栨秷鈫掗��鍥� 0 鑽夌锛涚敵璇蜂汉鍦ㄥ垪琛ㄧ洿鎺ョ湅鍒扮粨鏋滐紝骞跺彲杩涘叆銆屾煡鐪嬪鎵广�嶆煡鐪嬮�愮骇瀹℃壒璁板綍涓庢剰瑙�", "roles": ["鐢ㄨ溅鐢宠浜�", "瀹℃壒浜�"]}]},
+    {"id": "purchase", "name": "閲囪喘绠$悊娴佺▼", "mermaid": "mermaid/18-purchase-flow.mmd", "color": "#e0a458",
+     "summary": "鐙珛閲囪喘妯″潡锛坹udao-module-purchase锛夛細閲囪喘鍗曚富琛ㄨ褰曢噰璐椂闂淬�侀噰璐汉锛堢粡鍔炰汉锛変笌閲囪喘閲戦锛�1..n 琛岄噰璐晢鍝佹槑缁嗚褰曞晢鍝佸悕绉般�佽鏍煎瀷鍙枫�佹暟閲忋�佸崟浠蜂笌閲戦锛涜閲戦=鏁伴噺脳鍗曚环锛岄噰璐噾棰�=鏄庣粏鍚堣銆傞噰璐鎵逛笌杞姤閿�涓哄悗缁渶姹傘��",
+     "steps": [
+       {"stage": "褰曞崟", "action": "濉啓閲囪喘鏃堕棿銆侀噰璐汉锛堢粡鍔炰汉锛夛紝閫愯褰曞叆閲囪喘鍟嗗搧鏄庣粏锛堝晢鍝佸悕绉般�佽鏍煎瀷鍙枫�佹暟閲忋�佸崟浠凤級锛岀郴缁熻嚜鍔ㄨ绠楄閲戦涓庨噰璐噾棰濆悎璁�", "roles": ["閲囪喘缁忓姙浜�"]},
+       {"stage": "淇濆瓨", "action": "閲囪喘鍗曞彿 CG+yyyyMMdd+6浣嶆祦姘达紱鏄庣粏闅忎富琛ㄤ竴娆℃�ф彁浜わ紝淇敼鏃跺厛鍒犲悗澧烇紱鐘舵��=鑽夌", "roles": ["閲囪喘缁忓姙浜�"]},
+       {"stage": "淇敼/鍒犻櫎", "action": "鑽夌鍙慨鏀逛笌鍒犻櫎锛屽垹闄よ繛甯︽竻鐞嗘槑缁�", "roles": ["閲囪喘缁忓姙浜�"]},
+       {"stage": "瀹℃壒涓庢姤閿�锛堝悗缁級", "action": "鍚庣画闇�姹傦細閲囪喘鍗曟彁浜ゅ鎵癸紙閲囪喘鈫掗瀵奸�氳繃锛夆啋 瀹℃壒閫氳繃鍚庤浆鍏ヨ储鍔℃姤閿�鐢宠", "roles": ["閲囪喘缁忓姙浜�", "瀹℃壒浜�", "璐㈠姟"]}]},
     {"id": "archive", "name": "妗f绠$悊娴佺▼", "mermaid": "mermaid/15-archive-flow.mmd", "color": "#7fb3d5",
      "summary": "鐙珛妗f妯″潡锛坹udao-module-archive锛夛細涔︽灦鍒嗙被锛堝垎灞傚垎鍒楋級瑙勬暣 鈫� 妗f鐧昏锛堢數瀛愰檮浠� + 瀹炰綋浣嶇疆鍥涘瓧娈碉紝瀵嗙骇涓夋。锛夆啋 鍊熼槄鐧昏璧� BPM 涓ょ骇瀹℃壒锛堟。妗堢鐞嗗憳瀹℃牳 鈫� 鍒嗙棰嗗瀹℃壒锛夆啋 瀹℃壒閫氳繃妗f杞�屽�熷嚭涓�嶁啋 褰掕繕鐧昏鍥炪�屽湪搴撱�嶅舰鎴愭祦杞棴鐜� 鈫� 閫炬湡鏈繕鏌ヨ鏃跺疄鏃惰绠楀苟鍦ㄥ垪琛ㄦ爣绾� 鈫� 瀛橀噺涓庢祦杞粺璁★紙鎬绘暟/鍦ㄥ簱/鍊熷嚭涓�/閫炬湡 + 鍒嗙被路瀵嗙骇路杞戒綋涓夌粍鍒嗗竷锛夈�傚悓鏃舵帴鏀惰棰橈紙宸茬珛椤癸級銆佷細鍛橈紙姝e父锛夈�佽储鍔★紙宸查�氳繃锛変笁鏂圭殑銆屽綊妗c�嶈仈鍔紝缁忔柊寤虹殑 yudao-module-archive-api 鐢熸垚妗f骞跺洖鍐欐潵婧愯〃 archive_document_id銆備細璁�/鍏氬缓妗f绯荤粺鍐呮棤鏉ユ簮妯″潡锛屼粎鎵嬪伐褰曞叆銆�",
      "steps": [
diff --git a/docs/project-business/data/modules.json b/docs/project-business/data/modules.json
index d12eed8..9760b42 100644
--- a/docs/project-business/data/modules.json
+++ b/docs/project-business/data/modules.json
@@ -35,7 +35,7 @@
     {"key": "fin", "name": "璐㈠姟鎶ラ攢锛堢嫭绔嬶級", "color": "#3ec9a7", "tag": "FIN", "flow": "finance",
      "positioning": "鐙珛鐨勫彂绁ㄦ姤閿�鍗曟嵁涓庡綊妗�",
      "problem": "绾歌川鍙戠エ闅惧綊闆嗐�佹姤閿�瀹℃壒鏃犵暀鐥曘�佸綊妗e悗鏃犳硶杩芥函",
-     "features": ["鎶ラ攢鍗�(涓昏〃 + 1..n 鏄庣粏琛�)", "鍙戠エ闄勪欢澶氭枃浠朵笂浼�(闄勪欢涓績澶氭�佸叧鑱� storage_attachment)", "鍗曞彿 Redis 鐢熸垚 BX+yyyyMMdd+6浣嶆祦姘绰锋瘡鏃ラ噸缃�", "鏄庣粏闅忎富琛ㄤ竴娆℃�ф彁浜�(鍏堝垹鍚庡鍏ㄩ噺瑕嗙洊)", "BPM 瀹℃壒(閫氳繃20/椹冲洖35/鍙栨秷閫�鍥炶崏绋�0)", "宸查┏鍥炲彲鏀瑰彂绁ㄥ悗閲嶆柊鎻愪氦", "褰掓。缁堟�� 40 骞惰褰曞綊妗f椂闂�", "璐圭敤绫诲瀷/鎶ラ攢鐘舵�佸弻瀛楀吀 + Excel 瀵煎嚭", "瀵瑰鎻愪緵 FinanceReimbursementApi(yudao-module-finance-api)渚涜棰樼鐞嗙粡璐规嫧浠樿嚜鍔ㄥ缓鍗曞苟鎻愪氦(1 琛屾槑缁喡疯垂鐢ㄧ被鍨�=鍏朵粬路閲戦涓�鑷�)"],
+     "features": ["鎶ラ攢鍗�(涓昏〃 + 1..n 鏄庣粏琛�)", "鍙戠エ闄勪欢澶氭枃浠朵笂浼�(闄勪欢涓績澶氭�佸叧鑱� storage_attachment)", "鍙戠エ OCR 璇嗗埆(鍥剧墖/PDF路鍗曟鈮�20 涓稟I 澶氭ā鎬佸洖濉彿鐮�/绫诲瀷/閲戦/鏃ユ湡/閿�鍞柟)", "鍙戠エ绫诲瀷鍥哄畾鏋氫妇(澧炲�肩◣涓撶敤鍙戠エ/澧炲�肩◣鏅�氬彂绁�/鐢靛瓙鍙戠エ/鏀舵嵁/鍏朵粬)", "鐢宠浜哄彧璇� + 浠d粬浜哄彂璧�(瀹為檯鐢宠浜� applicant_id + 浠e綍浜� creator)", "鎻愪氦瀹℃壒鍐欏叆鐢宠浜轰笌浠e綍浜烘祦绋嬪彉閲�(applicantId/applicantName/initiatorId/initiatorName/isProxy)", "鍗曞彿 Redis 鐢熸垚 BX+yyyyMMdd+6浣嶆祦姘绰锋瘡鏃ラ噸缃�", "鏄庣粏闅忎富琛ㄤ竴娆℃�ф彁浜�(鍏堝垹鍚庡鍏ㄩ噺瑕嗙洊)", "BPM 瀹℃壒(閫氳繃20/椹冲洖35/鍙栨秷閫�鍥炶崏绋�0)", "宸查┏鍥炲彲鏀瑰彂绁ㄥ悗閲嶆柊鎻愪氦", "褰掓。缁堟�� 40 骞惰褰曞綊妗f椂闂�", "璐圭敤绫诲瀷/鎶ラ攢鐘舵�佸弻瀛楀吀 + Excel 瀵煎嚭", "瀵瑰鎻愪緵 FinanceReimbursementApi(yudao-module-finance-api)渚涜棰樼鐞嗙粡璐规嫧浠樿嚜鍔ㄥ缓鍗曞苟鎻愪氦(1 琛屾槑缁喡疯垂鐢ㄧ被鍨�=鍏朵粬路閲戦涓�鑷�)"],
      "objects": ["鎶ラ攢鍗�", "鎶ラ攢鏄庣粏"],
      "roles": ["鎶ラ攢浜�", "璐㈠姟", "瀹℃壒浜�"],
      "upstream": ["璇鹃绠$悊(缁忚垂鎷ㄤ粯 1:1 鑷姩鐢熸垚鎶ラ攢鍗暵疯蛋 yudao-module-finance-api路鏈湴 Bean 璋冪敤)"], "downstream": [],
@@ -99,7 +99,7 @@
     {"key": "ums", "name": "浼氬憳绠$悊锛堢嫭绔嬶級", "color": "#d6955c", "tag": "UMS", "flow": "ums",
      "positioning": "瀛︿細/鍗忎細鍨嬩細鍛樺叏鐢熷懡鍛ㄦ湡锛氬叆浼氱敵璇峰鎵� 鈫� 鑷姩寤烘。 鈫� 浼氬憳鍒楄〃涓庨�氳褰�",
      "problem": "浼氬憳鍏ヤ細闈犵嚎涓嬭〃鏍间笌浜哄伐寤烘。锛屼細鍛樼瓑绾у彛寰勬贩涔憋紝浼氬憳淇℃伅鏃犳硶鎸夊湴鍖�/琛屼笟妫�绱�",
-     "features": ["鍏ヤ細鐢宠鍗�(涓汉/鍗曚綅浜屽瀷路瀛楁鍒嗗弶)", "鐢宠绛夌骇涓ょ骇鍙e緞(涓汉 ums_personal_grade / 鍗曚綅 ums_unit_grade)", "鐢宠缂栧彿 SQ+yyyyMMdd+6浣嶆祦姘绰蜂細鍛樼紪鍙� HY+鍚屾棩娴佹按(Redis 姣忔棩閲嶇疆)", "BPM 涓ょ骇瀹℃壒(绉樹功澶勫垵瀹♀啋鐞嗕簨浼氬璁�)路鐘舵�� 0鑽夌/15瀹℃壒涓�/20宸查�氳繃/35宸查┏鍥�", "瀹℃壒閫氳繃鑷姩寤轰細鍛� + 鑷姩寤虹郴缁熺櫥褰曡处鍙�(AdminUserApi.createUser路鐢ㄦ埛鍚嶄笌鍒濆瀵嗙爜=鑱旂郴鐢佃瘽)", "浼氬憳绛夌骇涓庣被鍨嬪尮閰嶆牎楠�(鍗曚綅浼氬憳涓嶅彲閫夈�庣悊浜嬮暱銆�)", "浼氬憳閫氳褰曞彧璇绘绱�(鎸夊湴鍖虹紪鐮�/琛屼笟)", "璇佹槑鏉愭枡璧� system 闄勪欢涓績(recordType=ums_application / ums_member)"],
+     "features": ["鍏ヤ細鐢宠鍗�(涓汉/鍗曚綅浜屽瀷路瀛楁鍒嗗弶)", "鐢宠绛夌骇涓ょ骇鍙e緞(涓汉 ums_personal_grade / 鍗曚綅 ums_unit_grade)", "鐢宠缂栧彿 SQ+yyyyMMdd+6浣嶆祦姘绰蜂細鍛樼紪鍙� HY+鍚屾棩娴佹按(Redis 姣忔棩閲嶇疆)", "BPM 涓ょ骇瀹℃壒(绉樹功澶勫垵瀹♀啋鐞嗕簨浼氬璁�)路鐘舵�� 0鑽夌/15瀹℃壒涓�/20宸查�氳繃/35宸查┏鍥�", "瀹℃壒閫氳繃鑷姩寤轰細鍛� + 鑷姩寤虹郴缁熺櫥褰曡处鍙�(AdminUserApi.createUser路鐢ㄦ埛鍚嶄笌鍒濆瀵嗙爜=鑱旂郴鐢佃瘽)", "浼氬憳绛夌骇涓庣被鍨嬪尮閰嶆牎楠�(鍗曚綅浼氬憳涓嶅彲閫夈�庣悊浜嬮暱銆�)", "浼氳垂缂寸撼鏍囪瘑(鏄惁缂寸撼 + 缂磋垂骞村害路宸茬即蹇呭~骞村害路鏈即娓呯┖路鍒楄〃灞曠ず)", "浼氬憳鑷姪缁存姢璧勬枡(鎴戠殑璧勬枡椤德锋彁浜ゅ彉鏇寸敵璇封啋绠$悊鍛樺鏍搁�氳繃鍚庡啓鍥灺烽┏鍥炲師鍥犲洖鏄�)", "鏅�氫細鍛樿鑹叉潈闄愰殧绂�(浠呫�屾垜鐨勮祫鏂欍�嶈彍鍗暵锋。妗堢鐞嗙瓑鍚庡彴鑿滃崟涓嶅彲瑙伮锋帴鍙f潈闄愮爜鍏滃簳)", "浼氬憳閫氳褰曞彧璇绘绱�(鎸夊湴鍖虹紪鐮�/琛屼笟)", "璇佹槑鏉愭枡璧� system 闄勪欢涓績(recordType=ums_application / ums_member)"],
      "objects": ["鍏ヤ細鐢宠鍗�", "浼氬憳"],
      "roles": ["鍏ヤ細鐢宠浜�", "浼氬憳绠$悊鍛�", "绉樹功澶�(鍒濆)", "鐞嗕簨浼�(瀹¤)"],
      "upstream": [], "downstream": ["system(鑷姩鍒涘缓鐧诲綍璐﹀彿)"],
@@ -107,12 +107,36 @@
     {"key": "res", "name": "璇鹃绠$悊锛堢嫭绔嬶級", "color": "#e070a8", "tag": "RES", "flow": "research",
      "positioning": "瀛︿細/鍗忎細鍨嬬鐮旇棰樺叏鐢熷懡鍛ㄦ湡锛氭寚鍗楀彂甯� 鈫� 鍦ㄧ嚎鐢虫姤 鈫� 涓撳璇勫 鈫� 绔嬮」瀹℃壒 鈫� 缁忚垂鎷ㄤ粯锛堢珛椤归�氱煡璧扮郴缁熷叕鍛婏級",
      "problem": "璇鹃鎸囧崡闈犵嚎涓嬪彂甯冦�佺敵鎶ヤ功闈犵焊璐ㄦ垨閭欢鏀堕泦銆佷笓瀹惰瘎鍒嗘暎钀藉湪琛ㄦ牸閲屾棤娉曡嚜鍔ㄦ眹鎬绘帓鍚嶃�佺珛椤逛笌鎷ㄦ鏃犵暀鐥�",
-     "features": ["璇鹃鎸囧崡(閲嶇偣1/涓�鑸�2 鍒嗙被鏍囨敞)", "鎸囧崡鐘舵�� 0鑽夌/20宸插彂甯�/30宸叉埅姝⒙峰彂甯�/鎴涓や釜鍔ㄤ綔", "鎸囧崡缂栧彿 GL+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)", "鍦ㄧ嚎鐢虫姤涔�(閫夋寚鍗�+閫変細鍛樺甫鍑虹敵鎶ヤ汉淇℃伅+璇鹃缁勬垚鍛�+鐢宠缁忚垂+鐢虫姤涔︽鏂�)", "鐢虫姤缂栧彿 SB+鍚屾棩娴佹按路绫诲埆鍐椾綑鑷寚鍗椾笉淇′换鍓嶇", "鐢虫姤鐘舵�� 0鑽夌/15瀹℃壒涓�/20宸茬珛椤�/35宸查┏鍥�", "鎻愪氦鍓嶆牎楠屾寚鍗楀凡鍙戝竷涓斿湪鐢虫姤鏈熷唴(閫愭潯鎶ュ叿浣撶淮搴︿笌鏃堕棿)", "涓撳璇勫鏄庣粏瀛愯〃(涓�浣嶄笓瀹朵竴琛屄峰洓椤瑰姞鏉� 0.3/0.3/0.2/0.2)", "鍚庣鑷姩閲嶇畻鍔犳潈鍚堣涓庢帓鍚嵚峰洖鍐欎富鍗曞潎鍒嗕笌鎺ㄨ崘缁撹", "BPM 涓夌骇瀹℃壒 research_topic_approve(绉樹功澶勫舰寮忓鏌モ啋涓撳璇勫鈫掑浼氱珛椤瑰鎵�)", "瀹℃壒閫氳繃鐢熸垚绔嬮」缂栧彿 LX+鍚屾棩娴佹按 涓庣珛椤规椂闂�(骞傜瓑)", "鍙戝竷绔嬮」閫氱煡(鐢熸垚 system_notice 鍏憡路鍐欏洖 noticeId 闃查噸澶�)", "缁忚垂鎷ㄤ粯璁板綍(鎵规/閲戦/鏃ユ湡/鏀舵鍗曚綅/澶囨敞/鍑瘉闄勪欢)路鍑瘉蹇呭~路閲戦涓嶅緱瓒呯敵璇风粡璐�", "缁忚垂鎷ㄤ粯 1:1 鑷姩鐢熸垚骞舵彁浜よ储鍔℃姤閿�鍗�(FinanceReimbursementApi路1 琛屾槑缁喡疯垂鐢ㄧ被鍨�=鍏朵粬)", "鎷ㄤ粯鐘舵�� 0寰呮嫧浠�/15鎶ラ攢瀹℃壒涓�/20宸叉嫧浠�/30宸查┏鍥灺风敱 BPM 鐩戝惉鍣ㄥ洖鍐�(鍚┏鍥炲師鍥犱笌鏃堕棿)", "宸查┏鍥炲彲鏀归噾棰�/鍑瘉鍚庨噸鏂板彂璧锋姤閿�路寰呮嫧浠樺彲鍒犲苟鑱斿姩娓呯悊璐㈠姟渚ц崏绋挎姤閿�鍗�", "闄勪欢璧� system 闄勪欢涓績(recordType=research_guideline / research_application / research_fund_record)"],
+     "features": ["璇鹃鎸囧崡(閲嶇偣1/涓�鑸�2 鍒嗙被鏍囨敞)", "鎸囧崡鐘舵�� 0鑽夌/20宸插彂甯�/30宸叉埅姝⒙峰彂甯�/鎴涓や釜鍔ㄤ綔", "鎸囧崡缂栧彿 GL+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)", "鍦ㄧ嚎鐢虫姤涔�(閫夋寚鍗�+閫変細鍛樺甫鍑虹敵鎶ヤ汉淇℃伅+璇鹃缁勬垚鍛�+鐢宠缁忚垂+鐢虫姤涔︽鏂�)", "鑱旂郴鐢佃瘽蹇呭~ + 11 浣嶆墜鏈哄彿鏍煎紡鏍¢獙(鍓嶅悗绔弻閲嶆牎楠�)", "璇鹃缁勬垚鍛樺瓙琛�(濮撳悕/鐢佃瘽/鍗曚綅/鑱岀О/瑙掕壊/鎺掑簭)", "鎴愬憳缁村害缁熻(濮撳悕+鐢佃瘽+鍗曚綅鑱氬悎鍙備笌璇鹃鏁�)路瀵煎嚭 Excel路澶嶅埗缇ゅ彂鍚嶅崟", "鐢虫姤缂栧彿 SB+鍚屾棩娴佹按路绫诲埆鍐椾綑鑷寚鍗椾笉淇′换鍓嶇", "鐢虫姤鐘舵�� 0鑽夌/15瀹℃壒涓�/20宸茬珛椤�/35宸查┏鍥�", "鎻愪氦鍓嶆牎楠屾寚鍗楀凡鍙戝竷涓斿湪鐢虫姤鏈熷唴(閫愭潯鎶ュ叿浣撶淮搴︿笌鏃堕棿)", "涓撳璇勫鏄庣粏瀛愯〃(涓�浣嶄笓瀹朵竴琛屄峰洓椤瑰姞鏉� 0.3/0.3/0.2/0.2)", "鍚庣鑷姩閲嶇畻鍔犳潈鍚堣涓庢帓鍚嵚峰洖鍐欎富鍗曞潎鍒嗕笌鎺ㄨ崘缁撹", "BPM 涓夌骇瀹℃壒 research_topic_approve(绉樹功澶勫舰寮忓鏌モ啋涓撳璇勫鈫掑浼氱珛椤瑰鎵�)", "瀹℃壒閫氳繃鐢熸垚绔嬮」缂栧彿 LX+鍚屾棩娴佹按 涓庣珛椤规椂闂�(骞傜瓑)", "鍙戝竷绔嬮」閫氱煡(鐢熸垚 system_notice 鍏憡路鍐欏洖 noticeId 闃查噸澶�)", "缁忚垂鎷ㄤ粯璁板綍(鎵规/閲戦/鏃ユ湡/鏀舵鍗曚綅/澶囨敞/鍑瘉闄勪欢)路鍑瘉蹇呭~路閲戦涓嶅緱瓒呯敵璇风粡璐�", "缁忚垂鎷ㄤ粯 1:1 鑷姩鐢熸垚骞舵彁浜よ储鍔℃姤閿�鍗�(FinanceReimbursementApi路1 琛屾槑缁喡疯垂鐢ㄧ被鍨�=鍏朵粬)", "鎷ㄤ粯鐘舵�� 0寰呮嫧浠�/15鎶ラ攢瀹℃壒涓�/20宸叉嫧浠�/30宸查┏鍥灺风敱 BPM 鐩戝惉鍣ㄥ洖鍐�(鍚┏鍥炲師鍥犱笌鏃堕棿)", "宸查┏鍥炲彲鏀归噾棰�/鍑瘉鍚庨噸鏂板彂璧锋姤閿�路寰呮嫧浠樺彲鍒犲苟鑱斿姩娓呯悊璐㈠姟渚ц崏绋挎姤閿�鍗�", "闄勪欢璧� system 闄勪欢涓績(recordType=research_guideline / research_application / research_fund_record)"],
      "objects": ["璇鹃鎸囧崡", "璇鹃鐢虫姤涔�", "涓撳璇勫鏄庣粏", "缁忚垂鎷ㄤ粯璁板綍"],
      "roles": ["璇鹃鐢虫姤浜�(浼氬憳)", "璇鹃绠$悊鍛�", "璇勫涓撳", "瀛︿細绉樹功澶�", "瀛︿細鐞嗕簨浼�", "瀛︿細璐㈠姟"],
      "upstream": ["浼氬憳绠$悊(ums_member 鍙栫敵鎶ヤ汉濮撳悕/鍗曚綅/鑱岀О/鐢佃瘽)", "BPM(娴佺▼瀹氫箟 research_topic_approve / finance_reimbursement_approve)"],
      "downstream": ["system(system_notice 绔嬮」閫氱煡鍏憡)", "system(闄勪欢涓績)", "璐㈠姟(缁忚垂鎷ㄤ粯鐢熸垚 finance_reimbursement 鎶ラ攢鍗暵峰凡瀵规帴)"],
      "coreTables": ["research_guideline", "research_application", "research_topic_review", "research_fund_record"]},
+    {"key": "ast", "name": "璧勪骇绠$悊锛堢嫭绔嬶級", "color": "#8e7cc3", "tag": "AST", "flow": "asset",
+     "positioning": "璧勪骇鍙拌处 + 棰嗙敤褰掕繕鐨勭嫭绔嬬鐞嗘ā鍧�",
+     "problem": "璧勪骇鍒嗘暎鍦ㄤ釜浜烘墜閲屻�侀鐢ㄦ棤璁板綍銆佷繚绠′汉涓庨鐢ㄤ汉涓嶆竻銆佸簱瀛樿处瀹炰笉绗�",
+     "features": ["璧勪骇鍙拌处涓夌被: 鏄撹�楀搧/鍔炲叕璧勪骇(鎵撳嵃鏈虹瓑)/鍥哄畾璧勪骇", "璧勪骇缂栧彿 ZC+yyyyMMdd+6浣嶆祦姘绰峰彲鎵嬪伐鎸囧畾", "搴撳瓨鏁伴噺/鍗曚环/鎬讳环鍊艰嚜鍔ㄨ绠�", "淇濈浜�(绠$悊浜�)涓庡瓨鏀惧湴鐐硅褰�", "棰嗙敤鐧昏: 棰嗙敤浜�/棰嗙敤鏃堕棿/鏁伴噺/鐢ㄩ��/棰勮褰掕繕", "棰嗙敤鏃舵牎楠屽簱瀛樺苟鑷姩鎵e噺路涓嶈冻鏃剁簿纭彁绀�", "褰掕繕鐧昏: 閮ㄥ垎褰掕繕/鍏ㄩ儴褰掕繕路鑷姩鍥炶ˉ搴撳瓨", "鍒犻櫎棰嗙敤鍐查攢搴撳瓨(浠呮湭鍙戠敓褰掕繕)路鏈夋湭褰掕繕璁板綍绂佹鍒犺祫浜�", "棰嗙敤鍗曞彿 LY+yyyyMMdd+6浣嶆祦姘�"],
+     "objects": ["璧勪骇鍙拌处", "璧勪骇棰嗙敤"],
+     "roles": ["璧勪骇绠$悊鍛�", "棰嗙敤浜�"],
+     "upstream": ["system(鐢ㄦ埛淇℃伅鐢ㄤ簬淇濈浜�/棰嗙敤浜哄睍绀�)"], "downstream": [],
+     "coreTables": ["asset_item", "asset_requisition"]},
+    {"key": "veh", "name": "鐢ㄨ溅绠$悊锛堢嫭绔嬶級", "color": "#5a9bd5", "tag": "VEH", "flow": "vehicle",
+     "positioning": "鐢ㄨ溅鐢宠 + 瀹℃牳 + 閫愮骇瀹℃壒",
+     "problem": "鐢ㄨ溅鐢宠闈犲彛澶�/绾歌川銆佸鎵规棤鐣欑棔銆佺粨鏋滄棤娉曞弽棣堢敵璇蜂汉",
+     "features": ["鐢ㄨ溅鐢宠(鏃堕棿/鍑哄彂鍦�/鐩殑鍦�/闅忚浜烘暟/浜嬬敱/杞︾墝)", "鐢宠鍗曞彿 YC+yyyyMMdd+6浣嶆祦姘�", "鐘舵�� 0鑽夌/15瀹℃壒涓�/20宸查�氳繃/35宸查┏鍥�", "鎻愪氦 BPM 鐢ㄨ溅瀹℃壒(瀹℃牳 + 閫愮骇瀹℃壒鑺傜偣鍙厤缃�)", "瀹℃壒缁撴灉鐩戝惉鍣ㄥ洖鍐欑敵璇峰崟", "瀹℃壒閫氳繃鍥炲啓 approveTime", "鍙栨秷娴佺▼閫�鍥炶崏绋�", "鏌ョ湅閫愮骇瀹℃壒璁板綍涓庢剰瑙�"],
+     "objects": ["鐢ㄨ溅鐢宠"],
+     "roles": ["鐢ㄨ溅鐢宠浜�", "瀹℃牳浜�", "閮ㄩ棬璐熻矗浜�", "鍒嗙棰嗗"],
+     "upstream": ["BPM(娴佺▼瀹氫箟 vehicle_application_approve)"], "downstream": [],
+     "coreTables": ["vehicle_application"]},
+    {"key": "pur", "name": "閲囪喘绠$悊锛堢嫭绔嬶級", "color": "#e0a458", "tag": "PUR", "flow": "purchase",
+     "positioning": "閲囪喘鍗曚富琛� + 鍟嗗搧鏄庣粏琛�",
+     "problem": "閲囪喘鍟嗗搧銆侀噾棰濄�佺粡鍔炰汉璁板綍鍒嗘暎锛屾槑缁嗘棤娉曡拷婧�",
+     "features": ["閲囪喘鍗�(閲囪喘鏃堕棿/閲囪喘浜�/閲囪喘閲戦)", "閲囪喘鍟嗗搧鏄庣粏 1..n 琛�(鍟嗗搧鍚嶇О/瑙勬牸鍨嬪彿/鏁伴噺/鍗曚环/閲戦)", "琛岄噾棰濅笌閲囪喘閲戦鑷姩璁$畻", "閲囪喘鍗曞彿 CG+yyyyMMdd+6浣嶆祦姘�", "鏄庣粏闅忎富琛ㄤ竴娆℃�ф彁浜�(鍏堝垹鍚庡)", "鑽夌鍙敼鍙垹", "棰勭暀瀹℃壒涓庤浆鎶ラ攢鎵╁睍"],
+     "objects": ["閲囪喘鍗�", "閲囪喘鍟嗗搧鏄庣粏"],
+     "roles": ["閲囪喘缁忓姙浜�"],
+     "upstream": [], "downstream": ["璐㈠姟鎶ラ攢(鍚庣画闇�姹�)"],
+     "coreTables": ["purchase_order", "purchase_order_item"]},
     {"key": "arc", "name": "妗f绠$悊锛堢嫭绔嬶級", "color": "#7fb3d5", "tag": "ARC", "flow": "archive",
      "positioning": "瀛︿細/鍗忎細鍨嬪疄浣撲笌鐢靛瓙妗f鍏ㄧ敓鍛藉懆鏈燂細鍒嗙被涔︽灦瑙勬暣 鈫� 妗f鐧昏涓庢绱� 鈫� 鍊熼槄瀹℃壒 鈫� 褰掕繕闂幆 鈫� 瀛橀噺涓庢祦杞粺璁★紝骞舵帴鏀惰棰�/浼氬憳/璐㈠姟涓夋柟鐨勫綊妗h仈鍔�",
      "problem": "璇鹃鏉愭枡銆佷細鍛樻潗鏂欍�佽储鍔″嚟璇佹暎钀藉悇妯″潡鏃犲褰掗泦锛涚焊璐ㄦ。妗堝瓨鏀句綅缃潬绾夸笅鍙拌处锛屽�熼槄涓庡綊杩樻棤鐣欑棔銆侀�炬湡鏃犱汉绠★紱璐㈠姟绛夋晱鎰熸。妗堟棤瀵嗙骇绠℃帶",
diff --git a/docs/project-business/data/objects.json b/docs/project-business/data/objects.json
index f3b2b54..4b76ed9 100644
--- a/docs/project-business/data/objects.json
+++ b/docs/project-business/data/objects.json
@@ -48,14 +48,21 @@
     {"name": "鎺掔彮璁″垝", "module": "璁惧", "table": "mes_cal_plan", "doc": "鐝粍脳鐝脳鏃ユ湡鎺掔彮锛屼緵鐢熶骇涓庤�冨嫟寮曠敤", "upstream": [], "downstream": ["鐢熶骇浠诲姟", "鑰冨嫟"], "keywords": "鎺掔彮 鐝粍 鐝 鏃ュ巻"},
     {"name": "鎶ュ憡妯℃澘", "module": "璐ㄦ鎶ュ憡", "table": "qc_report_template", "doc": "鏅鸿兘璐ㄦ鎶ュ憡妯℃澘锛屽惎鐢�0/鍋滅敤1锛涚敾甯冨唴瀹瑰瓨鍦� qc_report_template_version锛堣崏绋�0/宸插彂甯�1/宸插仠鐢�2锛屽凡鍙戝竷涓嶅彲瑕嗙洊锛夛紝currentVersion 鎸囧悜褰撳墠宸插彂甯冪増鏈紱婧愭枃浠剁粡 AI 瀵煎叆璇嗗埆鎴愯崏绋垮悗鐢变汉宸ョ‘璁ゆ墠鎴愪负鐗堟湰锛涘悓涓�妯℃澘閲嶅瀵煎叆鎸夈�愭浛鎹€�戝鐞嗏�斺�斿厛鍒犳棫闄勪欢锛堣繛甯� blob 琛屼笌纾佺洏鏂囦欢锛夊啀缁戞柊鐨勶紝涓嶇暀瀛ゅ効鏂囦欢锛涘垹闄ゆā鏉夸細杩炲甫娓呴檮浠�", "upstream": ["浜哄伐璁捐(GrapesJS 璁捐鍣�)", "AI 瀵煎叆鑽夌(qc-report/ai-import/draft)"], "downstream": ["妯℃澘鐗堟湰", "鎶ュ憡瀹炰緥"], "keywords": "鎶ュ憡妯℃澘 璐ㄦ鎶ュ憡 template 璁捐鍣� 鐗堟湰 AI瀵煎叆 璇嗗埆 鑽夌 婧愭枃浠舵浛鎹�"},
     {"name": "鎶ュ憡瀹炰緥", "module": "璐ㄦ鎶ュ憡", "table": "qc_report_instance", "doc": "鍑轰欢浜х墿锛岀紪鍙� QR+yyyyMMdd+4浣嶆祦姘存寜澶╃嫭绔嬩笖涓氬姟鍞竴锛屽彲鏄惧紡鎸囧畾瑕嗙洊锛堟敞鎰� uk_report_no 涓嶅尯鍒嗚蒋鍒犻櫎锛屽垹鎺夌殑鍙蜂笉鍥炴敹锛夛紱涓ゆ潯鍑轰欢鍏ュ彛锛氶�氱敤 generate锛堣皟鐢ㄦ柟鑷甫 context锛変笌 generate-from-qc锛堟暟鎹粠 MES 璐ㄦ鍗曞彇锛屾潈闄愮爜 qc-report:instance:generate 宸插叆 system_menu id=1075416锛屼簲閬撴牎楠屾寜涓氬姟浼樺厛绾у彧鎶ョ涓�澶勶細105_000 绫诲瀷闈炴硶 / 105_001 鍗曟嵁涓嶅瓨鍦� / 105_002 鏈畬鎴� / 105_003 鏈垽瀹� / 105_005 妯℃澘绫诲瀷涓嶇 / 105_006 鏃犲彲鐢ㄦ楠岄」锛涘洓寮犺川妫�鍗曞垪琛ㄧ殑琛屾搷浣溿�屽嚭鎶ュ憡銆嶄粎鍦� status=宸插畬鎴愭椂鍙锛屽脊绐楀彧鍒� reportType 涓�鑷淬�佹ā鏉垮惎鐢ㄣ�佷笖 currentVersion 鎸囧悜鐨勭増鏈凡鍙戝竷鐨勬ā鏉匡紙鍚庣 /qc-report/template/selectable 鍒ゅ畾鈥斺�斿仠鐢ㄧ増鏈笉娓呯┖ currentVersion锛屽墠绔嬁鍒嗛〉缁撴灉鑷繁鎺ㄥ浼氭紡锛夛紝缁撴灉灏卞湴灞曠ず骞惰烦鏈〉鍒楄〃锛夛紱鍐荤粨 data_snapshot锛堝垽瀹氬悗鏁版嵁锛屽惈鍚庣绠楀ソ鐨� PASS/FAIL 涓庡悎鏍肩巼锛変笌 render_html锛況egenerate 鍙噸娓叉帓鐗堛�佹暟鎹笌缂栧彿涓嶅彉锛沚usiness_id/business_type 瀛� MES 琛ㄥ悕锛坢es_qc_iqc 绛夛級涓庡崟鎹� ID锛屽彲鍙嶆煡銆佷竴鍗曞彲鍑哄浠斤紱鎶ュ憡澶村彟甯﹀崟鎹師鍒ゅ畾 qcResult(鏁�)/qcResultText(涓枃锛屽悎鏍�/鐗归噰/涓嶅悎鏍奸��璐�/涓嶅悎鏍兼姤搴�)锛屼笌寮曟搸涓夋�佸垽瀹氬苟瀛樹簰涓嶈鐩栵紱鍝嶅簲甯� undecidableCount=蹇収涓� result 涓虹┖涓茬殑椤规暟涓� warnings锛堝垎缁勬爣棰樿烦杩�/鎸囨爣宸插垹/鏈綍瀹炴祴鍊�/鏍峰搧瓒�3锛夛紝涓よ�呴兘涓嶈惤搴撱�佷簨鍚庣湅涓嶅埌", "upstream": ["鎶ュ憡妯℃澘鐗堟湰", "璐ㄦ鍗曚笂涓嬫枃(閫氱敤 generate 鐢辫皟鐢ㄦ柟浼犲叆)", "MES 璐ㄦ鍗� IQC/IPQC/OQC/RQC(generate-from-qc 缁� MesQcReportApi 鍙栨暟)"], "downstream": ["鍓嶇瀹炰緥椤� /qc/report-instance(鍒楄〃路iframe棰勮路蹇収椤电路瀵煎嚭PDF)", "PDF褰掓。(system_storage_attachment recordType=qc_report_instance)"], "keywords": "鎶ュ憡瀹炰緥 鍑轰欢 QR缂栧彿 蹇収 澶嶇幇 regenerate PDF褰掓。 鎶ュ憡瀹炰緥椤� 瀵煎嚭PDF generate-from-qc 璐ㄦ鍗曞嚭鎶ュ憡 鍑烘姤鍛婂脊绐� qcResult undecidableCount warnings 寰呭垽瀹� 鍙嶆煡 涓氬姟鍗曟嵁绛涢��"},
-    {"name": "鎶ラ攢鍗�", "module": "璐㈠姟鎶ラ攢", "table": "finance_reimbursement", "doc": "鍗曞彿鍙嚜瀹氫箟(涓棿涓嶅厑璁哥┖鏍硷紝涓庡凡鏈夊崟鍙烽噸澶嶅垯鎶ラ敊)锛涙柊澧炴椂鐣欑┖鎴栫函绌烘牸鐢辩郴缁熺敓鎴� BX+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛屼慨鏀规椂鐣欑┖琛ㄧず淇濇寔涓嶅彉銆俿tatus: 0鑽夌/15瀹℃壒涓�/20宸查�氳繃/35宸查┏鍥�/40宸插綊妗o紱浠呰崏绋垮彲鍒犮�佽崏绋夸笌宸查┏鍥炲彲鏀广�佷粎宸查�氳繃鍙綊妗o紱鎻愪氦鍚庣敱 BPM 鐩戝惉鍣ㄦ寜缁撴灉鍥炲啓鐘舵��(鍙栨秷閫�鍥炶崏绋�)锛屽鎵逛腑 processInstanceId 琚洖鍐欍�傛潵婧愪袱鏉★細鈶� 璐㈠姟妯″潡椤甸潰浜哄伐濉姤 鈶¤棰樼鐞嗐�岀粡璐规嫧浠樸�嶇粡 FinanceReimbursementApi(yudao-module-finance-api)鑷姩寤哄崟骞剁珛鍗虫彁浜�(1:1銆侀噾棰濅竴鑷淬�佺郴缁熷浐瀹� 1 琛屾槑缁喡疯垂鐢ㄧ被鍨�=鍏朵粬)鈥斺�斿悗鑰呯殑瀹℃壒缁撴灉鐢辫棰樹晶 ResearchFundStatusListener 璁㈤槄鍚屼竴娴佺▼鍥炲啓鎷ㄦ鍙拌处锛屾姤閿�鍗曟湰韬笉鍥炲啓浠讳綍澶栭儴鍗曟嵁锛沘rchive_document_id 闈炵┖琛ㄧず宸插綊妗�", "upstream": ["璇鹃绠$悊(缁忚垂鎷ㄤ粯路缁� FinanceReimbursementApi 鑷姩寤哄崟)", "浜哄伐濉姤"], "downstream": ["鎶ラ攢鏄庣粏", "鍙戠エ闄勪欢(storage_attachment路recordType=finance_reimbursement)", "璇鹃鎷ㄦ鍙拌处(浠呯粡 BPM 浜嬩欢鍥炲啓鐘舵��)", "妗f绠$悊(鏃㈡湁褰掓。鍔ㄤ綔鍚屼簨鍔″彔鍔犅风敓鎴愩�屼繚瀵� 3銆嶇骇璐㈠姟妗f骞跺洖鍐� archive_document_id)"], "keywords": "鎶ラ攢鍗� 鎶ラ攢 鍙戠エ reimbursement BX 褰掓。 璐㈠姟 璐圭敤 鑷畾涔夊崟鍙� 璇鹃缁忚垂鎷ㄤ粯 鑷姩寤哄崟 FinanceReimbursementApi 璐㈠姟妗f 淇濆瘑 archive_document_id"},
-    {"name": "鎶ラ攢鏄庣粏", "module": "璐㈠姟鎶ラ攢", "table": "finance_reimbursement_item", "doc": "鎶ラ攢鍗曠殑璐圭敤琛岋細璐圭敤绫诲瀷(1宸梾/2鍔炲叕/3鎷涘緟/4浜ら��/5鍏朵粬)銆佸彂绁ㄥ彿鐮併�佸彂绁ㄩ噾棰濄�佸彂绁ㄦ棩鏈熴�佸紑绁ㄦ柟銆佽垂鐢ㄨ鏄庯紱闅忎富琛ㄤ竴娆℃�ф彁浜�(鍏堝垹鍚庡鍏ㄩ噺瑕嗙洊)锛屾彁浜ゆ椂蹇呴』鍓ユ帀琛� id 涓� reimbursementId 鈥斺�� 鏄庣粏鍒犻櫎鏄蒋鍒狅紝鍥炰紶鍘� id 浼氫笌宸茶蒋鍒犺涓婚敭鍐茬獊", "upstream": ["鎶ラ攢鍗�"], "downstream": [], "keywords": "鎶ラ攢鏄庣粏 璐圭敤鏄庣粏 璐圭敤绫诲瀷 鍙戠エ閲戦 item 鏄庣粏琛� 宸梾"},
+    {"name": "鎶ラ攢鍗�", "module": "璐㈠姟鎶ラ攢", "table": "finance_reimbursement", "doc": "鐢宠浜烘敮鎸佹湰浜哄彂璧蜂笌浠d粬浜哄彂璧凤細applicant_id 瀛樺疄闄呯敵璇蜂汉锛宑reator 瀛樹唬褰曚汉锛堝垱寤轰汉锛夛紱鏂板/淇敼鏃� applicantId 涓嶄紶榛樿鐧诲綍鐢ㄦ埛锛屼紶鍒欐牎楠岀敤鎴峰瓨鍦ㄤ笖鍚敤锛涙彁浜ゅ鎵规椂鍐欏叆娴佺▼鍙橀噺 applicantId/applicantName/initiatorId/initiatorName/isProxy銆傚崟鍙峰彲鑷畾涔�(涓棿涓嶅厑璁哥┖鏍硷紝涓庡凡鏈夊崟鍙烽噸澶嶅垯鎶ラ敊)锛涙柊澧炴椂鐣欑┖鎴栫函绌烘牸鐢辩郴缁熺敓鎴� BX+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛屼慨鏀规椂鐣欑┖琛ㄧず淇濇寔涓嶅彉銆俿tatus: 0鑽夌/15瀹℃壒涓�/20宸查�氳繃/35宸查┏鍥�/40宸插綊妗o紱浠呰崏绋垮彲鍒犮�佽崏绋夸笌宸查┏鍥炲彲鏀广�佷粎宸查�氳繃鍙綊妗o紱鎻愪氦鍚庣敱 BPM 鐩戝惉鍣ㄦ寜缁撴灉鍥炲啓鐘舵��(鍙栨秷閫�鍥炶崏绋�)锛屽鎵逛腑 processInstanceId 琚洖鍐欍�傛潵婧愪袱鏉★細鈶� 璐㈠姟妯″潡椤甸潰浜哄伐濉姤 鈶¤棰樼鐞嗐�岀粡璐规嫧浠樸�嶇粡 FinanceReimbursementApi(yudao-module-finance-api)鑷姩寤哄崟骞剁珛鍗虫彁浜�(1:1銆侀噾棰濅竴鑷淬�佺郴缁熷浐瀹� 1 琛屾槑缁喡疯垂鐢ㄧ被鍨�=鍏朵粬)鈥斺�斿悗鑰呯殑瀹℃壒缁撴灉鐢辫棰樹晶 ResearchFundStatusListener 璁㈤槄鍚屼竴娴佺▼鍥炲啓鎷ㄦ鍙拌处锛屾姤閿�鍗曟湰韬笉鍥炲啓浠讳綍澶栭儴鍗曟嵁锛沘rchive_document_id 闈炵┖琛ㄧず宸插綊妗�", "upstream": ["璇鹃绠$悊(缁忚垂鎷ㄤ粯路缁� FinanceReimbursementApi 鑷姩寤哄崟)", "浜哄伐濉姤"], "downstream": ["鎶ラ攢鏄庣粏", "鍙戠エ闄勪欢(storage_attachment路recordType=finance_reimbursement)", "璇鹃鎷ㄦ鍙拌处(浠呯粡 BPM 浜嬩欢鍥炲啓鐘舵��)", "妗f绠$悊(鏃㈡湁褰掓。鍔ㄤ綔鍚屼簨鍔″彔鍔犅风敓鎴愩�屼繚瀵� 3銆嶇骇璐㈠姟妗f骞跺洖鍐� archive_document_id)"], "keywords": "鎶ラ攢鍗� 鎶ラ攢 鍙戠エ reimbursement 浠e彂璧� 浠e綍浜� 瀹為檯鐢宠浜� OCR 鍙戠エ绫诲瀷 BX 褰掓。 璐㈠姟 璐圭敤 鑷畾涔夊崟鍙� 璇鹃缁忚垂鎷ㄤ粯 鑷姩寤哄崟 FinanceReimbursementApi 璐㈠姟妗f 淇濆瘑 archive_document_id"},
+    {"name": "鎶ラ攢鏄庣粏", "module": "璐㈠姟鎶ラ攢", "table": "finance_reimbursement_item", "doc": "鎶ラ攢鍗曠殑璐圭敤琛岋細璐圭敤绫诲瀷(1宸梾/2鍔炲叕/3鎷涘緟/4浜ら��/5鍏朵粬)銆佸彂绁ㄧ被鍨�(澧炲�肩◣涓撶敤鍙戠エ/澧炲�肩◣鏅�氬彂绁�/鐢靛瓙鍙戠エ/鏀舵嵁/鍏朵粬)銆佸彂绁ㄥ彿鐮併�佸彂绁ㄩ噾棰濄�佸彂绁ㄦ棩鏈熴�佸紑绁ㄦ柟銆佽垂鐢ㄨ鏄庯紱闅忎富琛ㄤ竴娆℃�ф彁浜�(鍏堝垹鍚庡鍏ㄩ噺瑕嗙洊)锛屾彁浜ゆ椂蹇呴』鍓ユ帀琛� id 涓� reimbursementId 鈥斺�� 鏄庣粏鍒犻櫎鏄蒋鍒狅紝鍥炰紶鍘� id 浼氫笌宸茶蒋鍒犺涓婚敭鍐茬獊锛涘彂绁ㄧ被鍨�/鍙风爜/閲戦/鏃ユ湡/閿�鍞柟鍙敱鍙戠エ OCR 璇嗗埆鑷姩鍥炲~锛岄渶浜哄伐鏍稿", "upstream": ["鎶ラ攢鍗�"], "downstream": [], "keywords": "鎶ラ攢鏄庣粏 璐圭敤鏄庣粏 璐圭敤绫诲瀷 鍙戠エ绫诲瀷 澧炲�肩◣涓撶敤鍙戠エ 鐢靛瓙鍙戠エ 鏀舵嵁 鍙戠エ閲戦 item 鏄庣粏琛� 宸梾"},
     {"name": "鍏ヤ細鐢宠鍗�", "module": "浼氬憳绠$悊", "table": "ums_application", "doc": "鍗曞彿 SQ+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛屽悗绔敓鎴愪笉鍙嚜瀹氫箟锛沵emberType 1涓汉/2鍗曚綅鍐冲畾瀛楁鍒嗗弶(涓汉鐢� workUnit/title锛屽崟浣嶇敤 legalPerson/organizationCode)锛沵emberGrade 鎸夌被鍨嬪彇涓嶅悓瀛楀吀锛泂tatus: 0鑽夌/15瀹℃壒涓�/20宸查�氳繃/35宸查┏鍥炪�備粎鑽夌鍙垹(杩炲甫娓呴檮浠�)銆佽崏绋夸笌宸查┏鍥炲彲鏀瑰彲閲嶆彁锛涙彁浜ゅ悗鐢� BPM 鐩戝惉鍣ㄦ寜缁撴灉鍥炲啓(鍙栨秷閫�鍥炶崏绋�)锛屽鎵逛腑鍥炲啓 processInstanceId锛涘鎵归�氳繃瑙﹀彂鑷姩寤轰細鍛樹笌绯荤粺璐﹀彿銆傜嫭绔嬫ā鍧楋紝涓嶄笌鍏跺畠涓氬姟妯″潡浜х敓鍗曟嵁鍏宠仈", "upstream": ["浜哄伐濉姤(鐢宠浜�)", "璇佹槑鏉愭枡闄勪欢(storage_attachment路recordType=ums_application)"], "downstream": ["浼氬憳(瀹℃壒閫氳繃鑷姩鍒涘缓)", "BPM 娴佺▼瀹炰緥(ums_membership_approve 鍒嗙被)"], "keywords": "鍏ヤ細鐢宠 鍏ヤ細 浼氬憳鐢宠 application SQ 绉樹功澶� 鐞嗕簨浼� 瀹℃壒 浼氬憳绫诲瀷 涓汉 鍗曚綅 娉曞畾浠h〃浜� 鐮旂┒鏂瑰悜 鐢宠绛夌骇"},
-    {"name": "浼氬憳", "module": "浼氬憳绠$悊", "table": "ums_member", "doc": "鍗曞彿 HY+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛涗袱涓潵婧愶細鈶犲叆浼氱敵璇峰鎵归�氳繃鑷姩鍒涘缓(鍥炲~ applicationId 涓� userName/绯荤粺璐﹀彿) 鈶′細鍛樺垪琛ㄦ墜宸ユ柊澧炪�俿tatus: 1姝e父/0鏆傚仠锛沵emberGrade 鎸� memberType 鍙栧搴斿瓧鍏革紝鏍¢獙瑙勫垯鏄崟浣嶄細鍛�(type=2)涓嶅彲閫夈�庣悊浜嬮暱銆�(grade=5)銆傝嚜鍔ㄥ缓妗f椂鍏ヤ細鏃ユ湡=褰撳ぉ銆佷笉鎼繍 contactUser/unitIntro/expireDate(鐢宠鍗曟棤瀵瑰簲瀛楁)锛涜嚜鍔ㄥ缓鐨勭郴缁熻处鍙风敤鎴峰悕涓鸿仈绯荤數璇濅笖鍒濆瀵嗙爜鍚屼负鑱旂郴鐢佃瘽锛岄儴闂ㄥ浐瀹氫负銆屽浼氫細鍛樸�嶏紱鎵嬫満鍙烽噸澶嶄細鏄庣‘鎶ラ敊涓旀暣鍗曚簨鍔″洖婊氾紱archive_document_id 闈炵┖琛ㄧず宸插綊妗o紙褰掓。鍚庡垪琛ㄩ〉闅愯棌銆屽綊妗c�嶆寜閽級", "upstream": ["鍏ヤ細鐢宠鍗�(瀹℃壒閫氳繃)", "浜哄伐鏂板", "闄勪欢(storage_attachment路recordType=ums_member)"], "downstream": ["浼氬憳閫氳褰�(鎸� regionCode/industry 妫�绱�)", "妗f绠$悊(鎵嬪姩銆屽綊妗c�嵚蜂粎姝e父鐘舵�佸彲褰掓。路缁� yudao-module-archive-api 鐢熸垚妗f骞跺洖鍐� archive_document_id)", "system_users(鐧诲綍璐﹀彿)"], "keywords": "浼氬憳 member HY 浼氬憳缂栧彿 浼氬憳绛夌骇 浼氬憳绫诲瀷 閫氳褰� 鍦板尯 琛屼笟 鍏ヤ細鏃ユ湡 鏈夋晥鏈� 褰掓。 archive_document_id"},
+    {"name": "浼氬憳", "module": "浼氬憳绠$悊", "table": "ums_member", "doc": "鍗曞彿 HY+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛涗袱涓潵婧愶細鈶犲叆浼氱敵璇峰鎵归�氳繃鑷姩鍒涘缓(鍥炲~ applicationId 涓� userName/绯荤粺璐﹀彿) 鈶′細鍛樺垪琛ㄦ墜宸ユ柊澧炪�俿tatus: 1姝e父/0鏆傚仠锛沵emberGrade 鎸� memberType 鍙栧搴斿瓧鍏革紝鏍¢獙瑙勫垯鏄崟浣嶄細鍛�(type=2)涓嶅彲閫夈�庣悊浜嬮暱銆�(grade=5)銆傝嚜鍔ㄥ缓妗f椂鍏ヤ細鏃ユ湡=褰撳ぉ銆佷笉鎼繍 contactUser/unitIntro/expireDate(鐢宠鍗曟棤瀵瑰簲瀛楁)锛涜嚜鍔ㄥ缓鐨勭郴缁熻处鍙风敤鎴峰悕涓鸿仈绯荤數璇濅笖鍒濆瀵嗙爜鍚屼负鑱旂郴鐢佃瘽锛岄儴闂ㄥ浐瀹氫负銆屽浼氫細鍛樸�嶏紱鎵嬫満鍙烽噸澶嶄細鏄庣‘鎶ラ敊涓旀暣鍗曚簨鍔″洖婊氾紱archive_document_id 闈炵┖琛ㄧず宸插綊妗o紙褰掓。鍚庡垪琛ㄩ〉闅愯棌銆屽綊妗c�嶆寜閽級锛沠ee_paid 琛ㄧず鏄惁缂寸撼浼氳垂锛宖ee_year 璁板綍缂磋垂骞村害锛堝 2026锛夛紝鏍囪宸茬即鏃跺勾搴﹀繀濉�佸彇娑堢即绾虫椂缃┖锛屽垪琛ㄥ睍绀恒�屽凡缂达紙骞村害锛�/鏈即銆嶏紱浼氬憳鍙�氳繃銆屾垜鐨勮祫鏂欍�嶆彁浜よ祫鏂欏彉鏇寸敵璇凤紝绠$悊鍛樺鏍搁�氳繃鍚庢墠鍐欏洖鏈〃", "upstream": ["鍏ヤ細鐢宠鍗�(瀹℃壒閫氳繃)", "浜哄伐鏂板", "闄勪欢(storage_attachment路recordType=ums_member)"], "downstream": ["浼氬憳閫氳褰�(鎸� regionCode/industry 妫�绱�)", "妗f绠$悊(鎵嬪姩銆屽綊妗c�嵚蜂粎姝e父鐘舵�佸彲褰掓。路缁� yudao-module-archive-api 鐢熸垚妗f骞跺洖鍐� archive_document_id)", "system_users(鐧诲綍璐﹀彿)"], "keywords": "浼氬憳 member HY 浼氬憳缂栧彿 浼氬憳绛夌骇 浼氬憳绫诲瀷 閫氳褰� 鍦板尯 琛屼笟 鍏ヤ細鏃ユ湡 鏈夋晥鏈� 褰掓。 archive_document_id 浼氳垂 浼氳垂缂寸撼 缂磋垂骞村害 fee_paid fee_year"},
     {"name": "璇鹃鎸囧崡", "module": "璇鹃绠$悊", "table": "research_guideline", "doc": "鎸囧崡缂栧彿 GL+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛沜ategory 1閲嶇偣璇鹃/2涓�鑸棰橈紱publishStatus 0鑽夌/20宸插彂甯�/30宸叉埅姝紝浠呰崏绋垮彲鏀瑰彲鍒狅紝publish 鍙戝竷鍥炲啓 publishTime锛宑lose 鎴缃� 30锛涘凡浜х敓鐢虫姤鍗�(applicationMapper.selectCountByGuidelineId 澶т簬 0)鐨勬寚鍗楃姝㈠垹闄ゅ苟鍦ㄦ彁绀轰腑鎶ュ嚭璇ユ寚鍗椾笅宸叉湁澶氬皯寮犵敵鎶ュ崟锛涙鏂� content 涓洪暱鏂囨湰锛涢檮浠� recordType=research_guideline", "upstream": ["浜哄伐缁存姢(瀛︿細绉樹功澶�)", "鎸囧崡闄勪欢(storage_attachment路recordType=research_guideline)"], "downstream": ["璇鹃鐢虫姤涔�(鐢虫姤鏃跺紩鐢ㄤ笖蹇呴』宸插彂甯冨苟鍦ㄧ敵鎶ユ湡鍐�)"], "keywords": "璇鹃鎸囧崡 鎸囧崡 鍙戝竷 鎴 閲嶇偣璇鹃 涓�鑸棰� GL 鎸囧崡缂栧彿 鐢虫姤鏈� 鐢虫姤璧锋"},
-    {"name": "璇鹃鐢虫姤涔�", "module": "璇鹃绠$悊", "table": "research_application", "doc": "鐢虫姤缂栧彿 SB+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛沢uidelineId 鍏宠仈鎸囧崡銆乵emberId 鍏宠仈 ums_member(杞叧鑱旀棤澶栭敭锛屼細鍛樺垹闄ゅ悗浼氱暀涓嬫偓绌� memberId 闇�瀹归敊鏄剧ず)锛沘pplicantName/workUnit/jobTitle/contactPhone/contactEmail 鐢遍�変細鍛樿嚜鍔ㄥ甫鍑哄彲鎵嬫敼锛沜ategory 鍐椾綑鑷寚鍗椾笖涓嶄俊浠诲墠绔紶鍊硷紱budgetAmount 鐢宠缁忚垂涓烘嫧浠樹笂闄愶紱status: 0鑽夌/15瀹℃壒涓�/20宸茬珛椤�/35宸查┏鍥炪�備粎鑽夌鍙垹(杩炲甫娓呴檮浠�)銆佽崏绋夸笌宸查┏鍥炲彲鏀瑰彲閲嶆彁锛泂ubmit 鍓嶉�愭潯鏍¢獙銆屾寚鍗楀凡鍙戝竷銆嶄笌銆岀幇鍦ㄥ湪鐢虫姤鏈熷唴銆嶏紝浠讳竴涓嶆弧瓒冲嵆鎷掔粷骞舵寜缁村害缁欏嚭鎸囧崡鍚嶄笌瀹為檯鍊硷紱瀹℃壒閫氳繃鐢辩洃鍚櫒鐢熸垚绔嬮」缂栧彿 researchNo(LX+鍚屾棩娴佹按)涓� approveTime(宸叉湁缂栧彿骞傜瓑璺宠繃)锛況eviewAvgScore/reviewPass 涓鸿瘎瀹℃眹鎬诲洖鍐欏�硷紝鏄庣粏鍒犵┖鏃舵竻鎴愭湭姹囨�伙紱noticeId 璁板綍宸插彂甯冪殑绔嬮」閫氱煡鍏憡锛岄噸澶嶅彂甯冭鎷︽埅锛沘rchive_document_id 闈炵┖琛ㄧず宸插綊妗o紙褰掓。鍚庡垪琛ㄩ〉闅愯棌銆屽綊妗c�嶆寜閽級", "upstream": ["璇鹃鎸囧崡(蹇呴』宸插彂甯冧笖鍦ㄧ敵鎶ユ湡鍐�)", "浼氬憳(ums_member路甯﹀嚭鐢虫姤浜轰俊鎭�)", "鐢虫姤闄勪欢(storage_attachment路recordType=research_application)"], "downstream": ["涓撳璇勫鏄庣粏", "缁忚垂鎷ㄤ粯璁板綍", "绔嬮」閫氱煡(system_notice 鍏憡)", "BPM 娴佺▼瀹炰緥(research_topic_approve 鍒嗙被)", "妗f绠$悊(鎵嬪姩銆屽綊妗c�嵚蜂粎宸茬珛椤瑰彲褰掓。路缁� yudao-module-archive-api 鐢熸垚妗f骞跺洖鍐� archive_document_id)", "闄勪欢涓績"], "keywords": "璇鹃鐢虫姤 鐢虫姤涔� 鐢虫姤鍗� SB 鐢虫姤缂栧彿 绔嬮」 绔嬮」缂栧彿 LX 鐢宠缁忚垂 鐢虫姤涔︽鏂� 璇鹃缁勬垚鍛� 涓撳璇勫 璇勫姹囨�� 绔嬮」閫氱煡 鍙戝竷閫氱煡 鎻愪氦瀹℃壒 椹冲洖 閲嶆彁 褰掓。 archive_document_id"},
+    {"name": "璇鹃鐢虫姤涔�", "module": "璇鹃绠$悊", "table": "research_application", "doc": "鐢虫姤缂栧彿 SB+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)锛沢uidelineId 鍏宠仈鎸囧崡銆乵emberId 鍏宠仈 ums_member(杞叧鑱旀棤澶栭敭锛屼細鍛樺垹闄ゅ悗浼氱暀涓嬫偓绌� memberId 闇�瀹归敊鏄剧ず)锛沘pplicantName/workUnit/jobTitle/contactPhone/contactEmail 鐢遍�変細鍛樿嚜鍔ㄥ甫鍑哄彲鎵嬫敼锛屽叾涓� contactPhone 涓哄繀濉」涓斿繀椤讳负 11 浣嶆墜鏈哄彿锛堝墠鍚庣鍙岄噸鏍¢獙锛夛紱璇鹃缁勬垚鍛樼敱 teamMembers 鏂囨湰瀛楁鏀归�犱负瀛愯〃 research_application_member锛堟棫瀛楁浠呭吋瀹瑰睍绀猴級锛屾垚鍛橀殢涓昏〃鍏堝垹鍚庡锛沜ategory 鍐椾綑鑷寚鍗椾笖涓嶄俊浠诲墠绔紶鍊硷紱budgetAmount 鐢宠缁忚垂涓烘嫧浠樹笂闄愶紱status: 0鑽夌/15瀹℃壒涓�/20宸茬珛椤�/35宸查┏鍥炪�備粎鑽夌鍙垹(杩炲甫娓呴檮浠�)銆佽崏绋夸笌宸查┏鍥炲彲鏀瑰彲閲嶆彁锛泂ubmit 鍓嶉�愭潯鏍¢獙銆屾寚鍗楀凡鍙戝竷銆嶄笌銆岀幇鍦ㄥ湪鐢虫姤鏈熷唴銆嶏紝浠讳竴涓嶆弧瓒冲嵆鎷掔粷骞舵寜缁村害缁欏嚭鎸囧崡鍚嶄笌瀹為檯鍊硷紱瀹℃壒閫氳繃鐢辩洃鍚櫒鐢熸垚绔嬮」缂栧彿 researchNo(LX+鍚屾棩娴佹按)涓� approveTime(宸叉湁缂栧彿骞傜瓑璺宠繃)锛況eviewAvgScore/reviewPass 涓鸿瘎瀹℃眹鎬诲洖鍐欏�硷紝鏄庣粏鍒犵┖鏃舵竻鎴愭湭姹囨�伙紱noticeId 璁板綍宸插彂甯冪殑绔嬮」閫氱煡鍏憡锛岄噸澶嶅彂甯冭鎷︽埅锛沘rchive_document_id 闈炵┖琛ㄧず宸插綊妗o紙褰掓。鍚庡垪琛ㄩ〉闅愯棌銆屽綊妗c�嶆寜閽級", "upstream": ["璇鹃鎸囧崡(蹇呴』宸插彂甯冧笖鍦ㄧ敵鎶ユ湡鍐�)", "浼氬憳(ums_member路甯﹀嚭鐢虫姤浜轰俊鎭�)", "鐢虫姤闄勪欢(storage_attachment路recordType=research_application)"], "downstream": ["涓撳璇勫鏄庣粏", "缁忚垂鎷ㄤ粯璁板綍", "绔嬮」閫氱煡(system_notice 鍏憡)", "BPM 娴佺▼瀹炰緥(research_topic_approve 鍒嗙被)", "妗f绠$悊(鎵嬪姩銆屽綊妗c�嵚蜂粎宸茬珛椤瑰彲褰掓。路缁� yudao-module-archive-api 鐢熸垚妗f骞跺洖鍐� archive_document_id)", "闄勪欢涓績"], "keywords": "璇鹃鐢虫姤 鐢虫姤涔� 鐢虫姤鍗� SB 鐢虫姤缂栧彿 绔嬮」 绔嬮」缂栧彿 LX 鐢宠缁忚垂 鐢虫姤涔︽鏂� 璇鹃缁勬垚鍛� 涓撳璇勫 璇勫姹囨�� 绔嬮」閫氱煡 鍙戝竷閫氱煡 鎻愪氦瀹℃壒 椹冲洖 閲嶆彁 褰掓。 archive_document_id"},
+    {"name": "璇鹃鐢虫姤鎴愬憳", "module": "璇鹃绠$悊", "table": "research_application_member", "doc": "璇鹃鐢虫姤涔︾殑鎴愬憳瀛愯〃锛歯ame 鎴愬憳濮撳悕 / contactPhone 鑱旂郴鐢佃瘽(閫夊~路濉啓鏃堕』涓�11浣嶆墜鏈哄彿) / workUnit 鎵�灞炲崟浣� / jobTitle 鑱岀О鑱屽姟 / memberRole 鎴愬憳瑙掕壊(涓绘寔浜�/鏍稿績鎴愬憳/鍙備笌浜�) / sortNo 鎺掑簭鍙枫�傞殢涓昏〃涓�娆℃�ф彁浜わ紝淇敼鏃跺厛鍒犲悗澧炲叏閲忚鐩栵紱瀹℃壒璇︽儏鍙灞曠ず銆傛垚鍛樼淮搴︾粺璁℃寜銆屽鍚�+鐢佃瘽+鍗曚綅銆嶈涓哄悓涓�鎴愬憳鑱氬悎鍙備笌璇鹃鏁颁笌璇鹃鍚嶅崟锛屾敮鎸佸鍑� Excel 涓庣敓鎴愮兢鍙戝悕鍗曪紱鏃� research_application.team_members 鏂囨湰浠呬繚鐣欏吋瀹瑰睍绀猴紝涓嶅啀鍙備笌缁熻", "upstream": ["璇鹃鐢虫姤涔�"], "downstream": ["鎴愬憳缁村害缁熻涓庡鍑�"], "keywords": "璇鹃鎴愬憳 鎴愬憳瀛愯〃 璇鹃缁勬垚鍛� research_application_member 涓绘寔浜� 鏍稿績鎴愬憳 鍙備笌浜� 鎴愬憳缁熻 鎴愬憳鍚嶅崟 缇ゅ彂鍚嶅崟 瀵煎嚭"},
     {"name": "涓撳璇勫鏄庣粏", "module": "璇鹃绠$悊", "table": "research_topic_review", "doc": "涓�浣嶄笓瀹朵竴琛岀殑涓�涓诲琛屽瓙琛細evaluatorName/evaluatorUnit/evaluatorTitle + topicScore 閫夐浠峰�间笌鎰忎箟(鏉冮噸0.3) + planScore 鐮旂┒鏂规鍙鎬�(0.3) + teamScore 鍥㈤槦涓庣爺绌跺熀纭�(0.2) + resultScore 棰勬湡鎴愭灉涓庢晥鐩�(0.2) + conclusion 1鎺ㄨ崘绔嬮」/2涓嶆帹鑽愮珛椤� + opinion 璇勫鎰忚 + reviewTime銆俢ompositeScore 鐢卞悗绔� weightedScore 璁$畻銆乺anking 鐢卞悗绔寜鍔犳潈鍒嗛檷搴忛噸鎺掞紝鍧囦笉鎺ュ彈鍓嶇浼犲�硷紱姣忔澧炲垹鏀瑰悗璋� recalculate 鍥炲啓涓诲崟 reviewAvgScore(鍏ㄩ儴涓撳鍔犳潈鍒嗗潎鍊悸蜂繚鐣�2浣�)涓� reviewPass(鍧囧垎杈炬爣 涓� 澶氭暟缁撹涓烘帹鑽愮珛椤规椂涓� 1)锛涘洓椤瑰垎鍊艰秴鍑� 0~100 鏃舵彁绀烘寚鍑哄叿浣撶淮搴︿笌瀹為檯鍊硷紱浠呭湪鐢虫姤鍗� status=15 瀹℃壒涓彲澧炲垹鏀�", "upstream": ["璇鹃鐢虫姤涔�(浠呭鎵逛腑鍙啓)"], "downstream": ["璇鹃鐢虫姤涔�(鍥炲啓 reviewAvgScore/reviewPass)"], "keywords": "涓撳璇勫 璇勫鏄庣粏 璇勫垎 鍔犳潈 鏉冮噸 鎺掑悕 ranking compositeScore 鎺ㄨ崘绔嬮」 璇勫鎰忚 鍧囧垎"},
     {"name": "妗f鍒嗙被", "module": "妗f绠$悊", "table": "archive_category", "doc": "鏍戝舰涔︽灦鍒嗙被(parentId路0=椤剁骇)锛屾壙杞姐�屽垎灞傚垎鍒椼�嶈鏁达細6 鏉″垵濮嬪垎绫� 浼氬憳妗f member / 璇鹃妗f research / 浼氳妗f meeting / 璐㈠姟妗f finance / 鏂囦功妗f document / 鍏氬缓妗f party锛沜ode 鍏ㄥ眬鍞竴 uk_code锛沝efaultSecretLevel 涓鸿鍒嗙被涓嬫。妗堢殑榛樿瀵嗙骇(1鍏紑/2鍐呴儴/3淇濆瘑锛岃储鍔″垎绫�=3)锛泂tatus 0鍚敤/1鍋滅敤銆備粎鍙敼鍙垹锛屾湁瀛愬垎绫荤鍒犮�佽鍒嗙被涓嬫湁妗f绂佸垹銆佺埗鍒嗙被涓嶈兘璁句负鑷韩鎴栧叾瀛愬垎绫汇�備細璁�/鍏氬缓妗f绯荤粺鍐呮棤鏉ユ簮妯″潡锛屼粎鏀寔鎵嬪伐褰曞叆妗f", "upstream": ["浜哄伐缁存姢(妗f绠$悊鍛�)"], "downstream": ["妗f(鏂板鏃跺甫鍑洪粯璁ゅ瘑绾�)"], "keywords": "妗f鍒嗙被 涔︽灦 鍒嗗眰鍒嗗垪 浼氬憳妗f 璇鹃妗f 浼氳妗f 璐㈠姟妗f 鏂囦功妗f 鍏氬缓妗f category 榛樿瀵嗙骇"},
+    {"name": "浼氬憳璧勬枡鍙樻洿鐢宠", "module": "浼氬憳绠$悊", "table": "ums_member_profile_change", "doc": "浼氬憳鑷姪缁存姢璧勬枡浜х敓鐨勫彉鏇寸敵璇凤細member_id/member_no/member_name 蹇収 + 鍙樻洿鍚庣殑鑱旂郴浜�/鐢佃瘽/閭/宸ヤ綔鍗曚綅/鑱岀О/鐮旂┒鏂瑰悜/鍗曚綅绠�浠�/娉曞畾浠h〃浜�/缁熶竴绀句細淇$敤浠g爜/鍦板尯/琛屼笟锛泂tatus 0寰呭鏍�/10宸查�氳繃/20宸查┏鍥烇紱鍚屼竴浼氬憳鍚屾椂鍙厑璁镐竴鏉″緟瀹℃牳鐢宠锛岃祫鏂欐棤鍙樺寲鏃舵嫆缁濇彁浜わ紱change_summary 璁板綍瀛楁绾у樊寮備緵瀹℃牳锛涘鏍搁�氳繃鏃跺叏閲忓啓鍥� ums_member锛堝厑璁告竻绌哄瓧娈碉級锛岄┏鍥炲繀椤诲~鍐� audit_remark 骞跺洖鏄剧粰浼氬憳", "upstream": ["浼氬憳(ums_member路浼氬憳鏈汉鎻愪氦)"], "downstream": ["浼氬憳(瀹℃牳閫氳繃鍚庡啓鍥炶祫鏂�)"], "keywords": "浼氬憳璧勬枡 鑷富缁存姢 璧勬枡鍙樻洿 鍙樻洿鐢宠 鎴戠殑璧勬枡 瀹℃牳 寰呭鏍� 宸查�氳繃 宸查┏鍥� 椹冲洖鍘熷洜 ums_member_profile_change"},
+    {"name": "璧勪骇鍙拌处", "module": "璧勪骇绠$悊", "table": "asset_item", "doc": "璧勪骇涓绘暟鎹細assetNo 璧勪骇缂栧彿 ZC+yyyyMMdd+6浣嶆祦姘�(鍙墜宸ユ寚瀹�)銆乶ame 鍚嶇О銆乧ategory 1鏄撹�楀搧/2鍔炲叕璧勪骇/3鍥哄畾璧勪骇銆乻pec 瑙勬牸鍨嬪彿銆乽nit 璁¢噺鍗曚綅銆乹uantity 褰撳墠搴撳瓨鏁伴噺銆乽nitPrice 鍗曚环銆乼otalAmount 鎬讳环鍊�=鍗曚环脳鏁伴噺銆乸urchaseDate 璐叆鏃ユ湡銆乧ustodianName 淇濈浜�(绠$悊浜�)銆乴ocation 瀛樻斁鍦扮偣銆傞鐢ㄦ墸鍑忓簱瀛樸�佸綊杩樺洖琛ュ簱瀛橈紱瀛樺湪鏈綊杩橀鐢ㄨ褰曟椂绂佹鍒犻櫎", "upstream": ["浜哄伐寤烘。"], "downstream": ["璧勪骇棰嗙敤(鎵e噺/鍥炶ˉ搴撳瓨)"], "keywords": "璧勪骇 鍙拌处 鏄撹�楀搧 鍔炲叕璧勪骇 鍥哄畾璧勪骇 鎵撳嵃鏈� 璧勪骇缂栧彿 ZC 搴撳瓨 淇濈浜� 绠$悊浜� 瀛樻斁鍦扮偣 asset_item"},
+    {"name": "璧勪骇棰嗙敤", "module": "璧勪骇绠$悊", "table": "asset_requisition", "doc": "棰嗙敤鍗曪細no 棰嗙敤鍗曞彿 LY+yyyyMMdd+6浣嶆祦姘淬�乤ssetId/assetNo/assetName/category 璧勪骇蹇収銆乧ustodianName 淇濈浜�(绠$悊浜郝烽殢璧勪骇蹇収)銆乺ecipientName 棰嗙敤浜恒�乺equisitionTime 棰嗙敤鏃堕棿銆乹uantity 棰嗙敤鏁伴噺銆乸urpose 鐢ㄩ�斻�乪xpectedReturnDate 棰勮褰掕繕鏃ユ湡銆乺eturnedQuantity 宸插綊杩樻暟閲忋�乺eturnTime 鏈�杩戝綊杩樻椂闂淬�乺eturnStatus 1鏈綊杩�/2閮ㄥ垎褰掕繕/3宸插綊杩樸�傚垱寤哄嵆鎵e噺搴撳瓨锛涘綊杩樻寜鏁伴噺鍥炶ˉ搴撳瓨锛涘垹闄や粎鏈彂鐢熷綊杩樻椂鍏佽骞跺啿閿�搴撳瓨", "upstream": ["璧勪骇鍙拌处"], "downstream": ["璧勪骇鍙拌处(搴撳瓨鍥炶ˉ)"], "keywords": "璧勪骇棰嗙敤 棰嗙敤鍗� 棰嗙敤浜� 淇濈浜� 绠$悊浜� 棰嗙敤鏃堕棿 棰嗙敤鏁伴噺 鐢ㄩ�� 褰掕繕 閮ㄥ垎褰掕繕 宸插綊杩� 鏈綊杩� 鍐查攢 LY asset_requisition"},
+    {"name": "鐢ㄨ溅鐢宠", "module": "鐢ㄨ溅绠$悊", "table": "vehicle_application", "doc": "鐢ㄨ溅鐢宠鍗曪細no 鐢宠鍗曞彿 YC+yyyyMMdd+6浣嶆祦姘淬�乤pplicantId/applicantName 鐢宠浜恒�乽seStartTime/useEndTime 鐢ㄨ溅璧锋鏃堕棿銆乨eparture 鍑哄彂鍦般�乨estination 鐩殑鍦般�乸assengers 闅忚浜烘暟銆乺eason 鐢ㄨ溅浜嬬敱銆乿ehicleNo 杞︾墝鍙枫�乻tatus 0鑽夌/15瀹℃壒涓�/20宸查�氳繃/35宸查┏鍥炪�乸rocessInstanceId BPM娴佺▼瀹炰緥銆乤pproveTime 瀹℃壒閫氳繃鏃堕棿銆傛彁浜ゆ椂鏍¢獙鑽夌/宸查┏鍥炵姸鎬佸苟鍒涘缓 BPM 娴佺▼锛涚洃鍚櫒鍥炲啓瀹℃壒缁撴灉锛屽彇娑堟祦绋嬮��鍥炶崏绋匡紱浠呰崏绋垮彲鍒犮�佽崏绋夸笌宸查┏鍥炲彲鏀�", "upstream": ["鐢宠浜哄~鎶�"], "downstream": ["BPM 鐢ㄨ溅瀹℃壒娴佺▼(閫愮骇瀹℃壒)"], "keywords": "鐢ㄨ溅 鐢ㄨ溅鐢宠 鐢ㄨ溅瀹℃壒 鐢ㄨ溅瀹℃牳 閫愮骇瀹℃壒 鍑哄彂鍦� 鐩殑鍦� 闅忚溅浜烘暟 鐢ㄨ溅浜嬬敱 杞︾墝 YC vehicle_application"},
+    {"name": "閲囪喘鍗�", "module": "閲囪喘绠$悊", "table": "purchase_order", "doc": "閲囪喘鍗曚富琛細no 閲囪喘鍗曞彿 CG+yyyyMMdd+6浣嶆祦姘淬�乸urchaseDate 閲囪喘鏃堕棿銆乥uyerId/buyerName 閲囪喘浜猴紙缁忓姙浜猴級銆乼otalAmount 閲囪喘閲戦锛堟槑缁嗗悎璁★級銆乻tatus 0鑽夌銆乺emark銆傛槑缁嗛殢涓昏〃涓�娆℃�ф彁浜わ紝淇敼鏃跺厛鍒犲悗澧烇紱鑽夌鍙敼鍙垹", "upstream": ["閲囪喘缁忓姙浜哄綍鍗�"], "downstream": ["閲囪喘鍟嗗搧鏄庣粏", "璐㈠姟鎶ラ攢(鍚庣画闇�姹�)"], "keywords": "閲囪喘 閲囪喘鍗� 閲囪喘鍟嗗搧 閲囪喘閲戦 閲囪喘浜� 缁忓姙浜� 閲囪喘鏃堕棿 CG purchase_order"},
+    {"name": "閲囪喘鍟嗗搧鏄庣粏", "module": "閲囪喘绠$悊", "table": "purchase_order_item", "doc": "閲囪喘鍗曞晢鍝佹槑缁嗚锛歩temName 鍟嗗搧鍚嶇О銆乻pec 瑙勬牸鍨嬪彿銆乹uantity 鏁伴噺銆乽nitPrice 鍗曚环銆乤mount 閲戦(=鏁伴噺脳鍗曚环锛屽悗绔绠�)銆乻ortNo 鎺掑簭鍙枫�傞殢涓昏〃涓�娆℃�ф彁浜わ紝鎻愪氦鏃跺墺鎺夎 id 涓� orderId", "upstream": ["閲囪喘鍗�"], "downstream": [], "keywords": "閲囪喘鏄庣粏 鍟嗗搧鏄庣粏 鍟嗗搧鍚嶇О 瑙勬牸鍨嬪彿 鏁伴噺 鍗曚环 閲戦 purchase_order_item"},
     {"name": "妗f", "module": "妗f绠$悊", "table": "archive_document", "doc": "妗f涓昏〃锛岀紪鍙� DA+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)銆傜數瀛愪笌瀹炰綋鍏煎叿锛氱數瀛愯蛋 system 闄勪欢涓績 recordType=archive_document(琛ㄥ唴涓嶅瓨鏂囦欢鍦板潃)銆佸疄浣撹 shelfNo 涔︽灦鍙�/layerNo 灞傚彿/columnNo 鍒楀彿/boxNo 鍐屽彿妗f鐩� + storePlace 瀛樻斁浣嶇疆鎻忚堪銆俢arrierType 1鐢靛瓙/2瀹炰綋/3鐢靛瓙+瀹炰綋锛泂ecretLevel 1鍏紑/2鍐呴儴/3淇濆瘑(涓� 3 涓斿綋鍓嶇敤鎴锋棤 archive:document:sensitive 鏃惰鎯呮姤閿欍�佸垪琛ㄤ笌瀵煎嚭寮哄埗杩囨护)锛泂ourceType 1鎵嬪伐/2璇鹃/3浼氬憳/4璐㈠姟 + sourceNo/sourceId/sourceTitle 璁板綍鏉ユ簮蹇収浠ヤ究妫�绱紱status 10鍦ㄥ簱/20鍊熷嚭涓�(鍊熷嚭涓鏀广�佸瓨鍦ㄦ湭褰掕繕鍊熼槄鍗曟椂绂佸垹)銆傛潵婧愪负 2/3/4 鐨勬。妗堢敱涓夋柟鍒楄〃椤点�屽綊妗c�嶆寜閽粡 ArchiveDocumentApi.archiveBySource 鐢熸垚(骞傜瓑)锛屽苟鍥炲啓鏉ユ簮琛� archive_document_id", "upstream": ["妗f鍒嗙被", "妗f鐧昏(浜哄伐)", "璇鹃褰掓。(宸茬珛椤圭敵鎶ヤ功)", "浼氬憳褰掓。(姝e父浼氬憳)", "璐㈠姟褰掓。(宸查�氳繃鎶ラ攢鍗�)"], "downstream": ["鍊熼槄鍗�", "闄勪欢涓績(recordType=archive_document)", "鏉ユ簮琛� archive_document_id 鍥炲啓"], "keywords": "妗f 鏂囨。 document DA 妗f缂栧彿 瀵嗙骇 淇濆瘑 鍏紑 鍐呴儴 鏁忔劅妗f 杞戒綋 鐢靛瓙 瀹炰綋 涔︽灦鍙� 灞傚彿 鍒楀彿 鍐屽彿 妗f鐩� 褰掓。鏃ユ湡 鍦ㄥ簱 鍊熷嚭涓� 妫�绱� 鍏抽敭璇� tags 鏉ユ簮鍗曞彿 褰掓。鑱斿姩"},
     {"name": "鍊熼槄鍗�", "module": "妗f绠$悊", "table": "archive_borrow", "doc": "鍊熼槄涓庡綊杩樺悎涓�锛岀紪鍙� JY+yyyyMMdd+6浣嶆祦姘�(Redis 姣忔棩閲嶇疆)銆傝褰曞�熼槄浜�/閮ㄩ棬/鐢佃瘽銆佸�熼槄鏃ユ湡銆佸�熼槄鐩殑銆佸綊杩樻湡闄�(returnDeadline路閫炬湡瀹炴椂璁$畻鍩哄噯)锛沝ocumentNo/documentTitle 涓烘。妗堝揩鐓с�俿tatus 0鑽夌/15瀹℃壒涓�/20鍊熷嚭涓�/30宸插綊杩�/35宸查┏鍥烇細浠� 0 涓� 35 鍙敼鍙噸鎻愩�佷粎 0 鍙垹銆佷粎 20 鍙櫥璁板綊杩樸�俿ubmit 鍓嶆牎楠屽悓涓�妗f涓嶅緱宸叉湁 15/20 鐨勫�熼槄鍗�(鍛戒腑鎶ュ凡鍊熺敤骞跺甫妗f鍚嶄笌宸插瓨鍦ㄥ�熼槄鍗曞彿)锛涙祦绋� key archive_borrow_approve 涓ょ骇(妗f绠$悊鍛樺鏍糕啋鍒嗙棰嗗瀹℃壒)锛屽墠绔笉鍐欐 key銆傚鎵归�氳繃鐢� ArchiveBorrowStatusListener 鍥炲啓 20+approveTime 骞舵潯浠舵洿鏂版。妗� SET status=20 WHERE status=10(褰卞搷 0 琛屾姤妗f宸茶鍗犵敤骞跺洖婊�)锛涘綊杩樼櫥璁版潯浠舵洿鏂� 20鈫�30 骞跺洖鍐� returnTime/褰掕繕纭浜恒�傞�炬湡涓嶈惤搴擄紝鏌ヨ鏃跺疄鏃剁畻 overdueDays 骞跺湪鍒楄〃娓叉煋绾㈣壊銆岄�炬湡 N 澶┿�嶏紝鍙︽湁 /overdue-page 鐙珛鍏ュ彛", "upstream": ["妗f(浠� 鍦ㄥ簱 10 鍙��)", "BPM(娴佺▼ archive_borrow_approve)", "浜哄伐鐧昏"], "downstream": ["妗f(鐘舵�� 20 鍊熷嚭涓� / 褰掕繕鍥� 10 鍦ㄥ簱)"], "keywords": "鍊熼槄 鍊熼槄鍗� 鍊熼槄鐧昏 褰掕繕 褰掕繕鐧昏 閫炬湡 閫炬湡鏈繕 鍊熼槄浜� 鍊熼槄鐩殑 褰掕繕鏈熼檺 JY 鍊熼槄鍗曞彿 妗f绠$悊鍛樺鏍� 鍒嗙棰嗗瀹℃壒 娴佽浆闂幆"},
     {"name": "缁忚垂鎷ㄤ粯璁板綍", "module": "璇鹃绠$悊", "table": "research_fund_record", "doc": "涓�涓诲琛岀殑鎷ㄦ鍙拌处锛歜atchName 鎵规鍚嶇О/amount 鎷ㄤ粯閲戦/payDate 鎷ㄤ粯鏃ユ湡/payee 鏀舵鍗曚綅/remark锛屽嚟璇侀檮浠惰蛋 system 闄勪欢涓績 recordType=research_fund_record(琛ㄥ唴涓嶅瓨鏂囦欢鍦板潃)銆俿tatus: 0寰呮嫧浠�/15鎶ラ攢瀹℃壒涓�/20宸叉嫧浠�/30宸查┏鍥烇紝鐢辩郴缁熶笌 BPM 椹卞姩锛屾柊澧�/淇敼鎺ュ彛宸茬Щ闄� status 鍏ュ弬(闃蹭吉閫犮�屽凡鎷ㄤ粯銆�)銆傜櫥璁板嵆涓�鏉¢緳锛氳惤鍙拌处 鈫� 鍚屼竴鎵� blob 鍙岀粦 鈫� 缁� FinanceReimbursementApi 鑷姩鐢熸垚 1:1 鐨� finance_reimbursement 骞剁珛鍗虫彁浜� BPM 鈫� 鍥炲啓 reimbursement_id/reimbursement_no 骞剁疆 15锛涘鎵圭粨鏋滅敱鐩戝惉鍣ㄥ洖鍐欙細閫氳繃鈫�20銆侀┏鍥炩啋30(+reject_reason/reject_time)銆佸彇娑堟祦绋嬧啋閫�鍥� 0锛堝彂璧蜂汉鎾ゅ洖鍙��鍥炲彂璧疯妭鐐广�佹祦绋嬫湭缁撴潫锛岀姸鎬佷笉鍙橈級銆備粎鐢虫姤鍗� status=20 宸茬珛椤瑰彲鐧昏锛涘嚟璇佸繀濉紱閲戦鏍¢獙锛氬凡鐧昏閲戦(璇ュ崟鍏ㄩ儴璁板綍姹傚拰) + 鏈閲戦 涓嶅緱瓒呭嚭鐢虫姤鍗� budgetAmount锛岃秴鍑烘椂鎻愮ず鎶ュ嚭鏈銆佸凡鐧昏銆佸悎璁°�侀绠楀洓椤规暟鍊硷紱浠呫�屽緟鎷ㄤ粯銆嶅彲鍒�(鑱斿姩鍒犻櫎璐㈠姟渚ц崏绋挎姤閿�鍗曪紝闈炶崏绋垮垯鎷掔粷)锛屻�屽緟鎷ㄤ粯銆嶃�屽凡椹冲洖銆嶅彲鏀瑰苟閲嶆柊鍙戣捣鎶ラ攢(宸查┏鍥炴敼鍚庢竻绌� reject_reason/reject_time)锛岀紪杈戞椂鎶婃湰琛岄噾棰濆姞鍥炲彲鐢ㄤ綑棰濆睍绀洪伩鍏嶈瑙c�備笌鎶ラ攢鍗曚负杞叧鑱�(鏃犲閿�)锛岃储鍔′晶鐩存帴鍒犲崟浼氱暀涓嬫偓绌� reimbursement_id锛屾鏃朵慨鏀规姤銆屾姤閿�鍗曞凡涓嶅瓨鍦紝璇峰垹闄ゆ湰鏉¤褰曞悗閲嶆柊鐧昏銆�", "upstream": ["璇鹃鐢虫姤涔�(蹇呴』宸茬珛椤�)", "璐㈠姟鎶ラ攢鍗�(1:1 鑷姩鐢熸垚路杞叧鑱� reimbursement_id)"], "downstream": ["鎷ㄤ粯鍑瘉(storage_attachment路recordType=research_fund_record)", "璐㈠姟鎶ラ攢鍗�(鐢熸垚骞舵彁浜ゅ嚭璐β峰鎵圭粨鏋滃洖鍐欐湰鍙拌处)"], "keywords": "缁忚垂鎷ㄤ粯 鎷ㄦ 鎷ㄤ粯璁板綍 鎵规 鎷ㄤ粯閲戦 鍑瘉 寰呮嫧浠� 鎶ラ攢瀹℃壒涓� 宸叉嫧浠� 宸查┏鍥� 椹冲洖鍘熷洜 璐㈠姟鎶ラ攢鍗曞彿 reimbursement_id 鏀舵鍗曚綅 璧勫姪閲戦 棰勭畻 瓒呴 閲嶆柊鍙戣捣鎶ラ攢"}
diff --git a/docs/project-business/data/roles.json b/docs/project-business/data/roles.json
index 98ad5bb..19e49fe 100644
--- a/docs/project-business/data/roles.json
+++ b/docs/project-business/data/roles.json
@@ -21,11 +21,15 @@
     {"role": "HR", "modules": ["HRM"], "operations": ["鍛樺伐妗f鍏ヨ浆璋冪", "鑰冨嫟瑙勫垯鎺掔彮", "钖叕鏍哥畻鍙戞斁", "绂昏亴浜ゆ帴"], "data": ["hrm_*"], "scope": "閮ㄩ棬鑼冨洿 dataScope"},
     {"role": "绠$悊灞�", "modules": ["BI"], "operations": ["鏌ョ湅閲囪喘閿�鍞�/浠撳簱/鐢熶骇璁惧/璐ㄩ噺/浜哄姏浜斿ぇ涓婚澶у睆"], "data": ["bi_dashboard"], "scope": "鍙"},
     {"role": "鍏ヤ細鐢宠浜�/浼氬憳绠$悊鍛�", "modules": ["UMS"], "operations": ["濉姤涓庝慨鏀瑰叆浼氱敵璇�(涓汉/鍗曚綅)", "涓婁紶璇佹槑鏉愭枡闄勪欢", "鎻愪氦 BPM 瀹℃壒", "浼氬憳鍒楄〃缁存姢涓庝細鍛橀�氳褰曟绱�"], "data": ["ums_application", "ums_member"], "scope": "鏉冮檺鐮� ums:application:create/update/delete/submit 路 ums:member:create/update/delete/query 路 ums:member:directory锛涙棤鏁版嵁鏉冮檺闅旂(鍏ㄥ憳鍙煡閫氳褰曪紝缁存姢绫绘寜閽彟鍑潈闄愮爜)"},
+    {"role": "鏅�氫細鍛�(鑷姪)", "modules": ["UMS"], "operations": ["鐧诲綍鍚庤繘鍏ャ�屾垜鐨勮祫鏂欍�嶇淮鎶よ仈绯绘柟寮忕瓑璧勬枡", "鎻愪氦璧勬枡鍙樻洿鐢宠骞舵煡鐪嬪鏍哥姸鎬�"], "data": ["ums_member(鏈汉)", "ums_member_profile_change(鏈汉鎻愪氦)"], "scope": "鏅�氫細鍛樿鑹� id=9100 浠呮巿浜堛�屾垜鐨勮祫鏂欍�嶈彍鍗�(1075563)锛涗笉鎺堜簣妗f绠$悊(1075600~1075699)鍙婂叾浠栧悗鍙拌彍鍗曪紱妗f鎺ュ彛浠嶇敱 archive:document:* 鏉冮檺鐮佸厹搴曟嫤鎴�"},
     {"role": "绉樹功澶�/鐞嗕簨浼�(瀹℃壒浜�)", "modules": ["UMS", "BPM"], "operations": ["鍏ヤ細鐢宠涓ょ骇瀹℃壒锛氱涔﹀鍒濆 鈫� 鐞嗕簨浼氬璁�"], "data": ["ums_application(鐘舵�佸洖鍐�)", "bpm_task"], "scope": "瀹℃壒浜虹敱 BPM 娴佺▼瀹氫箟閰嶇疆锛屽悗绔笉鍐欐锛涢�氳繃鍚庣郴缁熻嚜鍔ㄥ缓浼氬憳涓庣櫥褰曡处鍙�"},
     {"role": "璇鹃鐢虫姤浜�/璇鹃绠$悊鍛�", "modules": ["RES"], "operations": ["璇鹃鎸囧崡鍙戝竷涓庢埅姝�(璇鹃绠$悊鍛�)", "鏂板缓涓庝慨鏀硅棰樼敵鎶ヤ功(閫変細鍛樺甫鍑虹敵鎶ヤ汉淇℃伅銆佷笂浼犵敵鎶ラ檮浠�)", "鎻愪氦 BPM 瀹℃壒銆侀┏鍥炲悗淇敼閲嶆彁", "姣忔湀/姣忔壒娆$櫥璁扮粡璐规嫧浠樿褰曚笌鍑瘉", "鍙戝竷绔嬮」閫氱煡"], "data": ["research_guideline", "research_application", "research_topic_review", "research_fund_record"], "scope": "鏉冮檺鐮� research:guideline:create/update/delete/publish 路 research:application:create/update/delete/submit/notice 路 research:review:create/update/delete 路 research:fund:create/update/delete锛涙棤鏁版嵁鏉冮檺闅旂(鍒楄〃涓嶆寜鐢宠浜鸿繃婊�)锛屾寜閽嚟鏉冮檺鐮佹帶鍒�"},
     {"role": "妗f绠$悊鍛�", "modules": ["ARC"], "operations": ["妗f鍒嗙被(涔︽灦)缁存姢锛氬垎灞傚缓绫汇�佽榛樿瀵嗙骇銆佸惎鐢ㄥ仠鐢�", "妗f鐧昏涓庡鏉′欢妫�绱�/瀵煎嚭锛堢數瀛愰檮浠� + 瀹炰綋浣嶇疆鍥涘瓧娈点�佸瘑绾т笁妗o級", "鍊熼槄鐧昏銆佹彁浜ゅ鎵�", "褰掕繕鐧昏涓庡綊杩樺娉�", "鏌ョ湅妗f缁熻锛堝瓨閲忎笌娴佽浆锛�"], "data": ["archive_category", "archive_document", "archive_borrow"], "scope": "鏉冮檺鐮� archive:category:create/update/delete/query 路 archive:document:create/update/delete/query/export 路 archive:borrow:create/update/delete/query/submit/return 路 archive:stat:query锛涘瘑绾�=淇濆瘑(3) 鐨勬。妗堝彟闇� archive:document:sensitive 鎵嶈兘鏌ョ湅锛屾棤璇ユ潈闄愭椂鍒楄〃涓庡鍑哄己鍒惰繃婊わ紱鏃犳暟鎹潈闄愰殧绂�(鍒楄〃涓嶆寜浜鸿繃婊�)锛屾寜閽嚟鏉冮檺鐮佹帶鍒�"},
     {"role": "鍊熼槄浜�", "modules": ["ARC"], "operations": ["鐧昏鍊熼槄鍗曪紙閫夊湪搴撴。妗堛�佸~鍊熼槄浜轰俊鎭�/鍊熼槄鏃ユ湡/鍊熼槄鐩殑/褰掕繕鏈熼檺锛�", "鎻愪氦 BPM 鍊熼槄瀹℃壒", "鏌ョ湅鍊熼槄杩涘害锛堣烦 BPM 娴佺▼瀹炰緥璇︽儏锛�"], "data": ["archive_borrow", "archive_document(浠� 鍦ㄥ簱 10 鍙��)"], "scope": "鏉冮檺鐮� archive:borrow:create/query/submit锛涗粎鑽夌(0)涓庡凡椹冲洖(35)鍙敼鍙噸鎻愩�佷粎鑽夌鍙垹锛涘悓涓�妗f涓嶅緱鍚屾椂瀛樺湪瀹℃壒涓�(15)鎴栧�熷嚭涓�(20)鐨勫�熼槄鍗�"},
     {"role": "妗f绠$悊鍛�/鍒嗙棰嗗(鍊熼槄瀹℃壒浜�)", "modules": ["ARC", "BPM"], "operations": ["鍊熼槄瀹℃壒涓ょ骇锛氭。妗堢鐞嗗憳瀹℃牳 鈫� 鍒嗙棰嗗瀹℃壒", "鍦ㄥ鎵逛腑蹇冮�氳繃鑷畾涔夎〃鍗曞彧璇绘煡鐪嬪�熼槄鍗曚笌妗f淇℃伅"], "data": ["archive_borrow(鐘舵�佸洖鍐�)", "archive_document(鐘舵�侀殢瀹℃壒缁撴灉鏉′欢鏇存柊)", "bpm_task"], "scope": "瀹℃壒浜轰笌瀹℃壒鏂规硶鍦� BPM 娴佺▼瀹氫箟 archive_borrow_approve 鍐呴厤缃�(榛樿渚濇瀹℃壒路瑙掕壊鍏滃簳)锛屽悗绔笉鍐欐瀹℃壒浜猴紝閮ㄧ讲鍚庣敱绠$悊鍛樺湪娴佺▼璁捐鍣ㄦ寜瀹為檯缁勭粐璋冩暣"},
+    {"role": "璧勪骇绠$悊鍛�/棰嗙敤浜�", "modules": ["AST"], "operations": ["缁存姢璧勪骇鍙拌处(鏄撹�楀搧/鍔炲叕璧勪骇/鍥哄畾璧勪骇)", "鐧昏璧勪骇棰嗙敤骞惰褰曢鐢ㄤ汉銆佹椂闂淬�佹暟閲忋�佺敤閫�", "鍔炵悊閮ㄥ垎/鍏ㄩ儴褰掕繕", "鍒犻櫎鏈彂鐢熷綊杩樼殑棰嗙敤璁板綍骞跺啿閿�搴撳瓨"], "data": ["asset_item", "asset_requisition"], "scope": "鏉冮檺鐮� asset:item:query/create/update/delete 路 asset:requisition:query/create/update/return/delete锛涗繚绠′汉闅忚祫浜у彴璐︺�侀鐢ㄤ汉闅忛鐢ㄥ崟鍒嗗埆璁板綍"},
+    {"role": "鐢ㄨ溅鐢宠浜�/瀹℃壒浜�", "modules": ["VEH", "BPM"], "operations": ["鎻愪氦涓庝慨鏀圭敤杞︾敵璇�", "閫夋嫨瀹℃壒娴佺▼鎻愪氦閫愮骇瀹℃壒", "鍦� BPM 瀹℃壒涓績澶勭悊鐢ㄨ溅瀹℃牳/瀹℃壒鑺傜偣", "鏌ョ湅瀹℃壒缁撴灉涓庨�愮骇瀹℃壒璁板綍"], "data": ["vehicle_application", "bpm_task"], "scope": "鏉冮檺鐮� vehicle:application:query/create/update/delete/submit锛涘鎵硅妭鐐逛笌瀹℃壒浜虹敱 BPM 娴佺▼瀹氫箟閰嶇疆"},
+    {"role": "閲囪喘缁忓姙浜�", "modules": ["PUR"], "operations": ["褰曞叆閲囪喘鍗曚笌閲囪喘鍟嗗搧鏄庣粏", "缁存姢閲囪喘鏃堕棿銆侀噰璐汉锛堢粡鍔炰汉锛変笌澶囨敞", "鑽夌淇敼/鍒犻櫎"], "data": ["purchase_order", "purchase_order_item"], "scope": "鏉冮檺鐮� purchase:order:query/create/update/delete锛涢噰璐噾棰濈敱鏄庣粏鑷姩姹囨��"},
     {"role": "褰掓。缁忓姙浜�(璇鹃/浼氬憳/璐㈠姟)", "modules": ["ARC", "RES", "UMS", "FIN"], "operations": ["鍦ㄨ棰樼敵鎶ヤ功鍒楄〃瀵广�屽凡绔嬮」銆嶅崟鎹偣銆屽綊妗c��", "鍦ㄤ細鍛樺垪琛ㄥ銆屾甯搞�嶄細鍛樼偣銆屽綊妗c��", "鍦ㄦ姤閿�鍗曞垪琛ㄥ銆屽凡閫氳繃銆嶅崟鎹偣銆屽綊妗c��(涓庡叾鏃㈡湁 20鈫�40 褰掓。鍚屼簨鍔″彔鍔�)"], "data": ["archive_document(鐢熸垚)", "research_application / ums_member / finance_reimbursement(鍥炲啓 archive_document_id)"], "scope": "鏉冮檺鐮� research:application:archive 路 ums:member:archive 路 finance:reimbursement:archive锛涜皟鐢� yudao-module-archive-api 鐨� archiveBySource 骞傜瓑鐢熸垚妗f锛岄噸澶嶇偣鍑荤敱鏉ユ簮渚ф寜閿欒鐮佹嫤鎴苟缁欏嚭宸茬敓鎴愭。妗堢紪鍙�"},
     {"role": "璇勫涓撳/瀛︿細绉樹功澶劼风悊浜嬩細(璇鹃瀹℃壒浜�)", "modules": ["RES", "BPM"], "operations": ["鍦ㄥ鎵逛腑(15)褰曞叆鏈汉鐨勪笓瀹惰瘎鍒嗘槑缁�(鍥涢」鍒嗗�� + 缁撹 + 鎰忚)", "绉樹功澶勫舰寮忓鏌� 鈫� 涓撳璇勫 鈫� 瀛︿細绔嬮」瀹℃壒 涓夎妭鐐归『搴忓鎵�", "鍦ㄥ鎵逛腑蹇冮�氳繃鑷畾涔夎〃鍗曞彧璇绘煡鐪嬬敵鎶ヤ功姝f枃銆佽瘎瀹℃槑缁嗕笌鎷ㄤ粯璁板綍"], "data": ["research_topic_review", "research_application(鐘舵�佸洖鍐�)", "bpm_task"], "scope": "璇勫垎褰曞叆瑕佹眰鐢虫姤鍗曞浜庡鎵逛腑(15)路鐢辫棰樼鐞嗗憳鎴栦笓瀹朵緷鎹� research:review:* 鏉冮檺鐮佹墽琛岋紱瀹℃壒浜轰笌瀹℃壒鏂规硶鍦� BPM 娴佺▼瀹氫箟 research_topic_approve 鍐呴厤缃�(榛樿渚濇瀹℃壒路瑙掕壊鍏滃簳)锛屽悗绔笉鍐欐瀹℃壒浜猴紝閮ㄧ讲鍚庣敱绠$悊鍛樺湪娴佺▼璁捐鍣ㄦ寜瀛︿細瀹為檯缁勭粐璋冩暣"}
   ]
diff --git a/docs/project-business/mermaid/12-finance-flow.mmd b/docs/project-business/mermaid/12-finance-flow.mmd
index 9313486..2c75997 100644
--- a/docs/project-business/mermaid/12-finance-flow.mmd
+++ b/docs/project-business/mermaid/12-finance-flow.mmd
@@ -1,13 +1,15 @@
 flowchart TD
-  U1["鎶ラ攢浜哄~鎶ユ姤閿�鍗暵蜂富琛�: 鎶ラ攢鍗曞彿(閫夊~) 鎶ラ攢鏍囬 鎶ラ攢鎬婚噾棰� 鐢宠鏃ユ湡 澶囨敞"] --> U2["褰曞叆鎶ラ攢鏄庣粏 1..n 琛�: 璐圭敤绫诲瀷 鍙戠エ鍙风爜 鍙戠エ閲戦 鍙戠エ鏃ユ湡 寮�绁ㄦ柟 璐圭敤璇存槑"]
+  U1["鎶ラ攢浜�/浠e綍浜哄~鎶ユ姤閿�鍗暵蜂富琛�: 鎶ラ攢鍗曞彿(閫夊~) 鐢宠浜�(鏈汉鍙/浠e彂璧峰彲閫�) 鎶ラ攢鏍囬 鎶ラ攢鎬婚噾棰� 鐢宠鏃ユ湡 澶囨敞"] --> U2["褰曞叆鎶ラ攢鏄庣粏 1..n 琛�: 璐圭敤绫诲瀷 鍙戠エ绫诲瀷 鍙戠エ鍙风爜 鍙戠エ閲戦 鍙戠エ鏃ユ湡 寮�绁ㄦ柟 璐圭敤璇存槑"]
+  O1["鍙戠エ OCR 璇嗗埆: 鍥剧墖/PDF 涓婁紶 storage-blob 鈫� AI 澶氭ā鎬佽瘑鍒� 鈫� 鍥炲~鍙戠エ绫诲瀷/鍙风爜/閲戦/鏃ユ湡/閿�鍞柟"] --> U2
   U2 --> U3[鍙戠エ闄勪欢: 鍥剧墖鎴� PDF 鍏堜紶 system storage-blob upload 鍙� blobId]
   U3 --> S1[淇濆瓨 create路涓昏〃涓庢槑缁嗕笌鍏ㄩ儴 blobIds 涓�娆℃�ф彁浜
-  S1 --> G1["鎶ラ攢鍗曞彿锛氬彲鑷畾涔夛紱鐣欑┖(鍚函绌烘牸)鍒欑敱鍚庣鐢熸垚 BX + yyyyMMdd + 6浣嶆祦姘�(Redis路姣忔棩閲嶇疆)"]
+  S1 --> P1["鐢宠浜�: 涓嶄紶榛樿鐧诲綍鐢ㄦ埛锛涗唬浠栦汉鍙戣捣浼犲疄闄呯敵璇蜂汉宸ュ彿骞舵牎楠屽惎鐢紱瀹為檯鐢宠浜哄瓨 applicant_id锛屼唬褰曚汉鍙� creator 瀹¤瀛楁"]
+  P1 --> G1["鎶ラ攢鍗曞彿锛氬彲鑷畾涔夛紱鐣欑┖(鍚函绌烘牸)鍒欑敱鍚庣鐢熸垚 BX + yyyyMMdd + 6浣嶆祦姘�(Redis路姣忔棩閲嶇疆)"]
   G1 --> B1["鏄庣粏鍏堝垹鍚庡鍏ㄩ噺瑕嗙洊路鎻愪氦鍓ユ帀琛� id(杞垹鍚庨噸鎻掗伩鍏嶄富閿啿绐�)"]
   B1 --> A1["闄勪欢缁戝畾 recordType = finance_reimbursement路鎸夎鍗曞叏閮� blobId 鍏ㄩ噺鏇挎崲"]
   A1 --> K1[鑽夌 0]
   K1 -->|update 淇敼涓昏〃 鏄庣粏 闄勪欢| K1
-  K1 -->|submit 閫夊鎵规祦绋� processDefinitionKey| K2[瀹℃壒涓� 15路鍥炲啓 processInstanceId]
+  K1 -->|submit 閫夊鎵规祦绋� processDefinitionKey| K2["瀹℃壒涓� 15路鍥炲啓 processInstanceId锛涙祦绋嬪彉閲忔惡甯� applicantId/applicantName/initiatorId/initiatorName/isProxy"]
   K2 -->|BPM 瀹℃壒閫氳繃| K3[宸查�氳繃 20]
   K2 -->|BPM 瀹℃壒椹冲洖| K4[宸查┏鍥� 35]
   K2 -->|BPM 瀹℃壒鍙栨秷| K1
diff --git a/docs/project-business/mermaid/13-ums-membership-flow.mmd b/docs/project-business/mermaid/13-ums-membership-flow.mmd
index 4ec25f7..6bce9e1 100644
--- a/docs/project-business/mermaid/13-ums-membership-flow.mmd
+++ b/docs/project-business/mermaid/13-ums-membership-flow.mmd
@@ -17,7 +17,12 @@
   M1 --> M2["浼氬憳缂栧彿 HY + yyyyMMdd + 6浣嶆祦姘�(Redis)"]
   M2 --> M3["鍒涘缓绯荤粺鐧诲綍璐﹀彿 AdminUserApi.createUser路鐢ㄦ埛鍚嶄笌鍒濆瀵嗙爜=鑱旂郴鐢佃瘽路鏄电О=鐢宠浜哄鍚嵚烽儴闂�=瀛︿細浼氬憳"]
   M3 --> M4[浼氬憳钀藉簱路鍏ヤ細鏃ユ湡=褰撳ぉ路鐘舵�� 1姝e父路鏉ユ簮鐢宠鍗� applicationId 鍥炲~]
-  M4 --> D2[浼氬憳鍒楄〃路鍙墜宸� create / update / delete]
+  M4 --> P1["浼氬憳鑷姪缁存姢璧勬枡: 鎴戠殑璧勬枡椤典慨鏀硅仈绯绘柟寮�/鍗曚綅/鑱岀О/鐮旂┒鏂瑰悜绛� 鈫� 鎻愪氦鍙樻洿鐢宠(寰呭鏍�)"]
+  P1 --> P2["绠$悊鍛樿祫鏂欏彉鏇村鏍�: 閫氳繃鈫掑叏閲忓啓鍥炰細鍛樿祫鏂� / 椹冲洖鈫掑繀椤诲~鍐欏師鍥犲苟鍥炴樉缁欎細鍛�"]
+  P2 --> M4
+  M4 --> R1["鏉冮檺闅旂: 鏅�氫細鍛樿鑹�(9100)浠呮巿浜堛�屾垜鐨勮祫鏂欍�嶈彍鍗暵蜂笉鍚。妗堢鐞�(1075600~1075699)路妗f鎺ュ彛浠嶉渶 archive:document:* 鏉冮檺"]
+  M4 --> F1["浼氳垂缂寸撼鏍囪瘑: fee_paid 鏄惁缂寸撼浼氳垂 + fee_year 缂磋垂骞村害(宸茬即蹇呭~路鏈即娓呯┖)"]
+  F1 --> D2[浼氬憳鍒楄〃路鍙墜宸� create / update / delete路鍒楄〃灞曠ず宸茬即骞村害]
   D2 --> V1["绛夌骇绫诲瀷鏍¢獙: 鍗曚綅浼氬憳(memberType=2) 涓嶅彲閫夈�庣悊浜嬮暱銆�(memberGrade=5) 鐩存帴鎷掔粷"]
   M4 --> D1[浼氬憳閫氳褰暵峰彧璇宦锋寜鍦板尯 regionCode 涓庤涓� industry 妫�绱
   ATT1["闄勪欢 recordType = ums_application"] -.-> K1
diff --git a/docs/project-business/mermaid/14-research-flow.mmd b/docs/project-business/mermaid/14-research-flow.mmd
index 8fdc4b8..180d667 100644
--- a/docs/project-business/mermaid/14-research-flow.mmd
+++ b/docs/project-business/mermaid/14-research-flow.mmd
@@ -10,10 +10,12 @@
   G4 -->|"鎸囧崡宸插彂甯冩墠鍙綔涓虹敵鎶ュ墠鎻�"| A1
   subgraph P2 [鍦ㄧ嚎鐢虫姤]
     A1[閫夋寚鍗椔疯姹傚凡鍙戝竷 涓斿湪鐢虫姤鏈熷唴] --> A2[閫変細鍛� ums_member 鑷姩甯﹀嚭 濮撳悕 宸ヤ綔鍗曚綅 鑱岀О 鐢佃瘽 閭]
-    A2 --> A3[濉棰樺悕绉� 璇鹃缁勬垚鍛� 鐢宠缁忚垂 鐢虫姤涔︽鏂� 鐢虫姤闄勪欢]
+    A2 --> A3["濉棰樺悕绉� 璇鹃缁勬垚鍛�(瀛愯〃) 鐢宠缁忚垂 鐢虫姤涔︽鏂� 鐢虫姤闄勪欢锛涜仈绯荤數璇濆繀濉笖鏍¢獙 11 浣嶆墜鏈哄彿"]
+    A3 --> A3M["鎴愬憳瀛愯〃: 濮撳悕/鑱旂郴鐢佃瘽/鎵�灞炲崟浣�/鑱岀О鑱屽姟/鎴愬憳瑙掕壊/鎺掑簭 閫愯褰曞叆路闅忎富琛ㄥ厛鍒犲悗澧�"]
     A3 --> A4["鐢虫姤缂栧彿 SB + yyyyMMdd + 6浣嶆祦姘绰风被鍒啑浣欒嚜鎸囧崡涓嶄俊浠诲墠绔紶鍊�"]
     A4 --> K1[鑽夌 0]
   end
+  M1["鎴愬憳缁村害缁熻: 濮撳悕+鐢佃瘽+鍗曚綅鐩稿悓瑙嗕负鍚屼竴鎴愬憳 鈫� 鍙備笌璇鹃鏁颁笌璇鹃鍚嶅崟 鈫� 瀵煎嚭 Excel / 澶嶅埗缇ゅ彂鍚嶅崟"] --- K1
   K1 -->|update 淇敼路浠呰崏绋� 0 涓庡凡椹冲洖 35 鏀捐| K1
   K1 -->|delete 鍒犻櫎路浠呰崏绋� 0 鏀捐路杩炲甫娓呯悊闄勪欢| K1
   K1 -->|submit 閫夊鎵规祦绋� processDefinitionKey| K2["瀹℃壒涓� 15路鍥炲啓 processInstanceId"]
diff --git a/docs/sql/wxcp_module.sql b/docs/sql/wxcp_module.sql
index 8f2a542..b27c5da 100644
--- a/docs/sql/wxcp_module.sql
+++ b/docs/sql/wxcp_module.sql
@@ -3,9 +3,11 @@
 -- 鐩爣搴擄細mom-jssm锛�36.213.150.237:10000锛�
 -- 璇存槑锛氶」鐩笉浣跨敤澶氱鎴凤紝鍥犳涓嶅惈 tenant_id 瀛楁
 --
--- 娲诲姩灏侀潰銆佹姤鍚嶆彁浜ょ殑鍥剧墖/闄勪欢閮戒笉钀芥湰妯″潡鐨勮〃锛�
+-- 娲诲姩灏侀潰銆佽鎯呭浘鐗囥�佹姤鍚嶆彁浜ょ殑鍥剧墖/闄勪欢閮戒笉钀芥湰妯″潡鐨勮〃锛�
 -- 缁熶竴璧� system_storage_attachment 涓棿琛紝
--- 娲诲姩灏侀潰 record_type = 'wxcp_activity'锛屾姤鍚嶉檮浠� record_type = 'wxcp_activity_signup'
+-- 娲诲姩灏侀潰 record_type = 'wxcp_activity'锛屾椿鍔ㄨ鎯呭浘鐗� record_type = 'wxcp_activity_detail'锛�
+-- 鍥剧墖閫氱煡 record_type = 'wxcp_activity_image'锛岄檮浠堕�氱煡 record_type = 'wxcp_activity_file'锛�
+-- 鎶ュ悕闄勪欢 record_type = 'wxcp_activity_signup'
 -- ============================================================
 
 -- ----------------------------
@@ -14,7 +16,12 @@
 CREATE TABLE IF NOT EXISTS `wxcp_activity` (
   `id`                 bigint       NOT NULL AUTO_INCREMENT                COMMENT '缂栧彿',
   `title`              varchar(128) NOT NULL                               COMMENT '娲诲姩鏍囬',
+  `activity_type`      tinyint      NOT NULL DEFAULT 5                     COMMENT '娲诲姩妯℃澘锛�1绾�氱煡 2鍗曟潯娑堟伅 3鍥剧墖閫氱煡 4闄勪欢閫氱煡 5鏁版嵁閲囬泦/璇勪环',
   `content`            text                                                COMMENT '娲诲姩璇︽儏',
+  `button_text`        varchar(32)           DEFAULT NULL                  COMMENT '鍗$墖鎸夐挳鏂囨',
+  `rating_enabled`     bit(1)       NOT NULL DEFAULT b'0'                  COMMENT '鏄惁寮�鍚瘎浠�',
+  `rating_title`       varchar(128)          DEFAULT NULL                  COMMENT '璇勪环鏍囬',
+  `rating_max`         int          NOT NULL DEFAULT 5                     COMMENT '璇勪环鏈�楂樻槦绾�',
   `location`           varchar(255)          DEFAULT ''                   COMMENT '娲诲姩鍦扮偣',
   `start_time`         datetime              DEFAULT NULL                 COMMENT '娲诲姩寮�濮嬫椂闂�',
   `end_time`           datetime              DEFAULT NULL                 COMMENT '娲诲姩缁撴潫鏃堕棿',
@@ -51,6 +58,7 @@
   `mobile`        varchar(32)           DEFAULT ''                        COMMENT '鎵嬫満鍙�',
   `dept_name`     varchar(255)          DEFAULT ''                        COMMENT '閮ㄩ棬鍚嶇О',
   `content`       text                                                    COMMENT '鍙備笌璇存槑锛堟垚鍛樻彁浜ょ殑鏂囧瓧鍐呭锛�',
+  `rating_score`  int                   DEFAULT NULL                      COMMENT '璇勪环鏄熺骇',
   `status`        tinyint      NOT NULL DEFAULT 0                         COMMENT '鐘舵�侊細0宸叉姤鍚� 1宸插彇娑� 2宸茬鍒�',
   `signup_time`   datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP         COMMENT '鎶ュ悕鏃堕棿',
   `check_in_time` datetime              DEFAULT NULL                      COMMENT '绛惧埌鏃堕棿',
@@ -72,6 +80,13 @@
   `id`            bigint       NOT NULL AUTO_INCREMENT                    COMMENT '缂栧彿',
   `title`         varchar(128) NOT NULL                                   COMMENT '閫氱煡鏍囬',
   `content`       text                                                    COMMENT '閫氱煡姝f枃',
+  `notice_type`   tinyint      NOT NULL DEFAULT 1                         COMMENT '閫氱煡妯℃澘锛�1绾枃鏈� 2鍥炬枃鍗$墖(濉姤/閾炬帴) 3鏂囦欢閫氱煡 4鍥炬枃娑堟伅',
+  `summary`       varchar(512)          DEFAULT NULL                      COMMENT '鎽樿锛堝浘鏂囧崱鐗�/鍥炬枃娑堟伅锛�',
+  `link_url`      varchar(1024)         DEFAULT NULL                      COMMENT '璺宠浆閾炬帴锛堝~鎶�/璇︽儏锛�',
+  `link_btn_text` varchar(32)           DEFAULT NULL                      COMMENT '閾炬帴鎸夐挳鏂囨',
+  `cover_url`     varchar(1024)         DEFAULT NULL                      COMMENT '鍥炬枃娑堟伅灏侀潰鍥惧湴鍧�',
+  `file_blob_id`  bigint                DEFAULT NULL                      COMMENT '鏂囦欢閫氱煡鐨勯檮浠� Blob ID',
+  `file_name`     varchar(255)          DEFAULT NULL                      COMMENT '鏂囦欢閫氱煡鐨勯檮浠舵枃浠跺悕',
   `status`        tinyint      NOT NULL DEFAULT 0                         COMMENT '鐘舵�侊細0鑽夌 1宸插彂甯� 2宸叉挙鍥�',
   `push_scope`    tinyint      NOT NULL DEFAULT 0                         COMMENT '鎺ㄩ�佽寖鍥达細0鍏ㄥ憳 1鎸囧畾閮ㄩ棬 2鎸囧畾鎴愬憳',
   `push_target`   varchar(1024)         DEFAULT ''                        COMMENT '鎺ㄩ�佺洰鏍囷細閮ㄩ棬ID鎴栨垚鍛樿处鍙凤紝澶氫釜鐢ㄩ�楀彿鍒嗛殧',
@@ -100,3 +115,51 @@
 PREPARE stmt FROM @sql;
 EXECUTE stmt;
 DEALLOCATE PREPARE stmt;
+
+-- ============================================================
+-- 娲诲姩鑷畾涔夐噰闆嗗瓧娈碉紙鏁版嵁閲囬泦/璇勪环妯℃澘浣跨敤锛�
+-- ============================================================
+CREATE TABLE IF NOT EXISTS `wxcp_activity_field` (
+  `id`            bigint       NOT NULL AUTO_INCREMENT                    COMMENT '缂栧彿',
+  `activity_id`   bigint       NOT NULL                                   COMMENT '娲诲姩缂栧彿',
+  `field_label`   varchar(128) NOT NULL                                   COMMENT '瀛楁鍚嶇О',
+  `field_type`    varchar(32)  NOT NULL DEFAULT 'text'                    COMMENT '瀛楁绫诲瀷锛歵ext/textarea/number/date/select/radio/checkbox/rating',
+  `field_options` varchar(1000)         DEFAULT NULL                      COMMENT '閫夐」锛屽涓敤鑻辨枃閫楀彿鍒嗛殧',
+  `required`      bit(1)       NOT NULL DEFAULT b'0'                      COMMENT '鏄惁蹇呭~',
+  `placeholder`   varchar(255)          DEFAULT NULL                      COMMENT '杈撳叆鎻愮ず',
+  `sort_no`       int          NOT NULL DEFAULT 0                         COMMENT '鎺掑簭鍙�',
+  `creator`       varchar(64)           DEFAULT ''                        COMMENT '鍒涘缓鑰�',
+  `create_time`   datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP         COMMENT '鍒涘缓鏃堕棿',
+  `updater`       varchar(64)           DEFAULT ''                        COMMENT '鏇存柊鑰�',
+  `update_time`   datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP         COMMENT '鏇存柊鏃堕棿',
+  `deleted`       bit(1)       NOT NULL DEFAULT b'0'                      COMMENT '鏄惁鍒犻櫎',
+  PRIMARY KEY (`id`),
+  KEY `idx_activity_id` (`activity_id`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '浼佷笟寰俊娲诲姩閲囬泦瀛楁';
+
+-- ============================================================
+-- 鎶ュ悕閲囬泦鍊�
+-- ============================================================
+CREATE TABLE IF NOT EXISTS `wxcp_activity_signup_value` (
+  `id`           bigint        NOT NULL AUTO_INCREMENT                    COMMENT '缂栧彿',
+  `signup_id`    bigint        NOT NULL                                   COMMENT '鎶ュ悕璁板綍缂栧彿',
+  `activity_id`  bigint        NOT NULL                                   COMMENT '娲诲姩缂栧彿',
+  `field_id`     bigint        NOT NULL                                   COMMENT '瀛楁缂栧彿',
+  `field_label`  varchar(128)  NOT NULL                                   COMMENT '瀛楁鍚嶇О锛堝揩鐓э級',
+  `field_type`   varchar(32)            DEFAULT NULL                      COMMENT '瀛楁绫诲瀷锛堝揩鐓э級',
+  `field_value`  varchar(2000)          DEFAULT NULL                      COMMENT '瀛楁鍊硷紝澶氶�夌敤鑻辨枃閫楀彿鍒嗛殧',
+  `sort_no`      int           NOT NULL DEFAULT 0                         COMMENT '鎺掑簭鍙�',
+  `creator`      varchar(64)            DEFAULT ''                        COMMENT '鍒涘缓鑰�',
+  `create_time`  datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP         COMMENT '鍒涘缓鏃堕棿',
+  `updater`      varchar(64)            DEFAULT ''                        COMMENT '鏇存柊鑰�',
+  `update_time`  datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP         COMMENT '鏇存柊鏃堕棿',
+  `deleted`      bit(1)        NOT NULL DEFAULT b'0'                      COMMENT '鏄惁鍒犻櫎',
+  PRIMARY KEY (`id`),
+  KEY `idx_signup_id` (`signup_id`),
+  KEY `idx_activity_id` (`activity_id`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '浼佷笟寰俊娲诲姩鎶ュ悕閲囬泦鍊�';
+
+-- ============================================================
+-- 鍗囩骇鑴氭湰锛氭椿鍔ㄥ妯℃澘瀛楁锛堝凡寤哄簱鐜鎵ц锛屾柊搴撴棤闇�鎵ц锛�
+-- 鍏蜂綋瑙� docs/sql/wxcp_activity_templates_20261021.sql
+-- ============================================================
diff --git a/docs/wxcp_activity_integration.md b/docs/wxcp_activity_integration.md
index b6fb4c4..dd4105f 100644
--- a/docs/wxcp_activity_integration.md
+++ b/docs/wxcp_activity_integration.md
@@ -53,6 +53,7 @@
 | title | String | 鏄� | 娲诲姩鏍囬锛屸墹128 瀛楃 |
 | content | String | 鍚� | 娲诲姩璇︽儏 |
 | blobIds | Long[] | 鍚� | **灏侀潰鍥炬枃浠剁紪鍙�**锛屾渶澶� 1 涓紱涓嶄紶 = 涓嶆敼鍔ㄥ皝闈紝浼� `[]` = 娓呯┖灏侀潰 |
+| detailBlobIds | Long[] | 鍚� | **璇︽儏鍥剧墖鏂囦欢缂栧彿**锛屾渶澶� 5 涓紱涓嶄紶 = 涓嶆敼鍔紝浼� `[]` = 娓呯┖ |
 | location | String | 鍚� | 娲诲姩鍦扮偣锛屸墹255 瀛楃 |
 | startTime | DateTime | 鍚� | 娲诲姩寮�濮嬫椂闂� |
 | endTime | DateTime | 鍚� | 娲诲姩缁撴潫鏃堕棿 |
@@ -73,6 +74,7 @@
 | pushStatus | Integer | 0鏈帹閫� 1宸叉帹閫� |
 | pushTime | DateTime | 鏈�杩戞帹閫佹椂闂� |
 | attachmentList | Object[] | **灏侀潰鍥鹃檮浠跺垪琛�**锛堟渶澶� 1 鏉★級锛屽瓧娈靛悓鎶ュ悕闄勪欢锛歚id`銆乣name`銆乣url`銆乣previewURL`銆乣downloadURL`銆傚垪琛ㄧ缉鐣ュ浘銆佽〃鍗曞洖鏄鹃兘浠庤繖閲屽彇 |
+| detailAttachmentList | Object[] | **璇︽儏鍥剧墖闄勪欢鍒楄〃**锛堟渶澶� 5 鏉★紝鎸変笂浼犲厛鍚庢帓搴忥級锛屽瓧娈靛悓涓娿�傝〃鍗曞洖鏄句粠杩欓噷鍙� |
 
 ### 鎺ㄩ�佷笌浜岀淮鐮�
 
@@ -237,7 +239,9 @@
 | 鍙栨秷鍚庨噸鏂版姤鍚� | 澶嶇敤鍘熻褰曪紝涓嶄細浜х敓绗簩鏉″巻鍙� |
 | 鍙栨秷鎶ュ悕 | 娲诲姩 `signupCount` 鑷姩 -1 |
 | 灏侀潰鍥� | 涓嶅瓨涓氬姟琛ㄥ瓧娈碉紝瀛� `system_storage_attachment`锛坄record_type = wxcp_activity`锛夈�傚垹闄ゆ椿鍔ㄦ椂涓�骞舵竻鎺夐檮浠跺叧绯� |
-| 灏侀潰鍥惧睍绀� | 鎶ュ悕椤甸《閮ㄤ互 `object-fit: cover` 灞曠ず锛涚鐞嗗悗鍙板垪琛ㄥ湪鏍囬宸︿晶鏄剧ず缂╃暐鍥� |
+| 灏侀潰鍥惧睍绀� | 鎶ュ悕椤甸《閮ㄤ互 `object-fit: cover` 灞曠ず锛堜細鎸� 307脳260 瑁佸壀锛夛紱绠$悊鍚庡彴鍒楄〃鍦ㄦ爣棰樺乏渚ф樉绀虹缉鐣ュ浘 |
+| 璇︽儏鍥剧墖 | 鍚屾牱瀛� `system_storage_attachment`锛屼絾 `record_type = wxcp_activity_detail`锛屼笌灏侀潰浜掍笉褰卞搷锛屾渶澶� 5 寮狅紝鍒犻櫎娲诲姩鏃朵竴骞舵竻鎺� |
+| 璇︽儏鍥剧墖灞曠ず | 鎶ュ悕椤靛湪銆屾椿鍔ㄨ鎯呫�嶆枃瀛椾笅鏂规寜**鍘熷浘姣斾緥鍏ㄥ灞曠ず锛屼笉瑁佸壀**锛岀偣涓�涓嬫墦寮�鍘熷浘銆�**浜岀淮鐮併�佹捣鎶ヨ繖绫昏瑁佸氨澶辨晥鐨勫浘蹇呴』鏀捐鎯呭浘鐗囷紝涓嶈兘鏀惧皝闈�** |
 
 ## 娉ㄦ剰浜嬮」
 
@@ -253,6 +257,8 @@
 - **闄勪欢涓婁紶璧� system 妯″潡鐨勫叕鍏变笂浼犳帴鍙�**锛氭姤鍚嶉〉鍏堣皟 `POST /admin-api/system/storage-blob/public-upload`锛堣〃鍗曞瓧娈靛悕 `files`锛夋嬁鍒� `blobId`锛屽啀鎶� `blobIds` 闅忔姤鍚嶆彁浜わ紝鐢�**鏈嶅姟绔�**瀹屾垚涓庢姤鍚嶈褰曠殑缁戝畾銆傜鐞嗗悗鍙版煡闄勪欢鐢� `/wxcp/activity/signup/attachments`銆�
 - **鎶ュ悕鐨� `recordType` 鍥哄畾涓� `wxcp_activity_signup`**锛屽凡娉ㄥ唽杩� system 妯″潡鐨� `StorageRecordTypeEnum` 鐧藉悕鍗曘�備互鍚庢柊澧炲埆鐨勯檮浠跺叧鑱旂被鍨嬶紝蹇呴』鍚屾娉ㄥ唽锛屽惁鍒欑粦瀹氫細鎶ャ�屾棤鏁堢殑璁板綍绫诲瀷銆嶃��
 - **娲诲姩灏侀潰鐨� `recordType` 涓� `wxcp_activity`**锛屽悓鏍峰凡娉ㄥ唽杩涚櫧鍚嶅崟銆傚皝闈�**涓嶅啀鏈� `coverUrl` 瀛楁**锛宍POST/PUT /wxcp/activity/create|update` 鎻愪氦 `blobIds`锛宍GET` 璇� `attachmentList`銆�
+- **娲诲姩璇︽儏鍥剧墖鐨� `recordType` 涓� `wxcp_activity_detail`**锛屽凡娉ㄥ唽杩涚櫧鍚嶅崟銆傛彁浜� `detailBlobIds`銆乣GET` 璇� `detailAttachmentList`锛堟寜 blobId 鍗囧簭 = 涓婁紶鍏堝悗锛屽洜涓哄簳灞� `selectByIds` 涓嶆壙璇洪『搴忥級銆�
+- **灏侀潰鍜岃鎯呭浘鐗囨槸涓ゅ鐙珛鍏崇郴**锛屽悇鑷�屼笉浼� = 涓嶆敼鍔ㄣ�佷紶 `[]` = 娓呯┖銆嶏紝鏀逛竴涓笉浼氬奖鍝嶅彟涓�涓��
 - **灏侀潰鍥惧湪鎶ュ悕椤垫樉绀烘椂浼氬幓鎺夊崗璁拰鍩熷悕**銆傞檮浠跺湴鍧�閲岀殑鍩熷悕鍙栬嚜 `yudao.storage.domain`锛堜竴鑸槸鍐呯綉鍦板潃锛夛紝鑰屾墜鏈烘壂鐮佹槸浠庡叕缃戝煙鍚嶆墦寮�椤甸潰鐨勶紝鐩磋繛鍐呯綉鍦板潃鍔犺浇涓嶅嚭鍥剧墖锛屾墍浠ュ悗绔彧淇濈暀 `/admin-api/...` 璺緞浜ょ粰鍚屾簮浠g悊銆�**濡傛灉閮ㄧ讲鏃剁鐞嗗悗鍙颁笌鎶ュ悕椤典笉鍚屾簮锛岃繖涓鐞嗛渶瑕侀噸鏂拌瘎浼般��**
 - 琛ㄥ崟閲岀殑灏侀潰鐢� `ImageUpload` 缁勪欢锛坄maxNumber: 1`锛夋椂锛岀粍浠惰繑鍥炵殑鏄�**鍗曞�艰�屼笉鏄暟缁�**锛屾彁浜ゅ墠蹇呴』鍖呮垚鏁扮粍锛坄blobIds` 鍚庣绛惧悕鏄� `List<Long>`锛夈��
 - 閫氳褰曟帴鍙�**瀹炴椂璋冧紒涓氬井淇�**锛屾湁棰戠巼闄愬埗锛屽墠绔姟蹇呯紦瀛橈紝涓嶈闅忚緭鍏ュ疄鏃惰姹傘��
diff --git a/pom.xml b/pom.xml
index 55d8a98..5a429d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,9 @@
         <!-- 妗f绠$悊妯″潡 -->
         <module>yudao-module-archive</module>
         <module>yudao-module-archive-api</module>
+        <module>yudao-module-asset</module>
+        <module>yudao-module-vehicle</module>
+        <module>yudao-module-purchase</module>
     </modules>
 
     <name>${project.artifactId}</name>
diff --git a/yudao-module-asset/pom.xml b/yudao-module-asset/pom.xml
new file mode 100644
index 0000000..587e2ef
--- /dev/null
+++ b/yudao-module-asset/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-module-asset</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        asset 鍖呬笅锛岃祫浜х鐞嗘ā鍧楋紙璧勪骇鍙拌处锛氭槗鑰楀搧/鍔炲叕璧勪骇/鍥哄畾璧勪骇锛涜祫浜ч鐢細淇濈浜恒�侀鐢ㄤ汉銆侀鐢ㄦ椂闂淬�佹暟閲忋�佺敤閫斻�佸綊杩橈級銆�
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- Web 鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- DB 鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
+        </dependency>
+
+        <!-- 宸ュ叿绫荤浉鍏� -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-excel</artifactId>
+        </dependency>
+
+        <!-- Test 娴嬭瘯鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/AssetItemController.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/AssetItemController.java
new file mode 100644
index 0000000..378a074
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/AssetItemController.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.asset.controller.admin.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemRespVO;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.item.AssetItemDO;
+import cn.iocoder.yudao.module.asset.enums.AssetCategoryEnum;
+import cn.iocoder.yudao.module.asset.service.item.AssetItemService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 璧勪骇鍙拌处")
+@RestController
+@RequestMapping("/asset/item")
+@Validated
+public class AssetItemController {
+
+    @Resource
+    private AssetItemService itemService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璧勪骇")
+    @PreAuthorize("@ss.hasPermission('asset:item:create')")
+    public CommonResult<Long> createItem(@Valid @RequestBody AssetItemSaveReqVO reqVO) {
+        return success(itemService.createItem(reqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊璧勪骇")
+    @PreAuthorize("@ss.hasPermission('asset:item:update')")
+    public CommonResult<Boolean> updateItem(@Valid @RequestBody AssetItemSaveReqVO reqVO) {
+        itemService.updateItem(reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璧勪骇")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('asset:item:delete')")
+    public CommonResult<Boolean> deleteItem(@RequestParam("id") Long id) {
+        itemService.deleteItem(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璧勪骇")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('asset:item:query')")
+    public CommonResult<AssetItemRespVO> getItem(@RequestParam("id") Long id) {
+        return success(buildRespVO(itemService.getItem(id)));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璧勪骇鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('asset:item:query')")
+    public CommonResult<PageResult<AssetItemRespVO>> getItemPage(@Valid AssetItemPageReqVO reqVO) {
+        PageResult<AssetItemDO> pageResult = itemService.getItemPage(reqVO);
+        return success(new PageResult<>(buildRespVOList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "鑾峰緱璧勪骇绮剧畝鍒楄〃锛堢敤浜庨鐢ㄩ�夋嫨锛�")
+    @PreAuthorize("@ss.hasPermission('asset:item:query')")
+    public CommonResult<List<AssetItemRespVO>> getItemSimpleList() {
+        return success(buildRespVOList(itemService.getItemSimpleList()));
+    }
+
+    private AssetItemRespVO buildRespVO(AssetItemDO item) {
+        if (item == null) {
+            return null;
+        }
+        return buildRespVOList(java.util.Collections.singletonList(item)).get(0);
+    }
+
+    private List<AssetItemRespVO> buildRespVOList(List<AssetItemDO> list) {
+        return BeanUtils.toBean(list, AssetItemRespVO.class, vo ->
+                vo.setCategoryName(AssetCategoryEnum.nameOf(vo.getCategory())));
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemPageReqVO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemPageReqVO.java
new file mode 100644
index 0000000..95e97e7
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemPageReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.asset.controller.admin.item.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - 璧勪骇鍙拌处鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AssetItemPageReqVO extends PageParam {
+
+    @Schema(description = "璧勪骇缂栧彿", example = "ZC20261020000001")
+    private String assetNo;
+
+    @Schema(description = "璧勪骇鍚嶇О", example = "鎵撳嵃鏈�")
+    private String name;
+
+    @Schema(description = "璧勪骇鍒嗙被锛�1-鏄撹�楀搧 2-鍔炲叕璧勪骇 3-鍥哄畾璧勪骇", example = "2")
+    private Integer category;
+
+    @Schema(description = "淇濈浜哄鍚�", example = "寮犱笁")
+    private String custodianName;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemRespVO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemRespVO.java
new file mode 100644
index 0000000..3bedcfa
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemRespVO.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.asset.controller.admin.item.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 璧勪骇鍙拌处 Response VO")
+@Data
+public class AssetItemRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "璧勪骇缂栧彿", example = "ZC20261020000001")
+    private String assetNo;
+
+    @Schema(description = "璧勪骇鍚嶇О", example = "婵�鍏夋墦鍗版満")
+    private String name;
+
+    @Schema(description = "璧勪骇鍒嗙被", example = "2")
+    private Integer category;
+
+    @Schema(description = "璧勪骇鍒嗙被鍚嶇О", example = "鍔炲叕璧勪骇")
+    private String categoryName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "HP M405dn")
+    private String spec;
+
+    @Schema(description = "璁¢噺鍗曚綅", example = "鍙�")
+    private String unit;
+
+    @Schema(description = "褰撳墠搴撳瓨鏁伴噺", example = "10")
+    private Integer quantity;
+
+    @Schema(description = "鍗曚环锛堝厓锛�", example = "1999.00")
+    private BigDecimal unitPrice;
+
+    @Schema(description = "鎬讳环鍊硷紙鍏冿級", example = "19990.00")
+    private BigDecimal totalAmount;
+
+    @Schema(description = "璐叆鏃ユ湡", example = "2026-10-20")
+    private LocalDate purchaseDate;
+
+    @Schema(description = "淇濈浜虹敤鎴� ID", example = "1024")
+    private Long custodianId;
+
+    @Schema(description = "淇濈浜哄鍚�", example = "寮犱笁")
+    private String custodianName;
+
+    @Schema(description = "瀛樻斁鍦扮偣", example = "琛屾斂妤� 302")
+    private String location;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemSaveReqVO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemSaveReqVO.java
new file mode 100644
index 0000000..524942f
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/item/vo/AssetItemSaveReqVO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.asset.controller.admin.item.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 璧勪骇鍙拌处鏂板/淇敼 Request VO")
+@Data
+public class AssetItemSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "璧勪骇缂栧彿锛岀暀绌鸿嚜鍔ㄧ敓鎴�", example = "ZC20261020000001")
+    private String assetNo;
+
+    @Schema(description = "璧勪骇鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "婵�鍏夋墦鍗版満")
+    @NotBlank(message = "璧勪骇鍚嶇О涓嶈兘涓虹┖")
+    @Size(max = 128, message = "璧勪骇鍚嶇О涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String name;
+
+    @Schema(description = "璧勪骇鍒嗙被锛�1-鏄撹�楀搧 2-鍔炲叕璧勪骇 3-鍥哄畾璧勪骇", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "璇烽�夋嫨璧勪骇鍒嗙被")
+    private Integer category;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "HP M405dn")
+    @Size(max = 128, message = "瑙勬牸鍨嬪彿涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String spec;
+
+    @Schema(description = "璁¢噺鍗曚綅", example = "鍙�")
+    @Size(max = 16, message = "璁¢噺鍗曚綅涓嶈兘瓒呰繃 16 涓瓧绗�")
+    private String unit;
+
+    @Schema(description = "褰撳墠搴撳瓨鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @NotNull(message = "搴撳瓨鏁伴噺涓嶈兘涓虹┖")
+    @Min(value = 0, message = "搴撳瓨鏁伴噺涓嶈兘灏忎簬 0")
+    private Integer quantity;
+
+    @Schema(description = "鍗曚环锛堝厓锛�", example = "1999.00")
+    private BigDecimal unitPrice;
+
+    @Schema(description = "璐叆鏃ユ湡", example = "2026-10-20")
+    private LocalDate purchaseDate;
+
+    @Schema(description = "淇濈浜猴紙绠$悊浜猴級鐢ㄦ埛 ID", example = "1024")
+    private Long custodianId;
+
+    @Schema(description = "淇濈浜猴紙绠$悊浜猴級濮撳悕", example = "寮犱笁")
+    @Size(max = 64, message = "淇濈浜哄鍚嶄笉鑳借秴杩� 64 涓瓧绗�")
+    private String custodianName;
+
+    @Schema(description = "瀛樻斁鍦扮偣", example = "琛屾斂妤� 302")
+    @Size(max = 128, message = "瀛樻斁鍦扮偣涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String location;
+
+    @Schema(description = "澶囨敞")
+    @Size(max = 512, message = "澶囨敞涓嶈兘瓒呰繃 512 涓瓧绗�")
+    private String remark;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/AssetRequisitionController.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/AssetRequisitionController.java
new file mode 100644
index 0000000..134b78d
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/AssetRequisitionController.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.asset.controller.admin.requisition;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionRespVO;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionReturnReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.requisition.AssetRequisitionDO;
+import cn.iocoder.yudao.module.asset.enums.AssetCategoryEnum;
+import cn.iocoder.yudao.module.asset.enums.AssetReturnStatusEnum;
+import cn.iocoder.yudao.module.asset.service.requisition.AssetRequisitionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 璧勪骇棰嗙敤")
+@RestController
+@RequestMapping("/asset/requisition")
+@Validated
+public class AssetRequisitionController {
+
+    @Resource
+    private AssetRequisitionService requisitionService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璧勪骇棰嗙敤")
+    @PreAuthorize("@ss.hasPermission('asset:requisition:create')")
+    public CommonResult<Long> createRequisition(@Valid @RequestBody AssetRequisitionSaveReqVO reqVO) {
+        return success(requisitionService.createRequisition(reqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊璧勪骇棰嗙敤锛堜粎棰嗙敤浜恒�佺敤閫斻�侀璁″綊杩樻棩鏈熴�佸娉級")
+    @PreAuthorize("@ss.hasPermission('asset:requisition:update')")
+    public CommonResult<Boolean> updateRequisition(@Valid @RequestBody AssetRequisitionSaveReqVO reqVO) {
+        requisitionService.updateRequisition(reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璧勪骇棰嗙敤锛堟湭鍙戠敓褰掕繕鏃跺啿閿�搴撳瓨锛�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('asset:requisition:delete')")
+    public CommonResult<Boolean> deleteRequisition(@RequestParam("id") Long id) {
+        requisitionService.deleteRequisition(id);
+        return success(true);
+    }
+
+    @PutMapping("/return")
+    @Operation(summary = "褰掕繕璧勪骇")
+    @PreAuthorize("@ss.hasPermission('asset:requisition:return')")
+    public CommonResult<Boolean> returnRequisition(@Valid @RequestBody AssetRequisitionReturnReqVO reqVO) {
+        requisitionService.returnRequisition(reqVO);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璧勪骇棰嗙敤")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('asset:requisition:query')")
+    public CommonResult<AssetRequisitionRespVO> getRequisition(@RequestParam("id") Long id) {
+        return success(buildRespVO(requisitionService.getRequisition(id)));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璧勪骇棰嗙敤鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('asset:requisition:query')")
+    public CommonResult<PageResult<AssetRequisitionRespVO>> getRequisitionPage(
+            @Valid AssetRequisitionPageReqVO reqVO) {
+        PageResult<AssetRequisitionDO> pageResult = requisitionService.getRequisitionPage(reqVO);
+        return success(new PageResult<>(buildRespVOList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    private AssetRequisitionRespVO buildRespVO(AssetRequisitionDO requisition) {
+        if (requisition == null) {
+            return null;
+        }
+        return buildRespVOList(java.util.Collections.singletonList(requisition)).get(0);
+    }
+
+    private List<AssetRequisitionRespVO> buildRespVOList(List<AssetRequisitionDO> list) {
+        return BeanUtils.toBean(list, AssetRequisitionRespVO.class, vo -> {
+            vo.setCategoryName(AssetCategoryEnum.nameOf(vo.getCategory()));
+            vo.setReturnStatusName(AssetReturnStatusEnum.nameOf(vo.getReturnStatus()));
+            int quantity = vo.getQuantity() == null ? 0 : vo.getQuantity();
+            int returned = vo.getReturnedQuantity() == null ? 0 : vo.getReturnedQuantity();
+            vo.setRemainingQuantity(Math.max(quantity - returned, 0));
+        });
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionPageReqVO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionPageReqVO.java
new file mode 100644
index 0000000..485958f
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionPageReqVO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.asset.controller.admin.requisition.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - 璧勪骇棰嗙敤鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AssetRequisitionPageReqVO extends PageParam {
+
+    @Schema(description = "棰嗙敤鍗曞彿", example = "LY20261020000001")
+    private String no;
+
+    @Schema(description = "璧勪骇鍚嶇О", example = "鎵撳嵃鏈�")
+    private String assetName;
+
+    @Schema(description = "棰嗙敤浜哄鍚�", example = "鏉庡洓")
+    private String recipientName;
+
+    @Schema(description = "璧勪骇鍒嗙被锛�1-鏄撹�楀搧 2-鍔炲叕璧勪骇 3-鍥哄畾璧勪骇", example = "2")
+    private Integer category;
+
+    @Schema(description = "褰掕繕鐘舵�侊細1-鏈綊杩� 2-閮ㄥ垎褰掕繕 3-宸插綊杩�", example = "1")
+    private Integer returnStatus;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionRespVO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionRespVO.java
new file mode 100644
index 0000000..c280123
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionRespVO.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.asset.controller.admin.requisition.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 璧勪骇棰嗙敤 Response VO")
+@Data
+public class AssetRequisitionRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "棰嗙敤鍗曞彿", example = "LY20261020000001")
+    private String no;
+
+    @Schema(description = "璧勪骇 ID", example = "1")
+    private Long assetId;
+
+    @Schema(description = "璧勪骇缂栧彿", example = "ZC20261020000001")
+    private String assetNo;
+
+    @Schema(description = "璧勪骇鍚嶇О", example = "婵�鍏夋墦鍗版満")
+    private String assetName;
+
+    @Schema(description = "璧勪骇鍒嗙被", example = "2")
+    private Integer category;
+
+    @Schema(description = "璧勪骇鍒嗙被鍚嶇О", example = "鍔炲叕璧勪骇")
+    private String categoryName;
+
+    @Schema(description = "淇濈浜猴紙绠$悊浜猴級鐢ㄦ埛 ID", example = "1024")
+    private Long custodianId;
+
+    @Schema(description = "淇濈浜猴紙绠$悊浜猴級濮撳悕", example = "寮犱笁")
+    private String custodianName;
+
+    @Schema(description = "棰嗙敤浜虹敤鎴� ID", example = "1025")
+    private Long recipientId;
+
+    @Schema(description = "棰嗙敤浜哄鍚�", example = "鏉庡洓")
+    private String recipientName;
+
+    @Schema(description = "棰嗙敤鏃堕棿")
+    private LocalDateTime requisitionTime;
+
+    @Schema(description = "棰嗙敤鏁伴噺", example = "1")
+    private Integer quantity;
+
+    @Schema(description = "鐢ㄩ��", example = "琛屾斂鍔炲叕鎵撳嵃")
+    private String purpose;
+
+    @Schema(description = "棰勮褰掕繕鏃ユ湡")
+    private LocalDate expectedReturnDate;
+
+    @Schema(description = "宸插綊杩樻暟閲�", example = "0")
+    private Integer returnedQuantity;
+
+    @Schema(description = "鏈綊杩樻暟閲�", example = "1")
+    private Integer remainingQuantity;
+
+    @Schema(description = "鏈�杩戝綊杩樻椂闂�")
+    private LocalDateTime returnTime;
+
+    @Schema(description = "褰掕繕鐘舵�侊細1-鏈綊杩� 2-閮ㄥ垎褰掕繕 3-宸插綊杩�", example = "1")
+    private Integer returnStatus;
+
+    @Schema(description = "褰掕繕鐘舵�佸悕绉�", example = "鏈綊杩�")
+    private String returnStatusName;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionReturnReqVO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionReturnReqVO.java
new file mode 100644
index 0000000..491b0d0
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionReturnReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.controller.admin.requisition.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 璧勪骇褰掕繕 Request VO")
+@Data
+public class AssetRequisitionReturnReqVO {
+
+    @Schema(description = "棰嗙敤璁板綍缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "棰嗙敤璁板綍缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "鏈褰掕繕鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "褰掕繕鏁伴噺涓嶈兘涓虹┖")
+    @Min(value = 1, message = "褰掕繕鏁伴噺蹇呴』澶т簬 0")
+    private Integer returnQuantity;
+
+    @Schema(description = "褰掕繕鏃堕棿锛屼笉浼犻粯璁ゅ綋鍓嶆椂闂�")
+    private LocalDateTime returnTime;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionSaveReqVO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionSaveReqVO.java
new file mode 100644
index 0000000..b91bcb6
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/requisition/vo/AssetRequisitionSaveReqVO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.asset.controller.admin.requisition.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 璧勪骇棰嗙敤鏂板/淇敼 Request VO")
+@Data
+public class AssetRequisitionSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "璧勪骇 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "璇烽�夋嫨棰嗙敤璧勪骇")
+    private Long assetId;
+
+    @Schema(description = "棰嗙敤浜虹敤鎴� ID", example = "1024")
+    private Long recipientId;
+
+    @Schema(description = "棰嗙敤浜哄鍚�", requiredMode = Schema.RequiredMode.REQUIRED, example = "鏉庡洓")
+    @Size(max = 64, message = "棰嗙敤浜哄鍚嶄笉鑳借秴杩� 64 涓瓧绗�")
+    private String recipientName;
+
+    @Schema(description = "棰嗙敤鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "棰嗙敤鏃堕棿涓嶈兘涓虹┖")
+    private LocalDateTime requisitionTime;
+
+    @Schema(description = "棰嗙敤鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "棰嗙敤鏁伴噺涓嶈兘涓虹┖")
+    @Min(value = 1, message = "棰嗙敤鏁伴噺蹇呴』澶т簬 0")
+    private Integer quantity;
+
+    @Schema(description = "鐢ㄩ��", example = "琛屾斂鍔炲叕鎵撳嵃")
+    @Size(max = 512, message = "鐢ㄩ�斾笉鑳借秴杩� 512 涓瓧绗�")
+    private String purpose;
+
+    @Schema(description = "棰勮褰掕繕鏃ユ湡", example = "2026-11-20")
+    private LocalDate expectedReturnDate;
+
+    @Schema(description = "澶囨敞")
+    @Size(max = 512, message = "澶囨敞涓嶈兘瓒呰繃 512 涓瓧绗�")
+    private String remark;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/item/AssetItemDO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/item/AssetItemDO.java
new file mode 100644
index 0000000..3632db3
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/item/AssetItemDO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.item;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 璧勪骇鍙拌处 DO
+ */
+@TableName("asset_item")
+@KeySequence("asset_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AssetItemDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /** 璧勪骇缂栧彿 */
+    private String assetNo;
+
+    /** 璧勪骇鍚嶇О */
+    private String name;
+
+    /** 璧勪骇鍒嗙被锛�1-鏄撹�楀搧 2-鍔炲叕璧勪骇 3-鍥哄畾璧勪骇 */
+    private Integer category;
+
+    /** 瑙勬牸鍨嬪彿 */
+    private String spec;
+
+    /** 璁¢噺鍗曚綅 */
+    private String unit;
+
+    /** 褰撳墠搴撳瓨鏁伴噺 */
+    private Integer quantity;
+
+    /** 鍗曚环锛堝厓锛� */
+    private BigDecimal unitPrice;
+
+    /** 鎬讳环鍊硷紙鍏冿級 */
+    private BigDecimal totalAmount;
+
+    /** 璐叆鏃ユ湡 */
+    private LocalDate purchaseDate;
+
+    /** 淇濈浜猴紙绠$悊浜猴級鐢ㄦ埛 ID */
+    private Long custodianId;
+
+    /** 淇濈浜猴紙绠$悊浜猴級濮撳悕 */
+    private String custodianName;
+
+    /** 瀛樻斁鍦扮偣 */
+    private String location;
+
+    /** 澶囨敞 */
+    private String remark;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/requisition/AssetRequisitionDO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/requisition/AssetRequisitionDO.java
new file mode 100644
index 0000000..7a83486
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/requisition/AssetRequisitionDO.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.requisition;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 璧勪骇棰嗙敤 DO
+ */
+@TableName("asset_requisition")
+@KeySequence("asset_requisition_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AssetRequisitionDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /** 棰嗙敤鍗曞彿 */
+    private String no;
+
+    /** 璧勪骇 ID */
+    private Long assetId;
+
+    /** 璧勪骇缂栧彿锛堝揩鐓э級 */
+    private String assetNo;
+
+    /** 璧勪骇鍚嶇О锛堝揩鐓э級 */
+    private String assetName;
+
+    /** 璧勪骇鍒嗙被锛堝揩鐓э級 */
+    private Integer category;
+
+    /** 淇濈浜猴紙绠$悊浜猴級鐢ㄦ埛 ID */
+    private Long custodianId;
+
+    /** 淇濈浜猴紙绠$悊浜猴級濮撳悕 */
+    private String custodianName;
+
+    /** 棰嗙敤浜虹敤鎴� ID */
+    private Long recipientId;
+
+    /** 棰嗙敤浜哄鍚� */
+    private String recipientName;
+
+    /** 棰嗙敤鏃堕棿 */
+    private LocalDateTime requisitionTime;
+
+    /** 棰嗙敤鏁伴噺 */
+    private Integer quantity;
+
+    /** 鐢ㄩ�� */
+    private String purpose;
+
+    /** 棰勮褰掕繕鏃ユ湡 */
+    private LocalDate expectedReturnDate;
+
+    /** 宸插綊杩樻暟閲� */
+    private Integer returnedQuantity;
+
+    /** 鏈�杩戝綊杩樻椂闂� */
+    private LocalDateTime returnTime;
+
+    /** 褰掕繕鐘舵�侊細1-鏈綊杩� 2-閮ㄥ垎褰掕繕 3-宸插綊杩� */
+    private Integer returnStatus;
+
+    /** 澶囨敞 */
+    private String remark;
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/item/AssetItemMapper.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/item/AssetItemMapper.java
new file mode 100644
index 0000000..ce98daf
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/item/AssetItemMapper.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.item;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemPageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.item.AssetItemDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
+
+@Mapper
+public interface AssetItemMapper extends BaseMapperX<AssetItemDO> {
+
+    @Update("UPDATE asset_item SET quantity = quantity - #{quantity}, update_time = NOW() "
+            + "WHERE id = #{id} AND deleted = 0 AND quantity >= #{quantity}")
+    int decreaseStock(@Param("id") Long id, @Param("quantity") Integer quantity);
+
+    @Update("UPDATE asset_item SET quantity = quantity + #{quantity}, update_time = NOW() "
+            + "WHERE id = #{id} AND deleted = 0")
+    int increaseStock(@Param("id") Long id, @Param("quantity") Integer quantity);
+
+    default AssetItemDO selectByAssetNo(String assetNo) {
+        return selectOne(AssetItemDO::getAssetNo, assetNo);
+    }
+
+    default PageResult<AssetItemDO> selectPage(AssetItemPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AssetItemDO>()
+                .likeIfPresent(AssetItemDO::getAssetNo, reqVO.getAssetNo())
+                .likeIfPresent(AssetItemDO::getName, reqVO.getName())
+                .eqIfPresent(AssetItemDO::getCategory, reqVO.getCategory())
+                .likeIfPresent(AssetItemDO::getCustodianName, reqVO.getCustodianName())
+                .orderByDesc(AssetItemDO::getId));
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/requisition/AssetRequisitionMapper.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/requisition/AssetRequisitionMapper.java
new file mode 100644
index 0000000..33f535b
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/requisition/AssetRequisitionMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.requisition;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionPageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.requisition.AssetRequisitionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AssetRequisitionMapper extends BaseMapperX<AssetRequisitionDO> {
+
+    default PageResult<AssetRequisitionDO> selectPage(AssetRequisitionPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AssetRequisitionDO>()
+                .likeIfPresent(AssetRequisitionDO::getNo, reqVO.getNo())
+                .likeIfPresent(AssetRequisitionDO::getAssetName, reqVO.getAssetName())
+                .likeIfPresent(AssetRequisitionDO::getRecipientName, reqVO.getRecipientName())
+                .eqIfPresent(AssetRequisitionDO::getCategory, reqVO.getCategory())
+                .eqIfPresent(AssetRequisitionDO::getReturnStatus, reqVO.getReturnStatus())
+                .orderByDesc(AssetRequisitionDO::getId));
+    }
+
+    default Long selectCountByAssetIdAndNotReturned(Long assetId) {
+        return selectCount(new LambdaQueryWrapperX<AssetRequisitionDO>()
+                .eq(AssetRequisitionDO::getAssetId, assetId)
+                .ne(AssetRequisitionDO::getReturnStatus, 3));
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/AssetNoRedisDAO.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/AssetNoRedisDAO.java
new file mode 100644
index 0000000..905318a
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/AssetNoRedisDAO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.asset.dal.redis;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+/**
+ * Asset 缂栧彿 Redis DAO
+ */
+@Repository
+public class AssetNoRedisDAO {
+
+    /** 璧勪骇缂栧彿鍓嶇紑 */
+    public static final String ASSET_ITEM_NO_PREFIX = "ZC";
+
+    /** 棰嗙敤鍗曞彿鍓嶇紑 */
+    public static final String ASSET_REQUISITION_NO_PREFIX = "LY";
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    public String generate(String prefix) {
+        String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
+        String key = RedisKeyConstants.NO + noPrefix;
+        Long no = stringRedisTemplate.opsForValue().increment(key);
+        stringRedisTemplate.expire(key, Duration.ofDays(1L));
+        return noPrefix + String.format("%06d", no);
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/RedisKeyConstants.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/RedisKeyConstants.java
new file mode 100644
index 0000000..6d6ea18
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/dal/redis/RedisKeyConstants.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.asset.dal.redis;
+
+/**
+ * Asset Redis Key 鏋氫妇绫�
+ */
+public interface RedisKeyConstants {
+
+    /**
+     * 鍗曞彿鑷搴忓垪
+     * <p>
+     * KEY 鏍煎紡锛歛sset:no:{prefix}{yyyyMMdd}
+     * VALUE 鏍煎紡锛欼nteger
+     */
+    String NO = "asset:no:";
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetCategoryEnum.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetCategoryEnum.java
new file mode 100644
index 0000000..834988f
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetCategoryEnum.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 璧勪骇鍒嗙被鏋氫妇
+ */
+@Getter
+@AllArgsConstructor
+public enum AssetCategoryEnum {
+
+    CONSUMABLE(1, "鏄撹�楀搧"),
+    OFFICE(2, "鍔炲叕璧勪骇"),
+    FIXED(3, "鍥哄畾璧勪骇");
+
+    private final Integer category;
+    private final String name;
+
+    public static String nameOf(Integer category) {
+        for (AssetCategoryEnum value : values()) {
+            if (value.category.equals(category)) {
+                return value.name;
+            }
+        }
+        return "鏈煡";
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetReturnStatusEnum.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetReturnStatusEnum.java
new file mode 100644
index 0000000..1404053
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/AssetReturnStatusEnum.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 璧勪骇棰嗙敤褰掕繕鐘舵�佹灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum AssetReturnStatusEnum {
+
+    NOT_RETURNED(1, "鏈綊杩�"),
+    PARTIAL(2, "閮ㄥ垎褰掕繕"),
+    RETURNED(3, "宸插綊杩�");
+
+    private final Integer status;
+    private final String name;
+
+    public static String nameOf(Integer status) {
+        for (AssetReturnStatusEnum value : values()) {
+            if (value.status.equals(status)) {
+                return value.name;
+            }
+        }
+        return "鏈煡";
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java
new file mode 100644
index 0000000..e17dea8
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.asset.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Asset 閿欒鐮佹灇涓剧被
+ * <p>
+ * asset 绯荤粺锛屼娇鐢� 1-030-109-000 娈碉紙鎺ュ湪 ums 1-030-108 涔嬪悗锛�
+ */
+public interface ErrorCodeConstants {
+
+    // ========== 璧勪骇鍙拌处锛�1-030-109-000 ~ 009锛� ==========
+    ErrorCode ASSET_ITEM_NOT_EXISTS = new ErrorCode(1_030_109_000, "璧勪骇涓嶅瓨鍦紝璇峰埛鏂板垪琛ㄥ悗閲嶆柊閫夋嫨");
+    ErrorCode ASSET_ITEM_STOCK_NOT_ENOUGH = new ErrorCode(1_030_109_001, "璧勪骇銆寋}銆嶅簱瀛樹笉瓒筹細褰撳墠鍙鐢� {}锛屾湰娆$敵璇� {}锛岃鍑忓皯棰嗙敤鏁伴噺鎴栧厛琛ュ厖鍏ュ簱");
+    ErrorCode ASSET_ITEM_HAS_ACTIVE_REQUISITION = new ErrorCode(1_030_109_002, "璧勪骇銆寋}銆嶄粛鏈� {} 鏉℃湭褰掕繕鐨勯鐢ㄨ褰曪紝璇峰厛澶勭悊褰掕繕鍚庡啀鍒犻櫎");
+    ErrorCode ASSET_ITEM_QUANTITY_INVALID = new ErrorCode(1_030_109_003, "璧勪骇鏁伴噺蹇呴』澶т簬 0锛岃淇鍚庡啀淇濆瓨");
+    ErrorCode ASSET_ITEM_NO_EXISTS = new ErrorCode(1_030_109_004, "璧勪骇缂栧彿銆寋}銆嶅凡瀛樺湪锛岃鏇存崲缂栧彿鍚庨噸鏂颁繚瀛�");
+
+    // ========== 璧勪骇棰嗙敤锛�1-030-109-100 ~ 109锛� ==========
+    ErrorCode ASSET_REQUISITION_NOT_EXISTS = new ErrorCode(1_030_109_100, "璧勪骇棰嗙敤璁板綍涓嶅瓨鍦紝璇峰埛鏂板垪琛ㄥ悗閲嶈瘯");
+    ErrorCode ASSET_REQUISITION_RETURN_QUANTITY_INVALID = new ErrorCode(1_030_109_101, "褰掕繕鏁伴噺涓嶆纭細棰嗙敤 {}锛屽凡褰掕繕 {}锛屾湰娆″綊杩� {}锛屾湰娆″綊杩樻暟閲忎笉鑳借秴杩囧墿浣欐湭褰掕繕鏁伴噺 {}");
+    ErrorCode ASSET_REQUISITION_DELETE_FAIL_RETURNED = new ErrorCode(1_030_109_102, "璇ラ鐢ㄨ褰曞凡鍙戠敓褰掕繕锛屼笉鑳藉垹闄わ紱濡傞渶鍐查攢璇峰厛瀹屾垚鍏ㄩ儴褰掕繕");
+    ErrorCode ASSET_REQUISITION_RETURN_COMPLETED = new ErrorCode(1_030_109_103, "璇ラ鐢ㄨ褰曞凡鍏ㄩ儴褰掕繕锛屾棤闇�閲嶅褰掕繕");
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemService.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemService.java
new file mode 100644
index 0000000..31c7e11
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemService.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.asset.service.item;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.item.AssetItemDO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 璧勪骇鍙拌处 Service 鎺ュ彛
+ */
+public interface AssetItemService {
+
+    Long createItem(AssetItemSaveReqVO reqVO);
+
+    void updateItem(AssetItemSaveReqVO reqVO);
+
+    void deleteItem(Long id);
+
+    AssetItemDO getItem(Long id);
+
+    List<AssetItemDO> getItemList(Collection<Long> ids);
+
+    List<AssetItemDO> getItemSimpleList();
+
+    PageResult<AssetItemDO> getItemPage(AssetItemPageReqVO reqVO);
+
+    /** 鎵e噺搴撳瓨锛堥鐢級锛屽簱瀛樹笉瓒虫椂鎶涘嚭涓氬姟寮傚父 */
+    void decreaseStock(Long id, Integer quantity);
+
+    /** 澧炲姞搴撳瓨锛堝綊杩�/鍐查攢锛� */
+    void increaseStock(Long id, Integer quantity);
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemServiceImpl.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemServiceImpl.java
new file mode 100644
index 0000000..34ccee3
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/item/AssetItemServiceImpl.java
@@ -0,0 +1,154 @@
+package cn.iocoder.yudao.module.asset.service.item;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.item.vo.AssetItemSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.item.AssetItemDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.item.AssetItemMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.requisition.AssetRequisitionMapper;
+import cn.iocoder.yudao.module.asset.dal.redis.AssetNoRedisDAO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.asset.enums.ErrorCodeConstants.*;
+
+/**
+ * 璧勪骇鍙拌处 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class AssetItemServiceImpl implements AssetItemService {
+
+    @Resource
+    private AssetItemMapper itemMapper;
+
+    @Resource
+    private AssetRequisitionMapper requisitionMapper;
+
+    @Resource
+    private AssetNoRedisDAO noRedisDAO;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createItem(AssetItemSaveReqVO reqVO) {
+        String assetNo = StrUtil.trimToNull(reqVO.getAssetNo());
+        if (assetNo == null) {
+            assetNo = noRedisDAO.generate(AssetNoRedisDAO.ASSET_ITEM_NO_PREFIX);
+        } else if (itemMapper.selectByAssetNo(assetNo) != null) {
+            throw exception(ASSET_ITEM_NO_EXISTS, assetNo);
+        }
+        final String finalAssetNo = assetNo;
+        AssetItemDO item = BeanUtils.toBean(reqVO, AssetItemDO.class, in -> in
+                .setAssetNo(finalAssetNo)
+                .setUnit(StrUtil.blankToDefault(reqVO.getUnit(), "涓�"))
+                .setQuantity(reqVO.getQuantity() == null ? 0 : reqVO.getQuantity())
+                .setTotalAmount(calcTotalAmount(reqVO.getUnitPrice(), reqVO.getQuantity())));
+        itemMapper.insert(item);
+        return item.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateItem(AssetItemSaveReqVO reqVO) {
+        AssetItemDO old = validateItemExists(reqVO.getId());
+        String assetNo = StrUtil.trimToNull(reqVO.getAssetNo());
+        if (assetNo != null && !assetNo.equals(old.getAssetNo())) {
+            AssetItemDO exists = itemMapper.selectByAssetNo(assetNo);
+            if (exists != null && !exists.getId().equals(reqVO.getId())) {
+                throw exception(ASSET_ITEM_NO_EXISTS, assetNo);
+            }
+        }
+        AssetItemDO updateObj = BeanUtils.toBean(reqVO, AssetItemDO.class, in -> in
+                .setAssetNo(assetNo)
+                .setUnit(StrUtil.blankToDefault(reqVO.getUnit(), "涓�"))
+                .setTotalAmount(calcTotalAmount(reqVO.getUnitPrice(), reqVO.getQuantity())));
+        itemMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteItem(Long id) {
+        AssetItemDO item = validateItemExists(id);
+        Long activeCount = requisitionMapper.selectCountByAssetIdAndNotReturned(id);
+        if (activeCount != null && activeCount > 0) {
+            throw exception(ASSET_ITEM_HAS_ACTIVE_REQUISITION, item.getName(), activeCount);
+        }
+        itemMapper.deleteById(id);
+    }
+
+    @Override
+    public AssetItemDO getItem(Long id) {
+        return itemMapper.selectById(id);
+    }
+
+    @Override
+    public List<AssetItemDO> getItemList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return itemMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public List<AssetItemDO> getItemSimpleList() {
+        return itemMapper.selectList();
+    }
+
+    @Override
+    public PageResult<AssetItemDO> getItemPage(AssetItemPageReqVO reqVO) {
+        return itemMapper.selectPage(reqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void decreaseStock(Long id, Integer quantity) {
+        if (quantity == null || quantity <= 0) {
+            throw exception(ASSET_ITEM_QUANTITY_INVALID);
+        }
+        AssetItemDO item = validateItemExists(id);
+        int rows = itemMapper.decreaseStock(id, quantity);
+        if (rows == 0) {
+            throw exception(ASSET_ITEM_STOCK_NOT_ENOUGH, item.getName(),
+                    item.getQuantity() == null ? 0 : item.getQuantity(), quantity);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void increaseStock(Long id, Integer quantity) {
+        if (quantity == null || quantity <= 0) {
+            throw exception(ASSET_ITEM_QUANTITY_INVALID);
+        }
+        validateItemExists(id);
+        itemMapper.increaseStock(id, quantity);
+    }
+
+    private AssetItemDO validateItemExists(Long id) {
+        AssetItemDO item = id != null ? itemMapper.selectById(id) : null;
+        if (item == null) {
+            throw exception(ASSET_ITEM_NOT_EXISTS);
+        }
+        return item;
+    }
+
+    private BigDecimal calcTotalAmount(BigDecimal unitPrice, Integer quantity) {
+        if (unitPrice == null || quantity == null) {
+            return BigDecimal.ZERO;
+        }
+        return unitPrice.multiply(BigDecimal.valueOf(quantity));
+    }
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionService.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionService.java
new file mode 100644
index 0000000..2a4baba
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionService.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.asset.service.requisition;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionReturnReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.requisition.AssetRequisitionDO;
+
+/**
+ * 璧勪骇棰嗙敤 Service 鎺ュ彛
+ */
+public interface AssetRequisitionService {
+
+    Long createRequisition(AssetRequisitionSaveReqVO reqVO);
+
+    void updateRequisition(AssetRequisitionSaveReqVO reqVO);
+
+    void deleteRequisition(Long id);
+
+    /** 褰掕繕璧勪骇 */
+    void returnRequisition(AssetRequisitionReturnReqVO reqVO);
+
+    AssetRequisitionDO getRequisition(Long id);
+
+    PageResult<AssetRequisitionDO> getRequisitionPage(AssetRequisitionPageReqVO reqVO);
+
+}
diff --git a/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionServiceImpl.java b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionServiceImpl.java
new file mode 100644
index 0000000..03dc0de
--- /dev/null
+++ b/yudao-module-asset/src/main/java/cn/iocoder/yudao/module/asset/service/requisition/AssetRequisitionServiceImpl.java
@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.module.asset.service.requisition;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionReturnReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.requisition.vo.AssetRequisitionSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.item.AssetItemDO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.requisition.AssetRequisitionDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.requisition.AssetRequisitionMapper;
+import cn.iocoder.yudao.module.asset.dal.redis.AssetNoRedisDAO;
+import cn.iocoder.yudao.module.asset.enums.AssetReturnStatusEnum;
+import cn.iocoder.yudao.module.asset.service.item.AssetItemService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.asset.enums.ErrorCodeConstants.*;
+
+/**
+ * 璧勪骇棰嗙敤 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class AssetRequisitionServiceImpl implements AssetRequisitionService {
+
+    @Resource
+    private AssetRequisitionMapper requisitionMapper;
+
+    @Resource
+    private AssetItemService itemService;
+
+    @Resource
+    private AssetNoRedisDAO noRedisDAO;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createRequisition(AssetRequisitionSaveReqVO reqVO) {
+        AssetItemDO item = itemService.getItem(reqVO.getAssetId());
+        if (item == null) {
+            throw exception(ASSET_ITEM_NOT_EXISTS);
+        }
+        // 鍏堟墸搴撳瓨锛屽簱瀛樹笉瓒充細鎶涘嚭绮剧‘鎻愮ず
+        itemService.decreaseStock(item.getId(), reqVO.getQuantity());
+
+        AssetRequisitionDO requisition = BeanUtils.toBean(reqVO, AssetRequisitionDO.class, in -> in
+                .setNo(noRedisDAO.generate(AssetNoRedisDAO.ASSET_REQUISITION_NO_PREFIX))
+                .setAssetNo(item.getAssetNo())
+                .setAssetName(item.getName())
+                .setCategory(item.getCategory())
+                .setCustodianId(item.getCustodianId())
+                .setCustodianName(item.getCustodianName())
+                .setReturnedQuantity(0)
+                .setReturnStatus(AssetReturnStatusEnum.NOT_RETURNED.getStatus()));
+        requisitionMapper.insert(requisition);
+        return requisition.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateRequisition(AssetRequisitionSaveReqVO reqVO) {
+        AssetRequisitionDO old = validateRequisitionExists(reqVO.getId());
+        // 淇敼浠呭厑璁歌皟鏁撮鐢ㄤ汉銆佺敤閫斻�侀璁″綊杩樻棩鏈熶笌澶囨敞锛涜祫浜т笌鏁伴噺涓嶅厑璁哥洿鎺ユ敼锛岄伩鍏嶅簱瀛樿处瀹炰笉绗�
+        AssetRequisitionDO updateObj = new AssetRequisitionDO()
+                .setId(old.getId())
+                .setRecipientId(reqVO.getRecipientId())
+                .setRecipientName(reqVO.getRecipientName())
+                .setPurpose(reqVO.getPurpose())
+                .setExpectedReturnDate(reqVO.getExpectedReturnDate())
+                .setRemark(reqVO.getRemark());
+        requisitionMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteRequisition(Long id) {
+        AssetRequisitionDO requisition = validateRequisitionExists(id);
+        int returned = requisition.getReturnedQuantity() == null ? 0 : requisition.getReturnedQuantity();
+        if (returned > 0) {
+            throw exception(ASSET_REQUISITION_DELETE_FAIL_RETURNED);
+        }
+        // 鍐查攢锛氭妸鏈綊杩樻暟閲忛��鍥炲簱瀛�
+        itemService.increaseStock(requisition.getAssetId(), requisition.getQuantity());
+        requisitionMapper.deleteById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void returnRequisition(AssetRequisitionReturnReqVO reqVO) {
+        AssetRequisitionDO requisition = validateRequisitionExists(reqVO.getId());
+        int total = requisition.getQuantity() == null ? 0 : requisition.getQuantity();
+        int returned = requisition.getReturnedQuantity() == null ? 0 : requisition.getReturnedQuantity();
+        int remaining = total - returned;
+        if (remaining <= 0) {
+            throw exception(ASSET_REQUISITION_RETURN_COMPLETED);
+        }
+        if (reqVO.getReturnQuantity() == null || reqVO.getReturnQuantity() <= 0
+                || reqVO.getReturnQuantity() > remaining) {
+            throw exception(ASSET_REQUISITION_RETURN_QUANTITY_INVALID,
+                    total, returned, reqVO.getReturnQuantity(), remaining);
+        }
+
+        int newReturned = returned + reqVO.getReturnQuantity();
+        int newStatus = newReturned >= total
+                ? AssetReturnStatusEnum.RETURNED.getStatus()
+                : AssetReturnStatusEnum.PARTIAL.getStatus();
+        requisitionMapper.updateById(new AssetRequisitionDO()
+                .setId(requisition.getId())
+                .setReturnedQuantity(newReturned)
+                .setReturnTime(reqVO.getReturnTime() != null ? reqVO.getReturnTime() : LocalDateTime.now())
+                .setReturnStatus(newStatus)
+                .setRemark(StrUtil.blankToDefault(reqVO.getRemark(), requisition.getRemark())));
+        itemService.increaseStock(requisition.getAssetId(), reqVO.getReturnQuantity());
+    }
+
+    @Override
+    public AssetRequisitionDO getRequisition(Long id) {
+        return requisitionMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<AssetRequisitionDO> getRequisitionPage(AssetRequisitionPageReqVO reqVO) {
+        return requisitionMapper.selectPage(reqVO);
+    }
+
+    private AssetRequisitionDO validateRequisitionExists(Long id) {
+        AssetRequisitionDO requisition = id != null ? requisitionMapper.selectById(id) : null;
+        if (requisition == null) {
+            throw exception(ASSET_REQUISITION_NOT_EXISTS);
+        }
+        return requisition;
+    }
+
+}
diff --git a/yudao-module-finance/pom.xml b/yudao-module-finance/pom.xml
index 956b000..0afb6ad 100644
--- a/yudao-module-finance/pom.xml
+++ b/yudao-module-finance/pom.xml
@@ -43,6 +43,13 @@
             <version>${revision}</version>
         </dependency>
 
+        <!-- AI 澶фā鍨� API锛堟姤閿�鍙戠エ OCR 璇嗗埆锛� -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-ai-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- Web 鐩稿叧 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
@@ -76,6 +83,13 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-test</artifactId>
         </dependency>
+
+        <!-- PDF 瑙f瀽 -->
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>3.0.4</version>
+        </dependency>
     </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApiImpl.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApiImpl.java
index 3374c68..95725e3 100644
--- a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApiImpl.java
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApiImpl.java
@@ -53,7 +53,7 @@
         String processDefinitionKey = resolveApproveProcessDefinitionKey();
 
         // 2. 鍒涘缓鎶ラ攢鍗曪紙1 琛屾槑缁嗭級
-        FinanceReimbursementSaveReqVO createReqVO = buildSaveReqVO(null, reqDTO.getTitle(),
+        FinanceReimbursementSaveReqVO createReqVO = buildSaveReqVO(null, reqDTO.getApplicantId(), reqDTO.getTitle(),
                 reqDTO.getTotalAmount(), reqDTO.getApplyDate(), reqDTO.getRemark(),
                 reqDTO.getBlobIds(), reqDTO.getExpenseDescription());
         Long reimbursementId = reimbursementService.createReimbursement(createReqVO);
@@ -72,7 +72,7 @@
         String processDefinitionKey = resolveApproveProcessDefinitionKey();
 
         // 2. 淇敼鎶ラ攢鍗曪紙鏄庣粏閲嶅缓涓� 1 琛岋級
-        FinanceReimbursementSaveReqVO updateReqVO = buildSaveReqVO(reqDTO.getReimbursementId(),
+        FinanceReimbursementSaveReqVO updateReqVO = buildSaveReqVO(reqDTO.getReimbursementId(), null,
                 reqDTO.getTitle(), reqDTO.getTotalAmount(), reqDTO.getApplyDate(), reqDTO.getRemark(),
                 reqDTO.getBlobIds(), reqDTO.getExpenseDescription());
         reimbursementService.updateReimbursement(updateReqVO);
@@ -110,7 +110,7 @@
     }
 
     /** 缁勮鎶ラ攢鍗曚繚瀛� VO锛氭槑缁嗗浐瀹� 1 琛岋紝璐圭敤绫诲瀷銆屽叾浠栥�嶏紝鍙戠エ閲戦=鎶ラ攢鎬婚噾棰� */
-    private FinanceReimbursementSaveReqVO buildSaveReqVO(Long id, String title, BigDecimal totalAmount,
+    private FinanceReimbursementSaveReqVO buildSaveReqVO(Long id, Long applicantId, String title, BigDecimal totalAmount,
                                                          LocalDate applyDate, String remark, List<Long> blobIds,
                                                          String expenseDescription) {
         FinanceReimbursementItemSaveReqVO item = new FinanceReimbursementItemSaveReqVO();
@@ -121,6 +121,7 @@
 
         FinanceReimbursementSaveReqVO reqVO = new FinanceReimbursementSaveReqVO();
         reqVO.setId(id);
+        reqVO.setApplicantId(applicantId);
         reqVO.setTitle(title);
         reqVO.setTotalAmount(totalAmount);
         reqVO.setApplyDate(applyDate != null ? applyDate : LocalDate.now());
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/FinanceReimbursementAiController.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/FinanceReimbursementAiController.java
new file mode 100644
index 0000000..07543a3
--- /dev/null
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/FinanceReimbursementAiController.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo.FinanceReimbursementOcrReqVO;
+import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo.FinanceReimbursementOcrRespVO;
+import cn.iocoder.yudao.module.finance.service.reimbursement.ai.FinanceReimbursementAiService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 璐㈠姟鎶ラ攢鍗� AI OCR")
+@RestController
+@RequestMapping("/finance/reimbursement/ai")
+@Validated
+public class FinanceReimbursementAiController {
+
+    @Resource
+    private FinanceReimbursementAiService reimbursementAiService;
+
+    @PostMapping("/ocr")
+    @Operation(summary = "AI OCR 鎵归噺璇嗗埆鎶ラ攢鍙戠エ鏂囦欢")
+    @PreAuthorize("@ss.hasPermission('finance:reimbursement:query')")
+    public CommonResult<List<FinanceReimbursementOcrRespVO>> ocrInvoices(
+            @Valid @RequestBody FinanceReimbursementOcrReqVO reqVO) {
+        return success(reimbursementAiService.ocrInvoices(reqVO));
+    }
+
+}
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrReqVO.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrReqVO.java
new file mode 100644
index 0000000..ea8a6df
--- /dev/null
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - 鎶ラ攢鍗曞彂绁� OCR 璇嗗埆 Request VO")
+@Data
+public class FinanceReimbursementOcrReqVO {
+
+    @Schema(description = "鍙戠エ鏂囦欢 Blob 缂栧彿鍒楄〃", requiredMode = Schema.RequiredMode.REQUIRED,
+            example = "[1024, 1025]")
+    @NotEmpty(message = "璇峰厛涓婁紶鍙戠エ鏂囦欢")
+    @Size(max = 20, message = "鍗曟鏈�澶氳瘑鍒� 20 寮犲彂绁�")
+    private List<Long> blobIds;
+
+}
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrRespVO.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrRespVO.java
new file mode 100644
index 0000000..e3e0dee
--- /dev/null
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/ai/vo/FinanceReimbursementOcrRespVO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鎶ラ攢鍗曞彂绁� OCR 璇嗗埆 Response VO")
+@Data
+public class FinanceReimbursementOcrRespVO {
+
+    @Schema(description = "鍙戠エ鏂囦欢 Blob 缂栧彿", example = "1024")
+    private Long blobId;
+
+    @Schema(description = "鍙戠エ鍙风爜", example = "02564178")
+    private String invoiceNo;
+
+    @Schema(description = "鍙戠エ浠g爜锛堟暟鐢电エ鍙兘涓虹┖锛�", example = "044001900111")
+    private String invoiceCode;
+
+    @Schema(description = "鍙戠エ绫诲瀷", example = "澧炲�肩◣鐢靛瓙鏅�氬彂绁�")
+    private String invoiceType;
+
+    @Schema(description = "璐拱鏂瑰悕绉帮紙鍙戠エ鎶ご锛�", example = "姹熻嫃鍟嗚锤鑱屼笟瀛﹂櫌")
+    private String invoiceTitle;
+
+    @Schema(description = "閿�鍞柟鍚嶇О锛堝紑绁ㄦ柟锛�", example = "XX绉戞妧鏈夐檺鍏徃")
+    private String sellerName;
+
+    @Schema(description = "浠风◣鍚堣閲戦锛屽崟浣嶏細鍏�", example = "500.00")
+    private BigDecimal invoiceAmount;
+
+    @Schema(description = "绋庨鍚堣锛屽崟浣嶏細鍏�", example = "57.52")
+    private BigDecimal taxAmount;
+
+    @Schema(description = "寮�绁ㄦ棩鏈�", example = "2026-08-01")
+    private LocalDate invoiceDate;
+
+    @Schema(description = "澶囨敞", example = "鍚◣鍚繍璐�")
+    private String remark;
+
+    @Schema(description = "璇嗗埆澶辫触鏃剁殑閿欒鎻愮ず锛堟垚鍔熸椂涓虹┖锛�")
+    private String rawText;
+
+}
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/FinanceReimbursementSaveReqVO.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/FinanceReimbursementSaveReqVO.java
index d6ec21a..a53256f 100644
--- a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/FinanceReimbursementSaveReqVO.java
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/FinanceReimbursementSaveReqVO.java
@@ -18,6 +18,10 @@
     @Schema(description = "缂栧彿", example = "1")
     private Long id;
 
+    @Schema(description = "鐢宠浜虹紪鍙枫�備唬浠栦汉鍙戣捣鏃朵紶瀹為檯鐢宠浜猴紱涓嶄紶鏃堕粯璁ゅ綋鍓嶇櫥褰曠敤鎴�",
+            example = "1024")
+    private Long applicantId;
+
     @Schema(description = "鎶ラ攢鍗曞彿銆傜暀绌猴紙鍚彧濉┖鏍硷級鍒欑敱绯荤粺鎸� BX+鏃ユ湡+6浣嶆祦姘磋嚜鍔ㄧ敓鎴愶紱"
             + "淇敼鏃朵笉浼犳垨鐣欑┖琛ㄧず淇濇寔鍘熷崟鍙蜂笉鍙�", example = "BX20260928000001")
     @Size(max = 36, message = "鎶ラ攢鍗曞彿闀垮害涓嶈兘瓒呰繃 36 涓瓧绗�")
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemRespVO.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemRespVO.java
index c77d2f0..9cdf583 100644
--- a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemRespVO.java
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemRespVO.java
@@ -19,6 +19,9 @@
     @Schema(description = "璐圭敤绫诲瀷", example = "1")
     private Integer expenseType;
 
+    @Schema(description = "鍙戠エ绫诲瀷", example = "澧炲�肩◣涓撶敤鍙戠エ")
+    private String invoiceType;
+
     @Schema(description = "鍙戠エ鍙风爜", example = "12345678")
     private String invoiceNo;
 
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemSaveReqVO.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemSaveReqVO.java
index bae2764..b44e315 100644
--- a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemSaveReqVO.java
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/item/FinanceReimbursementItemSaveReqVO.java
@@ -2,6 +2,7 @@
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -18,6 +19,10 @@
     @NotNull(message = "璐圭敤绫诲瀷涓嶈兘涓虹┖")
     private Integer expenseType;
 
+    @Schema(description = "鍙戠エ绫诲瀷", example = "澧炲�肩◣涓撶敤鍙戠エ")
+    @Size(max = 32, message = "鍙戠エ绫诲瀷闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�")
+    private String invoiceType;
+
     @Schema(description = "鍙戠エ鍙风爜", example = "12345678")
     private String invoiceNo;
 
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/dal/dataobject/reimbursement/FinanceReimbursementItemDO.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/dal/dataobject/reimbursement/FinanceReimbursementItemDO.java
index 8c93b02..c8291b5 100644
--- a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/dal/dataobject/reimbursement/FinanceReimbursementItemDO.java
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/dal/dataobject/reimbursement/FinanceReimbursementItemDO.java
@@ -33,6 +33,9 @@
     /** 璐圭敤绫诲瀷锛�1-宸梾 2-鍔炲叕 3-鎷涘緟 4-浜ら�� 5-鍏朵粬 */
     private Integer expenseType;
 
+    /** 鍙戠エ绫诲瀷锛氬鍊肩◣涓撶敤鍙戠エ / 澧炲�肩◣鏅�氬彂绁� / 鐢靛瓙鍙戠エ / 鏀舵嵁 / 鍏朵粬 */
+    private String invoiceType;
+
     /** 鍙戠エ鍙风爜 */
     private String invoiceNo;
 
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java
index 6039626..d83aa72 100644
--- a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java
@@ -23,5 +23,6 @@
     ErrorCode REIMBURSEMENT_CUSTOM_NO_EXISTS = new ErrorCode(1_030_107_010, "鎶ラ攢鍗曞彿({})宸茶鍏朵粬鎶ラ攢鍗曚娇鐢紝璇锋洿鎹㈠叾浠栧崟鍙凤紝鎴栨竻绌鸿瀛楁鐢辩郴缁熻嚜鍔ㄧ敓鎴�");
     ErrorCode REIMBURSEMENT_CUSTOM_NO_INVALID = new ErrorCode(1_030_107_011, "鎶ラ攢鍗曞彿({})涓棿涓嶈兘鍖呭惈绌烘牸锛岃鍘绘帀绌烘牸鍚庨噸鏂拌緭鍏ワ紝鎴栨竻绌鸿瀛楁鐢辩郴缁熻嚜鍔ㄧ敓鎴�");
     ErrorCode REIMBURSEMENT_ALREADY_ARCHIVED = new ErrorCode(1_030_107_012, "閲嶅褰掓。锛氭姤閿�鍗�({})宸蹭簬姝ゅ墠褰掓。骞剁敓鎴愭。妗�(缂栧彿={})锛岃鍕块噸澶嶅綊妗o紝濡傞渶鏌ョ湅璇峰墠寰�妗f绠$悊妯″潡");
+    ErrorCode REIMBURSEMENT_APPLICANT_NOT_EXISTS = new ErrorCode(1_030_107_013, "鐢宠浜�(ID={})涓嶅瓨鍦ㄦ垨宸茶绂佺敤锛岃閲嶆柊閫夋嫨瀹為檯鐢宠浜�");
 
 }
\ No newline at end of file
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementServiceImpl.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementServiceImpl.java
index 60e4b10..445f470 100644
--- a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementServiceImpl.java
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementServiceImpl.java
@@ -2,6 +2,7 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.archive.api.document.ArchiveDocumentApi;
@@ -24,6 +25,8 @@
 import cn.iocoder.yudao.module.finance.enums.FinanceReimbursementStatusEnum;
 import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.flowable.engine.repository.ProcessDefinition;
@@ -82,6 +85,9 @@
     private StorageAttachmentApi storageAttachmentApi;
 
     @Resource
+    private AdminUserApi adminUserApi;
+
+    @Resource
     private ArchiveDocumentApi archiveDocumentApi;
 
     @Resource
@@ -97,21 +103,26 @@
         // 1. 鎶ラ攢鍗曞彿锛氬厑璁哥敤鎴疯嚜瀹氫箟锛屾湭濉椂鐢辩郴缁熺敓鎴�
         String no = resolveCreateNo(createReqVO.getNo());
 
-        // 2.1 鎻掑叆鎶ラ攢鍗�
+        // 2.1 鐢宠浜猴細鏀寔浠d粬浜哄彂璧凤紱鏈寚瀹氭椂榛樿褰撳墠鐧诲綍鐢ㄦ埛锛堜唬褰曚汉鐢� creator 瀹¤瀛楁璁板綍锛�
+        Long applicantId = createReqVO.getApplicantId() != null
+                ? createReqVO.getApplicantId() : getLoginUserId();
+        validateApplicant(applicantId);
+
+        // 2.2 鎻掑叆鎶ラ攢鍗�
         FinanceReimbursementDO reimbursement = BeanUtils.toBean(createReqVO, FinanceReimbursementDO.class, in -> in
                 .setNo(no)
                 .setStatus(FinanceReimbursementStatusEnum.DRAFT.getStatus())
-                .setApplicantId(getLoginUserId())
+                .setApplicantId(applicantId)
                 .setApplyDate(createReqVO.getApplyDate() != null ? createReqVO.getApplyDate() : LocalDate.now()));
         reimbursementMapper.insert(reimbursement);
 
-        // 2.2 鎻掑叆鎶ラ攢鏄庣粏
+        // 2.3 鎻掑叆鎶ラ攢鏄庣粏
         List<FinanceReimbursementItemDO> items = buildItems(reimbursement.getId(), createReqVO.getItems());
         if (CollUtil.isNotEmpty(items)) {
             reimbursementItemMapper.insertBatch(items);
         }
 
-        // 2.3 缁戝畾鍙戠エ闄勪欢
+        // 2.4 缁戝畾鍙戠エ闄勪欢
         storageAttachmentApi.bindAttachments("file", REIMBURSEMENT_RECORD_TYPE,
                 reimbursement.getId(), createReqVO.getBlobIds());
 
@@ -132,6 +143,11 @@
 
         // 2.1 鏇存柊鎶ラ攢鍗�
         FinanceReimbursementDO updateObj = BeanUtils.toBean(updateReqVO, FinanceReimbursementDO.class);
+        // 鐢宠浜猴細浠d粬浜哄彂璧锋椂鍏佽淇敼瀹為檯鐢宠浜猴紱涓嶄紶鍒欎繚鎸佸師鍊�
+        if (updateReqVO.getApplicantId() != null) {
+            validateApplicant(updateReqVO.getApplicantId());
+            updateObj.setApplicantId(updateReqVO.getApplicantId());
+        }
         // 鎶ラ攢鍗曞彿锛氭湭濉〃绀轰笉鍙樻洿锛屽~浜嗗垯鏍¢獙鍚庤鐩�
         String customNo = StrUtil.trimToNull(updateReqVO.getNo());
         if (customNo == null || customNo.equals(reimbursement.getNo())) {
@@ -205,10 +221,12 @@
             throw exception(REIMBURSEMENT_SUBMIT_FAIL_ITEM_EMPTY);
         }
 
-        // 2. 鍒涘缓 BPM 娴佺▼瀹炰緥
+        // 2. 鍒涘缓 BPM 娴佺▼瀹炰緥锛屾妸瀹為檯鐢宠浜轰笌浠e綍浜哄啓鍏ユ祦绋嬪彉閲忥紝渚涘鎵规祦灞曠ず涓庤矾鐢变娇鐢�
+        Map<String, Object> variables = buildProcessVariables(reimbursement, userId);
         String processInstanceId = processInstanceApi.createProcessInstance(userId,
                 new BpmProcessInstanceCreateReqDTO()
                         .setProcessDefinitionKey(processDefinitionKey)
+                        .setVariables(variables)
                         .setBusinessKey(String.valueOf(id)));
 
         // 3. 鏇存柊鐘舵�佷负瀹℃壒涓�
@@ -361,6 +379,35 @@
         }
     }
 
+    /**
+     * 鏍¢獙瀹為檯鐢宠浜哄瓨鍦ㄤ笖涓哄惎鐢ㄧ姸鎬�
+     */
+    private void validateApplicant(Long applicantId) {
+        AdminUserRespDTO applicant = applicantId != null ? adminUserApi.getUser(applicantId) : null;
+        if (applicant == null || !CommonStatusEnum.ENABLE.getStatus().equals(applicant.getStatus())) {
+            throw exception(REIMBURSEMENT_APPLICANT_NOT_EXISTS, applicantId);
+        }
+    }
+
+    /**
+     * 鏋勫缓鎶ラ攢瀹℃壒娴佺▼鍙橀噺锛氬疄闄呯敵璇蜂汉 + 浠e綍锛堝彂璧凤級浜�
+     *
+     * <p>瀹℃壒娴佷腑鍙�氳繃 applicantName 灞曠ず瀹為檯鐢宠浜猴紝閫氳繃 initiatorName 灞曠ず浠e綍浜猴紝
+     * 骞堕�氳繃 isProxy 鍒ゆ柇鏄惁涓轰唬浠栦汉鍙戣捣
+     */
+    private Map<String, Object> buildProcessVariables(FinanceReimbursementDO reimbursement, Long submitUserId) {
+        Long applicantId = reimbursement.getApplicantId();
+        AdminUserRespDTO applicant = applicantId != null ? adminUserApi.getUser(applicantId) : null;
+        AdminUserRespDTO submitter = submitUserId != null ? adminUserApi.getUser(submitUserId) : null;
+        Map<String, Object> variables = new HashMap<>(8);
+        variables.put("applicantId", applicantId);
+        variables.put("applicantName", applicant != null ? applicant.getNickname() : "");
+        variables.put("initiatorId", submitUserId);
+        variables.put("initiatorName", submitter != null ? submitter.getNickname() : "");
+        variables.put("isProxy", applicantId != null && !applicantId.equals(submitUserId));
+        return variables;
+    }
+
     private List<FinanceReimbursementItemDO> buildItems(Long reimbursementId,
                                                          List<FinanceReimbursementItemSaveReqVO> itemVOs) {
         if (CollUtil.isEmpty(itemVOs)) {
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiService.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiService.java
new file mode 100644
index 0000000..583d389
--- /dev/null
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiService.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.finance.service.reimbursement.ai;
+
+import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo.FinanceReimbursementOcrReqVO;
+import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo.FinanceReimbursementOcrRespVO;
+
+import java.util.List;
+
+/**
+ * 璐㈠姟鎶ラ攢鍗� AI OCR Service 鎺ュ彛
+ */
+public interface FinanceReimbursementAiService {
+
+    /**
+     * 鎵归噺璇嗗埆鍙戠エ鏂囦欢锛岄�愪釜杩斿洖璇嗗埆缁撴灉
+     *
+     * <p>鍗曚釜鏂囦欢璇嗗埆澶辫触涓嶅奖鍝嶅叾瀹冩枃浠讹紝澶辫触淇℃伅鏀惧湪 {@link FinanceReimbursementOcrRespVO#getRawText()} 涓�
+     *
+     * @param reqVO 璇嗗埆璇锋眰
+     * @return 涓� blobIds 椤哄簭涓�鑷寸殑璇嗗埆缁撴灉
+     */
+    List<FinanceReimbursementOcrRespVO> ocrInvoices(FinanceReimbursementOcrReqVO reqVO);
+
+}
diff --git a/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiServiceImpl.java b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiServiceImpl.java
new file mode 100644
index 0000000..3b49d17
--- /dev/null
+++ b/yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/ai/FinanceReimbursementAiServiceImpl.java
@@ -0,0 +1,297 @@
+package cn.iocoder.yudao.module.finance.service.reimbursement.ai;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.module.ai.api.chat.AiChatApi;
+import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo.FinanceReimbursementOcrReqVO;
+import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.ai.vo.FinanceReimbursementOcrRespVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageBlobDO;
+import cn.iocoder.yudao.module.system.service.storage.SystemStorageBlobService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.text.PDFTextStripper;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 璐㈠姟鎶ラ攢鍗� AI OCR Service 瀹炵幇绫�
+ *
+ * <p>
+ * 鏀寔璇嗗埆鍙戠エ鍥剧墖 / PDF / 鏂囨湰鏂囦欢锛屾彁鍙栧彂绁ㄥ彿鐮併�佸彂绁ㄤ唬鐮併�佸彂绁ㄧ被鍨嬨�佽喘涔版柟銆侀攢鍞柟銆�
+ * 浠风◣鍚堣銆佺◣棰濄�佸紑绁ㄦ棩鏈熺瓑缁撴瀯鍖栦俊鎭紝渚涘墠绔嚜鍔ㄥ洖濉姤閿�鏄庣粏銆�
+ * 鍥剧墖璧板妯℃�佽瑙夎瘑鍒紝PDF / 鏂囨湰鍏堟娊鍙栨枃鏈啀璧扮函鏂囨湰璇嗗埆銆�
+ */
+@Slf4j
+@Service
+public class FinanceReimbursementAiServiceImpl implements FinanceReimbursementAiService {
+
+    private static final Set<String> SUPPORTED_EXTENSIONS = Set.of(
+            "pdf", "png", "jpg", "jpeg", "gif", "bmp", "txt", "csv"
+    );
+
+    private static final Set<String> IMAGE_EXTENSIONS = Set.of("png", "jpg", "jpeg", "gif", "bmp");
+
+    /** 鍗曚釜鏂囦欢鏈�澶� 20MB锛岄伩鍏嶈秴澶ф枃浠舵嫋鍨瘑鍒湇鍔� */
+    private static final long MAX_FILE_SIZE = 20L * 1024 * 1024;
+
+    private static final String SYSTEM_PROMPT = """
+            浣犳槸涓�浣嶄笓涓氱殑涓浗鍙戠エ淇℃伅鎻愬彇涓撳銆傝浠庡彂绁ㄦ枃浠朵腑鎻愬彇缁撴瀯鍖栦俊鎭��
+
+            ## 鎻愬彇瑙勫垯
+            1. invoiceNo 涓哄彂绁ㄥ彿鐮侊紙鏁扮數绁ㄤ负 20 浣嶅彿鐮侊級锛屽師鏍疯繑鍥烇紝淇濈暀鍓嶅 0
+            2. invoiceCode 涓哄彂绁ㄤ唬鐮侊紱鏁扮數绁ㄣ�佺數瀛愬彂绁ㄦ病鏈夊彂绁ㄤ唬鐮佹椂涓嶈杩斿洖
+            3. invoiceType 涓哄彂绁ㄧ被鍨嬶紝濡傦細澧炲�肩◣涓撶敤鍙戠エ / 澧炲�肩◣鏅�氬彂绁� / 澧炲�肩◣鐢靛瓙鏅�氬彂绁� / 鐢靛瓙鍙戠エ锛堟櫘閫氬彂绁級/ 鏀舵嵁 / 鐏溅绁� / 鍏朵粬
+            4. invoiceTitle 涓鸿喘涔版柟鍚嶇О锛堝彂绁ㄦ姮澶达級
+            5. sellerName 涓洪攢鍞柟鍚嶇О锛堝紑绁ㄦ柟锛夛紝鍙�"閿�鍞柟"鎴�"閿�鏂�"淇℃伅
+            6. invoiceAmount 涓轰环绋庡悎璁★紙灏忓啓锛夐噾棰濓紝鍗曚綅鍏冿紝淇濈暀涓や綅灏忔暟
+            7. taxAmount 涓虹◣棰濆悎璁★紝鍗曚綅鍏冿紝淇濈暀涓や綅灏忔暟锛涘彂绁ㄦ湭鏍囨敞绋庨鏃朵笉杩斿洖
+            8. invoiceDate 缁熶竴杞崲涓� yyyy-MM-dd 鏍煎紡锛堝吋瀹� 2026骞�08鏈�01鏃ャ��2026/08/01 绛夊啓娉曪級
+            9. 鏁板瓧瀛楁浠呬繚鐣欐暟瀛楋紝鍘绘帀璐у竵绗﹀彿锛埪ャ�侊骏锛夊拰鍗冨垎浣嶉�楀彿
+            10. remark 涓哄彂绁ㄥ娉ㄦ爮鍐呭锛屾病鏈夊垯涓嶈繑鍥�
+
+            ## 杈撳嚭鏍煎紡
+            璇蜂弗鏍兼寜浠ヤ笅 JSON 鏍煎紡杩斿洖锛堜笉瑕佸寘鍚� Markdown 浠g爜鍧楁垨鍏跺畠鍐呭锛夛細
+            {"invoiceNo": "02564178", "invoiceCode": "044001900111", "invoiceType": "澧炲�肩◣鐢靛瓙鏅�氬彂绁�", "invoiceTitle": "姹熻嫃鍟嗚锤鑱屼笟瀛﹂櫌", "sellerName": "XX绉戞妧鏈夐檺鍏徃", "invoiceAmount": 500.00, "taxAmount": 57.52, "invoiceDate": "2026-08-01", "remark": "澶囨敞淇℃伅"}
+
+            鎵�鏈夊瓧娈靛潎涓哄彲閫夛紝鏈彁鍙栧埌鐨勫瓧娈典笉瑕佽繑鍥炪��
+            濡傛灉鏂囦欢鍐呭鏃犳硶璇嗗埆涓哄彂绁紝杩斿洖绌� JSON锛歿}
+            """;
+
+    private static final String SYSTEM_PROMPT_IMAGE = """
+            浣犳槸涓�浣嶄笓涓氱殑涓浗鍙戠エ OCR 璇嗗埆涓撳銆傝浠庡彂绁ㄥ浘鐗囦腑鎻愬彇缁撴瀯鍖栦俊鎭��
+
+            ## 鎻愬彇瑙勫垯
+            1. 浠旂粏璇嗗埆鍥剧墖涓殑鍙戠エ鏂囧瓧锛屾敞鎰忓彂绁ㄥ彲鑳藉�炬枩銆佹湁鍗扮珷閬尅鎴栦负鎷嶇収浠�
+            2. invoiceNo 涓哄彂绁ㄥ彿鐮侊紙鏁扮數绁ㄤ负 20 浣嶅彿鐮侊級锛屽師鏍疯繑鍥烇紝淇濈暀鍓嶅 0
+            3. invoiceCode 涓哄彂绁ㄤ唬鐮侊紱鏁扮數绁ㄣ�佺數瀛愬彂绁ㄦ病鏈夊彂绁ㄤ唬鐮佹椂涓嶈杩斿洖
+            4. invoiceType 涓哄彂绁ㄧ被鍨嬶紝濡傦細澧炲�肩◣涓撶敤鍙戠エ / 澧炲�肩◣鏅�氬彂绁� / 澧炲�肩◣鐢靛瓙鏅�氬彂绁� / 鐢靛瓙鍙戠エ锛堟櫘閫氬彂绁級/ 鏀舵嵁 / 鐏溅绁� / 鍏朵粬
+            5. invoiceTitle 涓鸿喘涔版柟鍚嶇О锛堝彂绁ㄦ姮澶达級
+            6. sellerName 涓洪攢鍞柟鍚嶇О锛堝紑绁ㄦ柟锛夛紝鍙�"閿�鍞柟"鎴�"閿�鏂�"淇℃伅
+            7. invoiceAmount 涓轰环绋庡悎璁★紙灏忓啓锛夐噾棰濓紝鍗曚綅鍏冿紝淇濈暀涓や綅灏忔暟
+            8. taxAmount 涓虹◣棰濆悎璁★紝鍗曚綅鍏冿紝淇濈暀涓や綅灏忔暟锛涘彂绁ㄦ湭鏍囨敞绋庨鏃朵笉杩斿洖
+            9. invoiceDate 缁熶竴杞崲涓� yyyy-MM-dd 鏍煎紡
+            10. 鏁板瓧瀛楁浠呬繚鐣欐暟瀛楋紝鍘绘帀璐у竵绗﹀彿锛埪ャ�侊骏锛夊拰鍗冨垎浣嶉�楀彿
+            11. remark 涓哄彂绁ㄥ娉ㄦ爮鍐呭锛屾病鏈夊垯涓嶈繑鍥�
+
+            ## 杈撳嚭鏍煎紡
+            璇蜂弗鏍兼寜浠ヤ笅 JSON 鏍煎紡杩斿洖锛堜笉瑕佸寘鍚� Markdown 浠g爜鍧楁垨鍏跺畠鍐呭锛夛細
+            {"invoiceNo": "02564178", "invoiceCode": "044001900111", "invoiceType": "澧炲�肩◣鐢靛瓙鏅�氬彂绁�", "invoiceTitle": "姹熻嫃鍟嗚锤鑱屼笟瀛﹂櫌", "sellerName": "XX绉戞妧鏈夐檺鍏徃", "invoiceAmount": 500.00, "taxAmount": 57.52, "invoiceDate": "2026-08-01", "remark": "澶囨敞淇℃伅"}
+
+            鎵�鏈夊瓧娈靛潎涓哄彲閫夛紝鏈彁鍙栧埌鐨勫瓧娈典笉瑕佽繑鍥炪��
+            濡傛灉鍥剧墖鍐呭鏃犳硶璇嗗埆涓哄彂绁紝杩斿洖绌� JSON锛歿}
+            """;
+
+    @Resource
+    private SystemStorageBlobService blobService;
+
+    @Resource
+    private AiChatApi aiChatApi;
+
+    @Override
+    public List<FinanceReimbursementOcrRespVO> ocrInvoices(FinanceReimbursementOcrReqVO reqVO) {
+        List<FinanceReimbursementOcrRespVO> results = new ArrayList<>(reqVO.getBlobIds().size());
+        for (Long blobId : reqVO.getBlobIds()) {
+            FinanceReimbursementOcrRespVO result = new FinanceReimbursementOcrRespVO();
+            result.setBlobId(blobId);
+            try {
+                ocrSingleInvoice(blobId, result);
+            } catch (Exception e) {
+                log.warn("AI OCR 璇嗗埆鎶ラ攢鍙戠エ澶辫触锛宐lobId={}", blobId, e);
+                result.setRawText("AI 璇嗗埆鏆傛椂涓嶅彲鐢紝璇锋墜鍔ㄥ綍鍏�");
+            }
+            results.add(result);
+        }
+        return results;
+    }
+
+    private void ocrSingleInvoice(Long blobId, FinanceReimbursementOcrRespVO result) throws IOException {
+        // 1. 鑾峰彇鏂囦欢
+        SystemStorageBlobDO blob = blobService.getStorageBlob(blobId);
+        if (blob == null) {
+            result.setRawText("鍙戠エ鏂囦欢涓嶅瓨鍦ㄦ垨宸茶鍒犻櫎锛岃閲嶆柊涓婁紶");
+            return;
+        }
+
+        // 2. 鏍¢獙鏂囦欢绫诲瀷
+        String ext = StrUtil.isBlank(blob.getOriginalFilename()) ? ""
+                : FileUtil.extName(blob.getOriginalFilename()).toLowerCase();
+        if (!SUPPORTED_EXTENSIONS.contains(ext)) {
+            result.setRawText("涓嶆敮鎸佺殑鏂囦欢绫诲瀷锛岃涓婁紶 PDF 鎴栧浘鐗囨枃浠�");
+            return;
+        }
+        if (blob.getByteSize() != null && blob.getByteSize() > MAX_FILE_SIZE) {
+            result.setRawText("鏂囦欢瓒呰繃 20MB锛岃鍘嬬缉鍚庨噸鏂颁笂浼�");
+            return;
+        }
+
+        // 3. 璇诲彇鏂囦欢瀛楄妭锛堢鐩樹笂鐪熷疄瀛樺偍鐨� blob锛屼笉鑳藉垹闄わ級
+        File blobFile = blobService.getPublicFile(blob.getUidFilename(), blob.getResourceKey());
+        if (blobFile == null || !blobFile.exists()) {
+            result.setRawText("鏂囦欢璇诲彇澶辫触锛岃閲嶆柊涓婁紶");
+            return;
+        }
+        byte[] fileBytes = Files.readAllBytes(blobFile.toPath());
+        if (fileBytes.length == 0) {
+            result.setRawText("鏂囦欢鍐呭涓虹┖锛屾棤娉曡瘑鍒�");
+            return;
+        }
+
+        // 4. AI 璇嗗埆锛堝浘鐗囪蛋澶氭ā鎬侊紝鏂囨湰璧扮函鏂囨湰锛�
+        boolean isImage = IMAGE_EXTENSIONS.contains(ext);
+        String aiResponse;
+        if (isImage) {
+            String imageBase64 = Base64.getEncoder().encodeToString(fileBytes);
+            aiResponse = aiChatApi.chatWithImage(SYSTEM_PROMPT_IMAGE,
+                    "璇疯瘑鍒浘鐗囦腑鐨勫彂绁ㄤ俊鎭�", imageBase64, getMimeType(ext));
+        } else {
+            String extractedText = extractTextByType(ext, fileBytes);
+            if (StrUtil.isEmpty(extractedText)) {
+                result.setRawText("鏂囦欢鍐呭涓虹┖鎴栦负鎵弿浠讹紝鏃犳硶璇嗗埆锛岃涓婁紶鍙戠エ鍥剧墖");
+                return;
+            }
+            aiResponse = aiChatApi.chat(SYSTEM_PROMPT, extractedText);
+        }
+        log.info("AI OCR 璇嗗埆鎶ラ攢鍙戠エ缁撴灉: blobId={}, file={}, response={}",
+                blobId, blob.getOriginalFilename(), aiResponse);
+
+        // 5. 瑙f瀽鍝嶅簲
+        parseOcrResponse(aiResponse, result);
+    }
+
+    private String extractTextByType(String ext, byte[] bytes) throws IOException {
+        return switch (ext) {
+            case "pdf" -> extractPdfText(bytes);
+            case "txt", "csv" -> new String(bytes, StandardCharsets.UTF_8);
+            default -> "";
+        };
+    }
+
+    private String extractPdfText(byte[] bytes) throws IOException {
+        try (PDDocument doc = Loader.loadPDF(bytes)) {
+            PDFTextStripper stripper = new PDFTextStripper();
+            stripper.setSortByPosition(true);
+            String text = stripper.getText(doc);
+            return StrUtil.isNotBlank(text) ? text.trim() : "";
+        }
+    }
+
+    private String getMimeType(String ext) {
+        return switch (ext) {
+            case "jpg", "jpeg" -> "image/jpeg";
+            case "gif" -> "image/gif";
+            case "bmp" -> "image/bmp";
+            default -> "image/png";
+        };
+    }
+
+    private void parseOcrResponse(String aiResponse, FinanceReimbursementOcrRespVO result) {
+        if (StrUtil.isBlank(aiResponse)) {
+            result.setRawText("鏈瘑鍒埌鍙戠エ鍐呭锛岃鎵嬪姩褰曞叆鎴栭噸鏂颁笂浼�");
+            return;
+        }
+        try {
+            String json = aiResponse;
+            int start = json.indexOf("{");
+            int end = json.lastIndexOf("}");
+            if (start >= 0 && end > start) {
+                json = json.substring(start, end + 1);
+            }
+            Map<String, Object> parsed = JSONUtil.toBean(json, Map.class);
+
+            result.setInvoiceNo(toStr(parsed.get("invoiceNo")));
+            result.setInvoiceCode(toStr(parsed.get("invoiceCode")));
+            result.setInvoiceType(toStr(parsed.get("invoiceType")));
+            result.setInvoiceTitle(toStr(parsed.get("invoiceTitle")));
+            result.setSellerName(toStr(parsed.get("sellerName")));
+            result.setRemark(toStr(parsed.get("remark")));
+            result.setInvoiceAmount(toBigDecimal(parsed.get("invoiceAmount")));
+            result.setTaxAmount(toBigDecimal(parsed.get("taxAmount")));
+
+            String invoiceDate = toStr(parsed.get("invoiceDate"));
+            if (StrUtil.isNotBlank(invoiceDate)) {
+                result.setInvoiceDate(parseDate(invoiceDate));
+            }
+
+            if (!hasRecognizedField(result)) {
+                result.setRawText("鏈瘑鍒埌鍙戠エ鍐呭锛岃鎵嬪姩褰曞叆鎴栭噸鏂颁笂浼�");
+            }
+        } catch (Exception e) {
+            log.warn("瑙f瀽 AI OCR 鍝嶅簲澶辫触: {}", aiResponse, e);
+            result.setRawText("璇嗗埆缁撴灉瑙f瀽澶辫触锛岃鎵嬪姩褰曞叆");
+        }
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁璇嗗埆鍑轰换浣曟湁鏁堜笟鍔″瓧娈�
+     */
+    private boolean hasRecognizedField(FinanceReimbursementOcrRespVO result) {
+        return StrUtil.isNotBlank(result.getInvoiceNo())
+                || StrUtil.isNotBlank(result.getInvoiceCode())
+                || StrUtil.isNotBlank(result.getInvoiceType())
+                || StrUtil.isNotBlank(result.getInvoiceTitle())
+                || StrUtil.isNotBlank(result.getSellerName())
+                || StrUtil.isNotBlank(result.getRemark())
+                || result.getInvoiceAmount() != null
+                || result.getTaxAmount() != null
+                || result.getInvoiceDate() != null;
+    }
+
+    private String toStr(Object value) {
+        return value == null ? null : StrUtil.trimToNull(String.valueOf(value));
+    }
+
+    /**
+     * 灏� AI 杩斿洖鐨勬暟瀛楋紙鍙兘鏄� Number 鎴栨暟瀛楀瓧绗︿覆锛夎浆鎹负 BigDecimal
+     */
+    private BigDecimal toBigDecimal(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof Number n) {
+            return BigDecimal.valueOf(n.doubleValue());
+        }
+        if (value instanceof String s) {
+            s = s.replace(",", "").replace("楼", "").replace("锟�", "").trim();
+            if (StrUtil.isBlank(s)) {
+                return null;
+            }
+            try {
+                return new BigDecimal(s);
+            } catch (NumberFormatException ignored) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 瑙f瀽鏃ユ湡锛屽吋瀹� yyyy-MM-dd銆亂yyy/MM/dd銆亂yyy骞碝M鏈坉d鏃� 绛夊父瑙佹牸寮�
+     */
+    private LocalDate parseDate(String value) {
+        String s = value.trim()
+                .replace("骞�", "-")
+                .replace("鏈�", "-")
+                .replace("鏃�", "")
+                .replace("/", "-");
+        try {
+            return LocalDate.parse(s);
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+}
diff --git a/yudao-module-purchase/pom.xml b/yudao-module-purchase/pom.xml
new file mode 100644
index 0000000..9a77bc7
--- /dev/null
+++ b/yudao-module-purchase/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-module-purchase</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        purchase 鍖呬笅锛岄噰璐鐞嗘ā鍧楋紙閲囪喘鍗曚富琛� + 閲囪喘鍟嗗搧鏄庣粏琛岋紝鍚庣画瀵规帴瀹℃壒涓庢姤閿�锛夈��
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- Web 鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- DB 鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
+        </dependency>
+
+        <!-- 宸ュ叿绫荤浉鍏� -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-excel</artifactId>
+        </dependency>
+
+        <!-- Test 娴嬭瘯鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/PurchaseOrderController.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/PurchaseOrderController.java
new file mode 100644
index 0000000..2e86d44
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/PurchaseOrderController.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.purchase.controller.admin.order;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderItemRespVO;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderPageReqVO;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderRespVO;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.purchase.dal.dataobject.order.PurchaseOrderDO;
+import cn.iocoder.yudao.module.purchase.service.order.PurchaseOrderService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 閲囪喘鍗�")
+@RestController
+@RequestMapping("/purchase/order")
+@Validated
+public class PurchaseOrderController {
+
+    @Resource
+    private PurchaseOrderService orderService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓閲囪喘鍗�")
+    @PreAuthorize("@ss.hasPermission('purchase:order:create')")
+    public CommonResult<Long> createOrder(@Valid @RequestBody PurchaseOrderSaveReqVO reqVO) {
+        return success(orderService.createOrder(reqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊閲囪喘鍗�")
+    @PreAuthorize("@ss.hasPermission('purchase:order:update')")
+    public CommonResult<Boolean> updateOrder(@Valid @RequestBody PurchaseOrderSaveReqVO reqVO) {
+        orderService.updateOrder(reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎閲囪喘鍗�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('purchase:order:delete')")
+    public CommonResult<Boolean> deleteOrder(@RequestParam("id") Long id) {
+        orderService.deleteOrder(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱閲囪喘鍗曪紙鍚槑缁嗭級")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('purchase:order:query')")
+    public CommonResult<PurchaseOrderRespVO> getOrder(@RequestParam("id") Long id) {
+        PurchaseOrderDO order = orderService.getOrder(id);
+        if (order == null) {
+            return success(null);
+        }
+        PurchaseOrderRespVO vo = BeanUtils.toBean(order, PurchaseOrderRespVO.class);
+        vo.setItems(BeanUtils.toBean(orderService.getItemListByOrderId(id), PurchaseOrderItemRespVO.class));
+        return success(vo);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱閲囪喘鍗曞垎椤�")
+    @PreAuthorize("@ss.hasPermission('purchase:order:query')")
+    public CommonResult<PageResult<PurchaseOrderRespVO>> getOrderPage(@Valid PurchaseOrderPageReqVO reqVO) {
+        PageResult<PurchaseOrderDO> pageResult = orderService.getOrderPage(reqVO);
+        List<PurchaseOrderRespVO> list = BeanUtils.toBean(pageResult.getList(), PurchaseOrderRespVO.class);
+        // 鍒楄〃涓嶈繑鍥炴槑缁嗭紝閬垮厤 N+1 鏌ヨ
+        list.forEach(vo -> vo.setItems(Collections.emptyList()));
+        return success(new PageResult<>(list, pageResult.getTotal()));
+    }
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemRespVO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemRespVO.java
new file mode 100644
index 0000000..fe66663
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemRespVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.purchase.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 閲囪喘鍟嗗搧鏄庣粏 Response VO")
+@Data
+public class PurchaseOrderItemRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "閲囪喘鍗� ID", example = "1")
+    private Long orderId;
+
+    @Schema(description = "鍟嗗搧鍚嶇О", example = "A4 澶嶅嵃绾�")
+    private String itemName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "70g / 500寮�")
+    private String spec;
+
+    @Schema(description = "鏁伴噺", example = "10")
+    private BigDecimal quantity;
+
+    @Schema(description = "鍗曚环锛堝厓锛�", example = "25.00")
+    private BigDecimal unitPrice;
+
+    @Schema(description = "閲戦锛堝厓锛�", example = "250.00")
+    private BigDecimal amount;
+
+    @Schema(description = "鎺掑簭鍙�", example = "1")
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemSaveReqVO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemSaveReqVO.java
new file mode 100644
index 0000000..8fb2c8a
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderItemSaveReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.purchase.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 閲囪喘鍟嗗搧鏄庣粏鏂板/淇敼 Request VO")
+@Data
+public class PurchaseOrderItemSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鍟嗗搧鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "A4 澶嶅嵃绾�")
+    @NotBlank(message = "鍟嗗搧鍚嶇О涓嶈兘涓虹┖")
+    @Size(max = 128, message = "鍟嗗搧鍚嶇О涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String itemName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "70g / 500寮�")
+    @Size(max = 128, message = "瑙勬牸鍨嬪彿涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String spec;
+
+    @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @NotNull(message = "鏁伴噺涓嶈兘涓虹┖")
+    @DecimalMin(value = "0.01", message = "鏁伴噺蹇呴』澶т簬 0")
+    private BigDecimal quantity;
+
+    @Schema(description = "鍗曚环锛堝厓锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "25.00")
+    @NotNull(message = "鍗曚环涓嶈兘涓虹┖")
+    @DecimalMin(value = "0", message = "鍗曚环涓嶈兘灏忎簬 0")
+    private BigDecimal unitPrice;
+
+    @Schema(description = "鎺掑簭鍙�", example = "1")
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderPageReqVO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderPageReqVO.java
new file mode 100644
index 0000000..8caae79
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderPageReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.purchase.controller.admin.order.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 閲囪喘鍗曞垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PurchaseOrderPageReqVO extends PageParam {
+
+    @Schema(description = "閲囪喘鍗曞彿", example = "CG20261020000001")
+    private String no;
+
+    @Schema(description = "閲囪喘浜猴紙缁忓姙浜猴級", example = "寮犱笁")
+    private String buyerName;
+
+    @Schema(description = "閲囪喘鏃堕棿鑼冨洿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate[] purchaseDate;
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderRespVO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderRespVO.java
new file mode 100644
index 0000000..0efd77f
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderRespVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.purchase.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - 閲囪喘鍗� Response VO")
+@Data
+public class PurchaseOrderRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "閲囪喘鍗曞彿", example = "CG20261020000001")
+    private String no;
+
+    @Schema(description = "閲囪喘鏃堕棿", example = "2026-10-20")
+    private LocalDate purchaseDate;
+
+    @Schema(description = "閲囪喘浜猴紙缁忓姙浜猴級鐢ㄦ埛 ID", example = "1024")
+    private Long buyerId;
+
+    @Schema(description = "閲囪喘浜猴紙缁忓姙浜猴級濮撳悕", example = "寮犱笁")
+    private String buyerName;
+
+    @Schema(description = "閲囪喘閲戦锛堝厓锛�", example = "1250.00")
+    private BigDecimal totalAmount;
+
+    @Schema(description = "鐘舵�侊細0-鑽夌", example = "0")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "閲囪喘鍟嗗搧鏄庣粏")
+    private List<PurchaseOrderItemRespVO> items;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderSaveReqVO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderSaveReqVO.java
new file mode 100644
index 0000000..0df57a7
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/controller/admin/order/vo/PurchaseOrderSaveReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.purchase.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - 閲囪喘鍗曟柊澧�/淇敼 Request VO")
+@Data
+public class PurchaseOrderSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "閲囪喘鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-10-20")
+    @NotNull(message = "閲囪喘鏃堕棿涓嶈兘涓虹┖")
+    private LocalDate purchaseDate;
+
+    @Schema(description = "閲囪喘浜猴紙缁忓姙浜猴級鐢ㄦ埛 ID", example = "1024")
+    private Long buyerId;
+
+    @Schema(description = "閲囪喘浜猴紙缁忓姙浜猴級濮撳悕", requiredMode = Schema.RequiredMode.REQUIRED, example = "寮犱笁")
+    @NotBlank(message = "閲囪喘浜猴紙缁忓姙浜猴級涓嶈兘涓虹┖")
+    @Size(max = 64, message = "閲囪喘浜哄鍚嶄笉鑳借秴杩� 64 涓瓧绗�")
+    private String buyerName;
+
+    @Schema(description = "澶囨敞")
+    @Size(max = 512, message = "澶囨敞涓嶈兘瓒呰繃 512 涓瓧绗�")
+    private String remark;
+
+    @Schema(description = "閲囪喘鍟嗗搧鏄庣粏", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "閲囪喘鍗曡嚦灏戦渶瑕� 1 琛岄噰璐晢鍝佹槑缁�")
+    @Valid
+    private List<PurchaseOrderItemSaveReqVO> items;
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderDO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderDO.java
new file mode 100644
index 0000000..97c41fe
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderDO.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.purchase.dal.dataobject.order;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 閲囪喘鍗� DO
+ */
+@TableName("purchase_order")
+@KeySequence("purchase_order_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PurchaseOrderDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /** 閲囪喘鍗曞彿 */
+    private String no;
+
+    /** 閲囪喘鏃堕棿 */
+    private LocalDate purchaseDate;
+
+    /** 閲囪喘浜猴紙缁忓姙浜猴級鐢ㄦ埛 ID */
+    private Long buyerId;
+
+    /** 閲囪喘浜猴紙缁忓姙浜猴級濮撳悕 */
+    private String buyerName;
+
+    /** 閲囪喘閲戦锛堟槑缁嗛噾棰濆悎璁★級 */
+    private BigDecimal totalAmount;
+
+    /** 鐘舵�侊細0-鑽夌 */
+    private Integer status;
+
+    /** 澶囨敞 */
+    private String remark;
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderItemDO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderItemDO.java
new file mode 100644
index 0000000..8530d6e
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/dataobject/order/PurchaseOrderItemDO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.purchase.dal.dataobject.order;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * 閲囪喘鍗曞晢鍝佹槑缁� DO
+ */
+@TableName("purchase_order_item")
+@KeySequence("purchase_order_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PurchaseOrderItemDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /** 閲囪喘鍗� ID */
+    private Long orderId;
+
+    /** 鍟嗗搧鍚嶇О */
+    private String itemName;
+
+    /** 瑙勬牸鍨嬪彿 */
+    private String spec;
+
+    /** 鏁伴噺 */
+    private BigDecimal quantity;
+
+    /** 鍗曚环锛堝厓锛� */
+    private BigDecimal unitPrice;
+
+    /** 閲戦锛堝厓锛�= 鏁伴噺 脳 鍗曚环 */
+    private BigDecimal amount;
+
+    /** 鎺掑簭鍙� */
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderLineMapper.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderLineMapper.java
new file mode 100644
index 0000000..122f760
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderLineMapper.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.purchase.dal.mysql.order;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.purchase.dal.dataobject.order.PurchaseOrderItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface PurchaseOrderLineMapper extends BaseMapperX<PurchaseOrderItemDO> {
+
+    default List<PurchaseOrderItemDO> selectListByOrderId(Long orderId) {
+        return selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<PurchaseOrderItemDO>()
+                .eq(PurchaseOrderItemDO::getOrderId, orderId)
+                .orderByAsc(PurchaseOrderItemDO::getSortNo)
+                .orderByAsc(PurchaseOrderItemDO::getId));
+    }
+
+    default int deleteByOrderId(Long orderId) {
+        return delete(PurchaseOrderItemDO::getOrderId, orderId);
+    }
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderMainMapper.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderMainMapper.java
new file mode 100644
index 0000000..3ea57ae
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/mysql/order/PurchaseOrderMainMapper.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.purchase.dal.mysql.order;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderPageReqVO;
+import cn.iocoder.yudao.module.purchase.dal.dataobject.order.PurchaseOrderDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PurchaseOrderMainMapper extends BaseMapperX<PurchaseOrderDO> {
+
+    default PurchaseOrderDO selectByNo(String no) {
+        return selectOne(PurchaseOrderDO::getNo, no);
+    }
+
+    default PageResult<PurchaseOrderDO> selectPage(PurchaseOrderPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<PurchaseOrderDO>()
+                .likeIfPresent(PurchaseOrderDO::getNo, reqVO.getNo())
+                .likeIfPresent(PurchaseOrderDO::getBuyerName, reqVO.getBuyerName())
+                .betweenIfPresent(PurchaseOrderDO::getPurchaseDate, reqVO.getPurchaseDate())
+                .orderByDesc(PurchaseOrderDO::getId));
+    }
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/PurchaseNoRedisDAO.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/PurchaseNoRedisDAO.java
new file mode 100644
index 0000000..e9f5300
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/PurchaseNoRedisDAO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.purchase.dal.redis;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+/**
+ * Purchase 缂栧彿 Redis DAO
+ */
+@Repository
+public class PurchaseNoRedisDAO {
+
+    /** 閲囪喘鍗曞彿鍓嶇紑 */
+    public static final String ORDER_NO_PREFIX = "CG";
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    public String generate(String prefix) {
+        String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
+        String key = RedisKeyConstants.NO + noPrefix;
+        Long no = stringRedisTemplate.opsForValue().increment(key);
+        stringRedisTemplate.expire(key, Duration.ofDays(1L));
+        return noPrefix + String.format("%06d", no);
+    }
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/RedisKeyConstants.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/RedisKeyConstants.java
new file mode 100644
index 0000000..8484c76
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/dal/redis/RedisKeyConstants.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.purchase.dal.redis;
+
+/**
+ * Purchase Redis Key 鏋氫妇绫�
+ */
+public interface RedisKeyConstants {
+
+    /**
+     * 閲囪喘鍗曞彿鑷搴忓垪
+     * <p>
+     * KEY 鏍煎紡锛歱urchase:no:{prefix}{yyyyMMdd}
+     */
+    String NO = "purchase:no:";
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/enums/ErrorCodeConstants.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/enums/ErrorCodeConstants.java
new file mode 100644
index 0000000..5e3fbe1
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/enums/ErrorCodeConstants.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.purchase.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Purchase 閿欒鐮佹灇涓剧被
+ * <p>
+ * purchase 绯荤粺锛屼娇鐢� 1-030-111-000 娈碉紙鎺ュ湪 vehicle 1-030-110 涔嬪悗锛�
+ */
+public interface ErrorCodeConstants {
+
+    ErrorCode PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_030_111_000, "閲囪喘鍗曚笉瀛樺湪锛岃鍒锋柊鍒楄〃鍚庨噸璇�");
+    ErrorCode PURCHASE_ORDER_NO_EXISTS = new ErrorCode(1_030_111_001, "鐢熸垚閲囪喘鍗曞彿澶辫触锛岃閲嶆柊鎻愪氦");
+    ErrorCode PURCHASE_ORDER_UPDATE_FAIL = new ErrorCode(1_030_111_002, "閲囪喘鍗�({})褰撳墠鐘舵�佷负銆攞}銆曪紝鍙湁鑽夌鐘舵�佹墠鑳戒慨鏀�");
+    ErrorCode PURCHASE_ORDER_DELETE_FAIL = new ErrorCode(1_030_111_003, "閲囪喘鍗�({})褰撳墠鐘舵�佷负銆攞}銆曪紝鍙湁鑽夌鐘舵�佹墠鑳藉垹闄�");
+    ErrorCode PURCHASE_ORDER_ITEM_EMPTY = new ErrorCode(1_030_111_004, "閲囪喘鍗曡嚦灏戦渶瑕� 1 琛岄噰璐晢鍝佹槑缁嗭紝璇锋坊鍔犳槑缁嗗悗鍐嶄繚瀛�");
+    ErrorCode PURCHASE_ORDER_ITEM_NAME_EMPTY = new ErrorCode(1_030_111_005, "绗� {} 琛岄噰璐晢鍝侊細鍟嗗搧鍚嶇О涓嶈兘涓虹┖锛岃濉啓鍚庡啀淇濆瓨");
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderService.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderService.java
new file mode 100644
index 0000000..1c406b1
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderService.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.purchase.service.order;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderPageReqVO;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.purchase.dal.dataobject.order.PurchaseOrderDO;
+import cn.iocoder.yudao.module.purchase.dal.dataobject.order.PurchaseOrderItemDO;
+
+import java.util.List;
+
+/**
+ * 閲囪喘鍗� Service 鎺ュ彛
+ */
+public interface PurchaseOrderService {
+
+    Long createOrder(PurchaseOrderSaveReqVO reqVO);
+
+    void updateOrder(PurchaseOrderSaveReqVO reqVO);
+
+    void deleteOrder(Long id);
+
+    PurchaseOrderDO getOrder(Long id);
+
+    List<PurchaseOrderItemDO> getItemListByOrderId(Long orderId);
+
+    PageResult<PurchaseOrderDO> getOrderPage(PurchaseOrderPageReqVO reqVO);
+
+}
diff --git a/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderServiceImpl.java b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderServiceImpl.java
new file mode 100644
index 0000000..9bbec66
--- /dev/null
+++ b/yudao-module-purchase/src/main/java/cn/iocoder/yudao/module/purchase/service/order/PurchaseOrderServiceImpl.java
@@ -0,0 +1,152 @@
+package cn.iocoder.yudao.module.purchase.service.order;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderItemSaveReqVO;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderPageReqVO;
+import cn.iocoder.yudao.module.purchase.controller.admin.order.vo.PurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.purchase.dal.dataobject.order.PurchaseOrderDO;
+import cn.iocoder.yudao.module.purchase.dal.dataobject.order.PurchaseOrderItemDO;
+import cn.iocoder.yudao.module.purchase.dal.mysql.order.PurchaseOrderLineMapper;
+import cn.iocoder.yudao.module.purchase.dal.mysql.order.PurchaseOrderMainMapper;
+import cn.iocoder.yudao.module.purchase.dal.redis.PurchaseNoRedisDAO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.purchase.enums.ErrorCodeConstants.*;
+
+/**
+ * 閲囪喘鍗� Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class PurchaseOrderServiceImpl implements PurchaseOrderService {
+
+    /** 鑽夌鐘舵�� */
+    private static final Integer STATUS_DRAFT = 0;
+
+    @Resource
+    private PurchaseOrderMainMapper orderMapper;
+
+    @Resource
+    private PurchaseOrderLineMapper itemMapper;
+
+    @Resource
+    private PurchaseNoRedisDAO noRedisDAO;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createOrder(PurchaseOrderSaveReqVO reqVO) {
+        List<PurchaseOrderItemDO> items = buildItems(null, reqVO.getItems());
+        PurchaseOrderDO order = BeanUtils.toBean(reqVO, PurchaseOrderDO.class, in -> in
+                .setNo(noRedisDAO.generate(PurchaseNoRedisDAO.ORDER_NO_PREFIX))
+                .setStatus(STATUS_DRAFT)
+                .setTotalAmount(sumAmount(items)));
+        orderMapper.insert(order);
+
+        items.forEach(item -> item.setOrderId(order.getId()));
+        itemMapper.insertBatch(items);
+        return order.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateOrder(PurchaseOrderSaveReqVO reqVO) {
+        PurchaseOrderDO old = validateOrderExists(reqVO.getId());
+        if (!STATUS_DRAFT.equals(old.getStatus())) {
+            throw exception(PURCHASE_ORDER_UPDATE_FAIL, old.getNo(), "闈炶崏绋�");
+        }
+        List<PurchaseOrderItemDO> items = buildItems(reqVO.getId(), reqVO.getItems());
+        PurchaseOrderDO updateObj = BeanUtils.toBean(reqVO, PurchaseOrderDO.class, in -> in
+                .setTotalAmount(sumAmount(items)));
+        // 鍗曞彿涓庣姸鎬佷笉鍏佽閫氳繃淇敼鎺ュ彛鍙樻洿
+        updateObj.setNo(null).setStatus(null);
+        orderMapper.updateById(updateObj);
+
+        itemMapper.deleteByOrderId(reqVO.getId());
+        itemMapper.insertBatch(items);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteOrder(Long id) {
+        PurchaseOrderDO order = validateOrderExists(id);
+        if (!STATUS_DRAFT.equals(order.getStatus())) {
+            throw exception(PURCHASE_ORDER_DELETE_FAIL, order.getNo(), "闈炶崏绋�");
+        }
+        itemMapper.deleteByOrderId(id);
+        orderMapper.deleteById(id);
+    }
+
+    @Override
+    public PurchaseOrderDO getOrder(Long id) {
+        return orderMapper.selectById(id);
+    }
+
+    @Override
+    public List<PurchaseOrderItemDO> getItemListByOrderId(Long orderId) {
+        return itemMapper.selectListByOrderId(orderId);
+    }
+
+    @Override
+    public PageResult<PurchaseOrderDO> getOrderPage(PurchaseOrderPageReqVO reqVO) {
+        return orderMapper.selectPage(reqVO);
+    }
+
+    private PurchaseOrderDO validateOrderExists(Long id) {
+        PurchaseOrderDO order = id != null ? orderMapper.selectById(id) : null;
+        if (order == null) {
+            throw exception(PURCHASE_ORDER_NOT_EXISTS);
+        }
+        return order;
+    }
+
+    /** 缁勮鏄庣粏锛氳绠楁瘡琛岄噾棰濅笌鎺掑簭鍙� */
+    private List<PurchaseOrderItemDO> buildItems(Long orderId, List<PurchaseOrderItemSaveReqVO> items) {
+        if (CollUtil.isEmpty(items)) {
+            throw exception(PURCHASE_ORDER_ITEM_EMPTY);
+        }
+        List<PurchaseOrderItemDO> result = new ArrayList<>(items.size());
+        int index = 1;
+        for (PurchaseOrderItemSaveReqVO item : items) {
+            if (item == null || StrUtil.isBlank(item.getItemName())) {
+                throw exception(PURCHASE_ORDER_ITEM_NAME_EMPTY, index);
+            }
+            BigDecimal quantity = item.getQuantity() == null ? BigDecimal.ZERO : item.getQuantity();
+            BigDecimal unitPrice = item.getUnitPrice() == null ? BigDecimal.ZERO : item.getUnitPrice();
+            BigDecimal amount = quantity.multiply(unitPrice).setScale(2, RoundingMode.HALF_UP);
+            result.add(PurchaseOrderItemDO.builder()
+                    .orderId(orderId)
+                    .itemName(StrUtil.trim(item.getItemName()))
+                    .spec(StrUtil.trim(item.getSpec()))
+                    .quantity(quantity)
+                    .unitPrice(unitPrice)
+                    .amount(amount)
+                    .sortNo(item.getSortNo() != null ? item.getSortNo() : index)
+                    .build());
+            index++;
+        }
+        return result;
+    }
+
+    private BigDecimal sumAmount(List<PurchaseOrderItemDO> items) {
+        return items.stream()
+                .map(PurchaseOrderItemDO::getAmount)
+                .filter(java.util.Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add)
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+}
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/ResearchApplicationController.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/ResearchApplicationController.java
index 63f150d..5145027 100644
--- a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/ResearchApplicationController.java
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/ResearchApplicationController.java
@@ -1,13 +1,18 @@
 package cn.iocoder.yudao.module.research.controller.admin.application;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.module.research.controller.admin.application.vo.ResearchApplicationPageReqVO;
 import cn.iocoder.yudao.module.research.controller.admin.application.vo.ResearchApplicationRespVO;
 import cn.iocoder.yudao.module.research.controller.admin.application.vo.ResearchApplicationSaveReqVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberRespVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberStatisticsReqVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberStatisticsRespVO;
 import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationDO;
 import cn.iocoder.yudao.module.research.dal.dataobject.guideline.ResearchGuidelineDO;
 import cn.iocoder.yudao.module.research.service.application.ResearchApplicationService;
@@ -21,14 +26,17 @@
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
 import java.util.*;
 import java.util.stream.Collectors;
 
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
@@ -83,7 +91,30 @@
     @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('research:application:query')")
     public CommonResult<ResearchApplicationRespVO> getApplication(@RequestParam("id") Long id) {
-        return success(buildApplicationDetail(applicationService.getApplication(id)));
+        ResearchApplicationRespVO vo = buildApplicationDetail(applicationService.getApplication(id));
+        if (vo != null) {
+            vo.setMembers(BeanUtils.toBean(applicationService.getMemberListByApplicationId(id),
+                    ResearchApplicationMemberRespVO.class));
+        }
+        return success(vo);
+    }
+
+    @GetMapping("/member-statistics")
+    @Operation(summary = "鎸夋垚鍛樼淮搴︾粺璁¤棰樺弬涓庢儏鍐�")
+    @PreAuthorize("@ss.hasPermission('research:application:query')")
+    public CommonResult<List<ResearchApplicationMemberStatisticsRespVO>> getMemberStatistics(
+            @Valid ResearchApplicationMemberStatisticsReqVO reqVO) {
+        return success(applicationService.getMemberStatistics(reqVO));
+    }
+
+    @GetMapping("/member-statistics/export-excel")
+    @Operation(summary = "瀵煎嚭璇鹃鎴愬憳缁村害缁熻 Excel")
+    @PreAuthorize("@ss.hasPermission('research:application:query')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportMemberStatisticsExcel(@Valid ResearchApplicationMemberStatisticsReqVO reqVO,
+                                            HttpServletResponse response) throws IOException {
+        List<ResearchApplicationMemberStatisticsRespVO> list = applicationService.getMemberStatistics(reqVO);
+        ExcelUtils.write(response, "璇鹃鎴愬憳缁熻.xls", "鏁版嵁", ResearchApplicationMemberStatisticsRespVO.class, list);
     }
 
     @GetMapping("/page")
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationRespVO.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationRespVO.java
index 6d461eb..3d0f41d 100644
--- a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationRespVO.java
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationRespVO.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.research.controller.admin.application.vo;
 
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberRespVO;
 import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -51,9 +52,12 @@
     @Schema(description = "鑱旂郴閭", example = "zhangsan@example.com")
     private String contactEmail;
 
-    @Schema(description = "璇鹃缁勬垚鍛�")
+    @Schema(description = "璇鹃缁勬垚鍛橈紙鏃х増鏂囨湰瀛楁锛屼粎鐢ㄤ簬鍏煎鍘嗗彶鏁版嵁锛�")
     private String teamMembers;
 
+    @Schema(description = "璇鹃缁勬垚鍛樺垪琛紙瀛愯〃锛�")
+    private List<ResearchApplicationMemberRespVO> members;
+
     @Schema(description = "鐢宠缁忚垂锛堝厓锛�", example = "100000.00")
     private BigDecimal budgetAmount;
 
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationSaveReqVO.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationSaveReqVO.java
index a33fe24..806de20 100644
--- a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationSaveReqVO.java
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationSaveReqVO.java
@@ -1,8 +1,12 @@
 package cn.iocoder.yudao.module.research.controller.admin.application.vo;
 
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberSaveReqVO;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
 import jakarta.validation.constraints.PositiveOrZero;
 import lombok.Data;
 
@@ -37,15 +41,22 @@
     @Schema(description = "鑱岀О", example = "鏁欐巿")
     private String jobTitle;
 
-    @Schema(description = "鑱旂郴鐢佃瘽", example = "13800138000")
+    @Schema(description = "鑱旂郴鐢佃瘽锛堝繀濉紝11 浣嶆墜鏈哄彿锛�", requiredMode = Schema.RequiredMode.REQUIRED,
+            example = "13800138000")
+    @NotBlank(message = "鑱旂郴鐢佃瘽涓嶈兘涓虹┖")
+    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "鑱旂郴鐢佃瘽鏍煎紡涓嶆纭紝璇疯緭鍏ユ纭殑 11 浣嶆墜鏈哄彿")
     private String contactPhone;
 
     @Schema(description = "鑱旂郴閭", example = "zhangsan@example.com")
     private String contactEmail;
 
-    @Schema(description = "璇鹃缁勬垚鍛橈紙姣忚涓�鏉★細濮撳悕|鍗曚綅|鑱岀О锛�")
+    @Schema(description = "璇鹃缁勬垚鍛橈紙鏃х増鏂囨湰瀛楁锛屼粎淇濈暀鍏煎锛涙柊鏁版嵁璇蜂娇鐢� members锛�")
     private String teamMembers;
 
+    @Schema(description = "璇鹃缁勬垚鍛樺垪琛紙瀛愯〃锛�")
+    @Valid
+    private List<ResearchApplicationMemberSaveReqVO> members;
+
     @Schema(description = "鐢宠缁忚垂锛堝厓锛�", example = "100000.00")
     @PositiveOrZero(message = "鐢宠缁忚垂涓嶈兘涓鸿礋鏁�")
     private BigDecimal budgetAmount;
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberRespVO.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberRespVO.java
new file mode 100644
index 0000000..445317b
--- /dev/null
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberRespVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.research.controller.admin.application.vo.member;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇鹃鐢虫姤涔︽垚鍛� Response VO")
+@Data
+public class ResearchApplicationMemberRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "璇鹃鐢虫姤涔� ID", example = "1")
+    private Long applicationId;
+
+    @Schema(description = "鎴愬憳濮撳悕", example = "鏉庡洓")
+    private String name;
+
+    @Schema(description = "鑱旂郴鐢佃瘽", example = "13800138000")
+    private String contactPhone;
+
+    @Schema(description = "鎵�灞炲崟浣�", example = "XX澶у")
+    private String workUnit;
+
+    @Schema(description = "鑱岀О/鑱屽姟", example = "鍓暀鎺�")
+    private String jobTitle;
+
+    @Schema(description = "鎴愬憳瑙掕壊", example = "鏍稿績鎴愬憳")
+    private String memberRole;
+
+    @Schema(description = "鎺掑簭鍙�", example = "1")
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberSaveReqVO.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberSaveReqVO.java
new file mode 100644
index 0000000..dc45858
--- /dev/null
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberSaveReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.research.controller.admin.application.vo.member;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇鹃鐢虫姤涔︽垚鍛樻柊澧�/淇敼 Request VO")
+@Data
+public class ResearchApplicationMemberSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鎴愬憳濮撳悕", requiredMode = Schema.RequiredMode.REQUIRED, example = "鏉庡洓")
+    @NotBlank(message = "鎴愬憳濮撳悕涓嶈兘涓虹┖")
+    @Size(max = 64, message = "鎴愬憳濮撳悕闀垮害涓嶈兘瓒呰繃 64 涓瓧绗�")
+    private String name;
+
+    @Schema(description = "鑱旂郴鐢佃瘽锛堥�夊~锛屽~鍐欐椂椤讳负 11 浣嶆墜鏈哄彿锛�", example = "13800138000")
+    @Size(max = 20, message = "鑱旂郴鐢佃瘽闀垮害涓嶈兘瓒呰繃 20 涓瓧绗�")
+    @Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "鎴愬憳鑱旂郴鐢佃瘽鏍煎紡涓嶆纭紝璇疯緭鍏ユ纭殑 11 浣嶆墜鏈哄彿")
+    private String contactPhone;
+
+    @Schema(description = "鎵�灞炲崟浣�", example = "XX澶у")
+    @Size(max = 128, message = "鎵�灞炲崟浣嶉暱搴︿笉鑳借秴杩� 128 涓瓧绗�")
+    private String workUnit;
+
+    @Schema(description = "鑱岀О/鑱屽姟", example = "鍓暀鎺�")
+    @Size(max = 64, message = "鑱岀О/鑱屽姟闀垮害涓嶈兘瓒呰繃 64 涓瓧绗�")
+    private String jobTitle;
+
+    @Schema(description = "鎴愬憳瑙掕壊锛氫富鎸佷汉 / 鏍稿績鎴愬憳 / 鍙備笌浜�", example = "鏍稿績鎴愬憳")
+    @Size(max = 32, message = "鎴愬憳瑙掕壊闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�")
+    private String memberRole;
+
+    @Schema(description = "鎺掑簭鍙�", example = "1")
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsReqVO.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsReqVO.java
new file mode 100644
index 0000000..122fa22
--- /dev/null
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.research.controller.admin.application.vo.member;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇鹃鎴愬憳缁村害缁熻 Request VO")
+@Data
+public class ResearchApplicationMemberStatisticsReqVO {
+
+    @Schema(description = "鍏抽敭瀛楋細鎴愬憳濮撳悕 / 鑱旂郴鐢佃瘽 / 鎵�灞炲崟浣�", example = "寮犱笁")
+    @Size(max = 64, message = "鍏抽敭瀛楅暱搴︿笉鑳借秴杩� 64 涓瓧绗�")
+    private String keyword;
+
+    @Schema(description = "鐢虫姤涔︾姸鎬佽繃婊わ細0鑽夌 15瀹℃壒涓� 20宸茬珛椤� 35宸查┏鍥烇紱涓嶄紶缁熻鍏ㄩ儴鐘舵��",
+            example = "20")
+    private Integer status;
+
+}
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsRespVO.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsRespVO.java
new file mode 100644
index 0000000..0f2e73f
--- /dev/null
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/member/ResearchApplicationMemberStatisticsRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.research.controller.admin.application.vo.member;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇鹃鎴愬憳缁村害缁熻 Response VO")
+@Data
+public class ResearchApplicationMemberStatisticsRespVO {
+
+    @Schema(description = "鎴愬憳濮撳悕", example = "鏉庡洓")
+    @ExcelProperty("鎴愬憳濮撳悕")
+    private String name;
+
+    @Schema(description = "鑱旂郴鐢佃瘽", example = "13800138000")
+    @ExcelProperty("鑱旂郴鐢佃瘽")
+    private String contactPhone;
+
+    @Schema(description = "鎵�灞炲崟浣�", example = "XX澶у")
+    @ExcelProperty("鎵�灞炲崟浣�")
+    private String workUnit;
+
+    @Schema(description = "鑱岀О/鑱屽姟", example = "鍓暀鎺�")
+    @ExcelProperty("鑱岀О/鑱屽姟")
+    private String jobTitle;
+
+    @Schema(description = "鎴愬憳瑙掕壊锛堝涓鑹蹭互椤垮彿鍒嗛殧锛�", example = "涓绘寔浜恒�佹牳蹇冩垚鍛�")
+    @ExcelProperty("鎴愬憳瑙掕壊")
+    private String memberRoles;
+
+    @Schema(description = "鍙備笌璇鹃鏁�", example = "2")
+    @ExcelProperty("鍙備笌璇鹃鏁�")
+    private Integer projectCount;
+
+    @Schema(description = "鍙備笌璇鹃鍚嶇О锛堝涓互鍒嗗彿鍒嗛殧锛�", example = "璇鹃A锛涜棰楤")
+    @ExcelProperty("鍙備笌璇鹃")
+    private String projectTitles;
+
+}
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/application/ResearchApplicationMemberDO.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/application/ResearchApplicationMemberDO.java
new file mode 100644
index 0000000..b8564a2
--- /dev/null
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/application/ResearchApplicationMemberDO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.research.dal.dataobject.application;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 璇鹃-鐢虫姤涔︽垚鍛樺瓙琛� DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("research_application_member")
+@KeySequence("research_application_member_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ResearchApplicationMemberDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /** 璇鹃鐢虫姤涔� ID */
+    private Long applicationId;
+
+    /** 鎴愬憳濮撳悕 */
+    private String name;
+
+    /** 鑱旂郴鐢佃瘽 */
+    private String contactPhone;
+
+    /** 鎵�灞炲崟浣� */
+    private String workUnit;
+
+    /** 鑱岀О/鑱屽姟 */
+    private String jobTitle;
+
+    /** 鎴愬憳瑙掕壊锛氫富鎸佷汉 / 鏍稿績鎴愬憳 / 鍙備笌浜� */
+    private String memberRole;
+
+    /** 鎺掑簭鍙� */
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/mysql/application/ResearchApplicationMemberMapper.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/mysql/application/ResearchApplicationMemberMapper.java
new file mode 100644
index 0000000..099c3de
--- /dev/null
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/mysql/application/ResearchApplicationMemberMapper.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.research.dal.mysql.application;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationMemberDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ResearchApplicationMemberMapper extends BaseMapperX<ResearchApplicationMemberDO> {
+
+    default List<ResearchApplicationMemberDO> selectListByApplicationId(Long applicationId) {
+        return selectList(ResearchApplicationMemberDO::getApplicationId, applicationId);
+    }
+
+    default int deleteByApplicationId(Long applicationId) {
+        return delete(ResearchApplicationMemberDO::getApplicationId, applicationId);
+    }
+
+}
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationService.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationService.java
index fa5393a..1f630da 100644
--- a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationService.java
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationService.java
@@ -3,7 +3,10 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.research.controller.admin.application.vo.ResearchApplicationPageReqVO;
 import cn.iocoder.yudao.module.research.controller.admin.application.vo.ResearchApplicationSaveReqVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberStatisticsReqVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberStatisticsRespVO;
 import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationDO;
+import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationMemberDO;
 
 import jakarta.validation.Valid;
 import java.math.BigDecimal;
@@ -20,6 +23,12 @@
 
     ResearchApplicationDO getApplication(Long id);
 
+    /** 鏌ヨ鏌愮敵鎶ヤ功鐨勬垚鍛樺瓙琛� */
+    List<ResearchApplicationMemberDO> getMemberListByApplicationId(Long applicationId);
+
+    /** 鎸夋垚鍛樼淮搴︾粺璁℃垚鍛樺弬涓庤棰樻儏鍐� */
+    List<ResearchApplicationMemberStatisticsRespVO> getMemberStatistics(ResearchApplicationMemberStatisticsReqVO reqVO);
+
     PageResult<ResearchApplicationDO> getApplicationPage(ResearchApplicationPageReqVO pageReqVO);
 
     void submitApplication(Long id, String processDefinitionKey);
diff --git a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java
index 827d337..00b1de9 100644
--- a/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java
+++ b/yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java
@@ -7,6 +7,7 @@
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.archive.api.document.ArchiveDocumentApi;
 import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
 import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentRespDTO;
@@ -18,9 +19,14 @@
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.research.controller.admin.application.vo.ResearchApplicationPageReqVO;
 import cn.iocoder.yudao.module.research.controller.admin.application.vo.ResearchApplicationSaveReqVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberSaveReqVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberStatisticsReqVO;
+import cn.iocoder.yudao.module.research.controller.admin.application.vo.member.ResearchApplicationMemberStatisticsRespVO;
 import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationDO;
+import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationMemberDO;
 import cn.iocoder.yudao.module.research.dal.dataobject.guideline.ResearchGuidelineDO;
 import cn.iocoder.yudao.module.research.dal.mysql.application.ResearchApplicationMapper;
+import cn.iocoder.yudao.module.research.dal.mysql.application.ResearchApplicationMemberMapper;
 import cn.iocoder.yudao.module.research.dal.redis.ResearchNoRedisDAO;
 import cn.iocoder.yudao.module.research.enums.ResearchApplicationStatusEnum;
 import cn.iocoder.yudao.module.research.enums.ResearchGuidelineCategoryEnum;
@@ -78,6 +84,9 @@
     private ResearchApplicationMapper applicationMapper;
 
     @Resource
+    private ResearchApplicationMemberMapper memberMapper;
+
+    @Resource
     private ArchiveDocumentApi archiveDocumentApi;
 
     @Resource
@@ -125,6 +134,7 @@
                 .setStatus(ResearchApplicationStatusEnum.DRAFT.getStatus())
                 .setBudgetAmount(in.getBudgetAmount() == null ? BigDecimal.ZERO : in.getBudgetAmount()));
         applicationMapper.insert(application);
+        saveMembers(application.getId(), createReqVO.getMembers());
 
         storageAttachmentApi.bindAttachments("file", APPLICATION_RECORD_TYPE,
                 application.getId(), createReqVO.getBlobIds());
@@ -147,6 +157,10 @@
         updateObj.setCategory(guideline.getCategory());
         applicationMapper.updateById(updateObj);
 
+        // 鎴愬憳瀛愯〃锛氬厛鍒犲悗澧烇紝鍏ㄩ噺瑕嗙洊
+        memberMapper.deleteByApplicationId(updateReqVO.getId());
+        saveMembers(updateReqVO.getId(), updateReqVO.getMembers());
+
         storageAttachmentApi.updateAttachments("file", APPLICATION_RECORD_TYPE,
                 updateReqVO.getId(), updateReqVO.getBlobIds());
     }
@@ -160,6 +174,7 @@
                     application.getNo(), ResearchApplicationStatusEnum.nameOf(application.getStatus()));
         }
 
+        memberMapper.deleteByApplicationId(id);
         applicationMapper.deleteById(id);
         storageAttachmentApi.deleteAttachmentsByRecord(APPLICATION_RECORD_TYPE, id);
     }
@@ -170,6 +185,116 @@
     }
 
     @Override
+    public List<ResearchApplicationMemberDO> getMemberListByApplicationId(Long applicationId) {
+        return memberMapper.selectListByApplicationId(applicationId);
+    }
+
+    @Override
+    public List<ResearchApplicationMemberStatisticsRespVO> getMemberStatistics(ResearchApplicationMemberStatisticsReqVO reqVO) {
+        // 1. 鎸夌姸鎬佽繃婊ょ敵鎶ュ崟
+        List<ResearchApplicationDO> applications = applicationMapper.selectList(
+                new LambdaQueryWrapperX<ResearchApplicationDO>()
+                        .eqIfPresent(ResearchApplicationDO::getStatus, reqVO.getStatus()));
+        if (CollUtil.isEmpty(applications)) {
+            return Collections.emptyList();
+        }
+        Map<Long, ResearchApplicationDO> applicationMap = applications.stream()
+                .collect(Collectors.toMap(ResearchApplicationDO::getId, application -> application, (a, b) -> a));
+
+        // 2. 鎴愬憳瀛愯〃鑱氬悎锛氬鍚� + 鐢佃瘽 + 鍗曚綅 鐩稿悓瑙嗕负鍚屼竴鎴愬憳
+        String keyword = StrUtil.trimToNull(reqVO.getKeyword());
+        Map<String, ResearchApplicationMemberStatisticsRespVO> resultMap = new LinkedHashMap<>();
+        Map<String, Set<String>> roleMap = new HashMap<>();
+        Map<String, Set<Long>> projectIdMap = new HashMap<>();
+        Map<String, Set<String>> projectTitleMap = new HashMap<>();
+        for (ResearchApplicationMemberDO member : memberMapper.selectList()) {
+            ResearchApplicationDO application = applicationMap.get(member.getApplicationId());
+            if (application == null || !matchesMemberKeyword(member, keyword)) {
+                continue;
+            }
+            String key = buildMemberKey(member);
+            ResearchApplicationMemberStatisticsRespVO vo = resultMap.computeIfAbsent(key, k -> {
+                ResearchApplicationMemberStatisticsRespVO item = new ResearchApplicationMemberStatisticsRespVO();
+                item.setName(StrUtil.trim(member.getName()));
+                item.setContactPhone(StrUtil.trim(member.getContactPhone()));
+                item.setWorkUnit(StrUtil.trim(member.getWorkUnit()));
+                item.setJobTitle(StrUtil.trim(member.getJobTitle()));
+                return item;
+            });
+            if (StrUtil.isBlank(vo.getJobTitle()) && StrUtil.isNotBlank(member.getJobTitle())) {
+                vo.setJobTitle(StrUtil.trim(member.getJobTitle()));
+            }
+            if (StrUtil.isNotBlank(member.getMemberRole())) {
+                roleMap.computeIfAbsent(key, k -> new LinkedHashSet<>()).add(StrUtil.trim(member.getMemberRole()));
+            }
+            projectIdMap.computeIfAbsent(key, k -> new LinkedHashSet<>()).add(member.getApplicationId());
+            if (StrUtil.isNotBlank(application.getTitle())) {
+                projectTitleMap.computeIfAbsent(key, k -> new LinkedHashSet<>()).add(application.getTitle());
+            }
+        }
+
+        // 3. 姹囨�绘垚鍛樼淮搴︾粨鏋�
+        List<ResearchApplicationMemberStatisticsRespVO> result = new ArrayList<>(resultMap.size());
+        for (Map.Entry<String, ResearchApplicationMemberStatisticsRespVO> entry : resultMap.entrySet()) {
+            String key = entry.getKey();
+            ResearchApplicationMemberStatisticsRespVO vo = entry.getValue();
+            vo.setMemberRoles(String.join("銆�", roleMap.getOrDefault(key, Collections.emptySet())));
+            vo.setProjectCount(projectIdMap.getOrDefault(key, Collections.emptySet()).size());
+            vo.setProjectTitles(String.join("锛�", projectTitleMap.getOrDefault(key, Collections.emptySet())));
+            result.add(vo);
+        }
+        result.sort((a, b) -> {
+            int compare = Integer.compare(
+                    b.getProjectCount() == null ? 0 : b.getProjectCount(),
+                    a.getProjectCount() == null ? 0 : a.getProjectCount());
+            return compare != 0 ? compare : StrUtil.compare(a.getName(), b.getName(), true);
+        });
+        return result;
+    }
+
+    /** 淇濆瓨鐢虫姤涔︽垚鍛樺瓙琛紙璋冪敤鏂归渶鑷淇濊瘉 update 鍦烘櫙宸插厛鍒犻櫎鏃ф暟鎹級 */
+    private void saveMembers(Long applicationId, List<ResearchApplicationMemberSaveReqVO> members) {
+        if (CollUtil.isEmpty(members)) {
+            return;
+        }
+        List<ResearchApplicationMemberDO> list = new ArrayList<>(members.size());
+        int index = 1;
+        for (ResearchApplicationMemberSaveReqVO member : members) {
+            if (member == null || StrUtil.isBlank(member.getName())) {
+                continue;
+            }
+            list.add(ResearchApplicationMemberDO.builder()
+                    .applicationId(applicationId)
+                    .name(StrUtil.trim(member.getName()))
+                    .contactPhone(StrUtil.trim(member.getContactPhone()))
+                    .workUnit(StrUtil.trim(member.getWorkUnit()))
+                    .jobTitle(StrUtil.trim(member.getJobTitle()))
+                    .memberRole(StrUtil.trim(member.getMemberRole()))
+                    .sortNo(member.getSortNo() != null ? member.getSortNo() : index)
+                    .build());
+            index++;
+        }
+        if (CollUtil.isNotEmpty(list)) {
+            memberMapper.insertBatch(list);
+        }
+    }
+
+    private String buildMemberKey(ResearchApplicationMemberDO member) {
+        return StrUtil.join("|", StrUtil.nullToEmpty(member.getName()).trim(),
+                StrUtil.nullToEmpty(member.getContactPhone()).trim(),
+                StrUtil.nullToEmpty(member.getWorkUnit()).trim());
+    }
+
+    private boolean matchesMemberKeyword(ResearchApplicationMemberDO member, String keyword) {
+        if (StrUtil.isBlank(keyword)) {
+            return true;
+        }
+        return StrUtil.containsIgnoreCase(member.getName(), keyword)
+                || StrUtil.containsIgnoreCase(member.getContactPhone(), keyword)
+                || StrUtil.containsIgnoreCase(member.getWorkUnit(), keyword);
+    }
+
+    @Override
     public PageResult<ResearchApplicationDO> getApplicationPage(ResearchApplicationPageReqVO pageReqVO) {
         return applicationMapper.selectPage(pageReqVO);
     }
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
index cf90237..4de01bb 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -86,6 +86,9 @@
 
     // ===== 浼佷笟寰俊娲诲姩 =====
     WXCP_ACTIVITY("wxcp_activity", "浼佷笟寰俊娲诲姩灏侀潰"),
+    WXCP_ACTIVITY_DETAIL("wxcp_activity_detail", "浼佷笟寰俊娲诲姩璇︽儏鍥�"),
+    WXCP_ACTIVITY_IMAGE("wxcp_activity_image", "浼佷笟寰俊娲诲姩鍥剧墖閫氱煡"),
+    WXCP_ACTIVITY_FILE("wxcp_activity_file", "浼佷笟寰俊娲诲姩闄勪欢閫氱煡"),
     WXCP_ACTIVITY_SIGNUP("wxcp_activity_signup", "浼佷笟寰俊娲诲姩鎶ュ悕");
 
     private final String type;
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/storage/StorageFileUtil.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/storage/StorageFileUtil.java
index c4854fd..a0bfc83 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/storage/StorageFileUtil.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/storage/StorageFileUtil.java
@@ -23,6 +23,7 @@
 import javax.crypto.SecretKey;
 import java.io.File;
 import java.math.BigDecimal;
+import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
@@ -461,7 +462,10 @@
         }
         String domain = StringUtils.trimTrailingCharacter(properties.getDomain(), '/');
         String prefix = properties.getUrlPrefix().startsWith("/") ? properties.getUrlPrefix() : "/" + properties.getUrlPrefix();
-        String baseUrl = domain + prefix + actionPath + uidFilename;
+        // 鏂囦欢鍚嶅彲鑳藉寘鍚腑鏂囥�佺┖鏍肩瓑瀛楃锛屽繀椤荤紪鐮佸悗鍐嶆嫾鎺ュ埌 URL锛屽惁鍒欓儴鍒嗗鎴风/瀹瑰櫒浼氱洿鎺ヨ繑鍥� 400
+        String encodedUidFilename = URLEncoder.encode(uidFilename, StandardCharsets.UTF_8)
+                .replace("+", "%20");
+        String baseUrl = domain + prefix + actionPath + encodedUidFilename;
 
         // -1 琛ㄧず姘镐箙鏈夋晥锛屼笉鐢熸垚 token锛屾敼涓� publicKey 缁勫悎鏍¢獙
         if (expired != null && BigDecimal.valueOf(-1L).compareTo(expired) == 0) {
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/UmsMemberProfileChangeController.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/UmsMemberProfileChangeController.java
new file mode 100644
index 0000000..bd382e6
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/UmsMemberProfileChangeController.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.ums.controller.admin.member;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.UmsMemberRespVO;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangePageReqVO;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangeRespVO;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangeSaveReqVO;
+import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberProfileChangeDO;
+import cn.iocoder.yudao.module.ums.service.member.UmsMemberProfileChangeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 浼氬憳璧勬枡鑷富缁存姢")
+@RestController
+@RequestMapping("/ums/member/profile")
+@Validated
+public class UmsMemberProfileChangeController {
+
+    @Resource
+    private UmsMemberProfileChangeService profileChangeService;
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱褰撳墠鐧诲綍浼氬憳鐨勪釜浜鸿祫鏂�")
+    public CommonResult<UmsMemberRespVO> getMyProfile() {
+        return success(BeanUtils.toBean(profileChangeService.getMyProfile(), UmsMemberRespVO.class));
+    }
+
+    @GetMapping("/change/latest")
+    @Operation(summary = "鑾峰緱褰撳墠浼氬憳鏈�杩戜竴娆¤祫鏂欏彉鏇寸敵璇�")
+    public CommonResult<UmsMemberProfileChangeRespVO> getLatestChange() {
+        return success(BeanUtils.toBean(profileChangeService.getLatestChange(),
+                UmsMemberProfileChangeRespVO.class));
+    }
+
+    @PostMapping("/change/apply")
+    @Operation(summary = "鎻愪氦璧勬枡鍙樻洿鐢宠")
+    public CommonResult<Long> applyChange(@Valid @RequestBody UmsMemberProfileChangeSaveReqVO reqVO) {
+        return success(profileChangeService.applyChange(reqVO));
+    }
+
+    @GetMapping("/change/page")
+    @Operation(summary = "鑾峰緱璧勬枡鍙樻洿鐢宠鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('ums:member:change:query')")
+    public CommonResult<PageResult<UmsMemberProfileChangeRespVO>> getChangePage(
+            @Valid UmsMemberProfileChangePageReqVO reqVO) {
+        PageResult<UmsMemberProfileChangeDO> pageResult = profileChangeService.getChangePage(reqVO);
+        return success(new PageResult<>(BeanUtils.toBean(pageResult.getList(),
+                UmsMemberProfileChangeRespVO.class), pageResult.getTotal()));
+    }
+
+    @PutMapping("/change/approve")
+    @Operation(summary = "瀹℃牳閫氳繃璧勬枡鍙樻洿鐢宠")
+    @Parameter(name = "id", description = "鐢宠缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('ums:member:change:audit')")
+    public CommonResult<Boolean> approveChange(@RequestParam("id") Long id) {
+        profileChangeService.approve(id);
+        return success(true);
+    }
+
+    @PutMapping("/change/reject")
+    @Operation(summary = "椹冲洖璧勬枡鍙樻洿鐢宠")
+    @Parameter(name = "id", description = "鐢宠缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('ums:member:change:audit')")
+    public CommonResult<Boolean> rejectChange(@RequestParam("id") Long id,
+                                              @RequestParam("remark") String remark) {
+        profileChangeService.reject(id, remark);
+        return success(true);
+    }
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberRespVO.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberRespVO.java
index 58b4baa..7944a0a 100644
--- a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberRespVO.java
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberRespVO.java
@@ -75,6 +75,12 @@
     @Schema(description = "鏈夋晥鏈熻嚦", example = "2028-01-01")
     private LocalDate expireDate;
 
+    @Schema(description = "鏄惁缂寸撼浼氳垂", example = "true")
+    private Boolean feePaid;
+
+    @Schema(description = "缂磋垂骞村害", example = "2026")
+    private Integer feeYear;
+
     @Schema(description = "鐘舵��", example = "1")
     private Integer status;
 
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberSaveReqVO.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberSaveReqVO.java
index cead6bc..6cc37f0 100644
--- a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberSaveReqVO.java
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberSaveReqVO.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.ums.controller.admin.member.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
@@ -66,6 +68,14 @@
     @Schema(description = "鏈夋晥鏈熻嚦", example = "2028-01-01")
     private LocalDate expireDate;
 
+    @Schema(description = "鏄惁缂寸撼浼氳垂", example = "true")
+    private Boolean feePaid;
+
+    @Schema(description = "缂磋垂骞村害锛堝 2026锛夛紱鏍囪涓哄凡缂寸撼鏃跺繀濉�", example = "2026")
+    @Min(value = 2000, message = "缂磋垂骞村害涓嶈兘鏃╀簬 2000 骞�")
+    @Max(value = 2100, message = "缂磋垂骞村害涓嶈兘鏅氫簬 2100 骞�")
+    private Integer feeYear;
+
     @Schema(description = "鐘舵��", example = "1")
     private Integer status;
 
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangePageReqVO.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangePageReqVO.java
new file mode 100644
index 0000000..863b078
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangePageReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "绠$悊鍚庡彴 - 浼氬憳璧勬枡鍙樻洿鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UmsMemberProfileChangePageReqVO extends PageParam {
+
+    @Schema(description = "浼氬憳缂栧彿", example = "HY20261020000001")
+    private String memberNo;
+
+    @Schema(description = "浼氬憳鍚嶇О", example = "寮犱笁")
+    private String memberName;
+
+    @Schema(description = "鐘舵�侊細0-寰呭鏍� 10-宸查�氳繃 20-宸查┏鍥�", example = "0")
+    private Integer status;
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeRespVO.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeRespVO.java
new file mode 100644
index 0000000..60c482e
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeRespVO.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 浼氬憳璧勬枡鍙樻洿鐢宠 Response VO")
+@Data
+public class UmsMemberProfileChangeRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "浼氬憳 ID", example = "1")
+    private Long memberId;
+
+    @Schema(description = "浼氬憳缂栧彿", example = "HY20261020000001")
+    private String memberNo;
+
+    @Schema(description = "浼氬憳鍚嶇О", example = "寮犱笁")
+    private String memberName;
+
+    @Schema(description = "鎻愪氦浜虹敤鎴� ID", example = "100")
+    private Long userId;
+
+    @Schema(description = "鐘舵�侊細0-寰呭鏍� 10-宸查�氳繃 20-宸查┏鍥�", example = "0")
+    private Integer status;
+
+    @Schema(description = "鍙樻洿鍚庣殑鑱旂郴浜�")
+    private String contactUser;
+
+    @Schema(description = "鍙樻洿鍚庣殑鑱旂郴鐢佃瘽")
+    private String contactPhone;
+
+    @Schema(description = "鍙樻洿鍚庣殑閭")
+    private String contactEmail;
+
+    @Schema(description = "鍙樻洿鍚庣殑宸ヤ綔鍗曚綅")
+    private String workUnit;
+
+    @Schema(description = "鍙樻洿鍚庣殑鑱岀О")
+    private String title;
+
+    @Schema(description = "鍙樻洿鍚庣殑鐮旂┒鏂瑰悜")
+    private String researchDirection;
+
+    @Schema(description = "鍙樻洿鍚庣殑鍗曚綅绠�浠�")
+    private String unitIntro;
+
+    @Schema(description = "鍙樻洿鍚庣殑娉曞畾浠h〃浜�")
+    private String legalPerson;
+
+    @Schema(description = "鍙樻洿鍚庣殑缁熶竴绀句細淇$敤浠g爜")
+    private String organizationCode;
+
+    @Schema(description = "鍙樻洿鍚庣殑鍦板尯缂栫爜")
+    private String regionCode;
+
+    @Schema(description = "鍙樻洿鍚庣殑琛屼笟")
+    private String industry;
+
+    @Schema(description = "鍙樻洿鎽樿")
+    private String changeSummary;
+
+    @Schema(description = "瀹℃牳浜虹敤鎴� ID")
+    private Long auditUserId;
+
+    @Schema(description = "瀹℃牳鏃堕棿")
+    private LocalDateTime auditTime;
+
+    @Schema(description = "瀹℃牳鎰忚锛堥┏鍥炲師鍥狅級")
+    private String auditRemark;
+
+    @Schema(description = "鎻愪氦鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeSaveReqVO.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeSaveReqVO.java
new file mode 100644
index 0000000..4a1f816
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/profile/UmsMemberProfileChangeSaveReqVO.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 浼氬憳璧勬枡鍙樻洿鐢宠 Request VO")
+@Data
+public class UmsMemberProfileChangeSaveReqVO {
+
+    @Schema(description = "鑱旂郴浜猴紙鍗曚綅鐢級", example = "鐜嬩簲")
+    @Size(max = 64, message = "鑱旂郴浜洪暱搴︿笉鑳借秴杩� 64 涓瓧绗�")
+    private String contactUser;
+
+    @Schema(description = "鑱旂郴鐢佃瘽", example = "13800138000")
+    @Size(max = 20, message = "鑱旂郴鐢佃瘽闀垮害涓嶈兘瓒呰繃 20 涓瓧绗�")
+    @Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "鑱旂郴鐢佃瘽鏍煎紡涓嶆纭紝璇疯緭鍏ユ纭殑 11 浣嶆墜鏈哄彿")
+    private String contactPhone;
+
+    @Schema(description = "閭", example = "zhangsan@example.com")
+    @Size(max = 128, message = "閭闀垮害涓嶈兘瓒呰繃 128 涓瓧绗�")
+    @Email(message = "閭鏍煎紡涓嶆纭�")
+    private String contactEmail;
+
+    @Schema(description = "宸ヤ綔鍗曚綅锛堜釜浜轰笓鏈夛級", example = "XX澶у")
+    @Size(max = 128, message = "宸ヤ綔鍗曚綅闀垮害涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String workUnit;
+
+    @Schema(description = "鑱岀О锛堜釜浜轰笓鏈夛級", example = "鏁欐巿")
+    @Size(max = 64, message = "鑱岀О闀垮害涓嶈兘瓒呰繃 64 涓瓧绗�")
+    private String title;
+
+    @Schema(description = "鐮旂┒鏂瑰悜", example = "浜哄伐鏅鸿兘涓庢櫤鑳藉埗閫�")
+    @Size(max = 256, message = "鐮旂┒鏂瑰悜闀垮害涓嶈兘瓒呰繃 256 涓瓧绗�")
+    private String researchDirection;
+
+    @Schema(description = "鍗曚綅绠�浠嬶紙鍗曚綅涓撴湁锛�", example = "涓撴敞浜庢櫤鑳藉埗閫犻鍩�")
+    @Size(max = 512, message = "鍗曚綅绠�浠嬮暱搴︿笉鑳借秴杩� 512 涓瓧绗�")
+    private String unitIntro;
+
+    @Schema(description = "娉曞畾浠h〃浜猴紙鍗曚綅涓撴湁锛�", example = "鏉庡洓")
+    @Size(max = 64, message = "娉曞畾浠h〃浜洪暱搴︿笉鑳借秴杩� 64 涓瓧绗�")
+    private String legalPerson;
+
+    @Schema(description = "缁熶竴绀句細淇$敤浠g爜", example = "91440101MA5XXXXXX")
+    @Size(max = 64, message = "缁熶竴绀句細淇$敤浠g爜闀垮害涓嶈兘瓒呰繃 64 涓瓧绗�")
+    private String organizationCode;
+
+    @Schema(description = "鍦板尯缂栫爜", example = "110000")
+    @Size(max = 32, message = "鍦板尯缂栫爜闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�")
+    private String regionCode;
+
+    @Schema(description = "琛屼笟", example = "1")
+    @Size(max = 32, message = "琛屼笟闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�")
+    private String industry;
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberDO.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberDO.java
index db74a6b..5b7994e 100644
--- a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberDO.java
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberDO.java
@@ -83,6 +83,12 @@
     /** 鏈夋晥鏈熻嚦 */
     private LocalDate expireDate;
 
+    /** 鏄惁缂寸撼浼氳垂 */
+    private Boolean feePaid;
+
+    /** 缂磋垂骞村害锛堝 2026锛� */
+    private Integer feeYear;
+
     /** 鐘舵�侊細1-姝e父 0-鏆傚仠 */
     private Integer status;
 
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberProfileChangeDO.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberProfileChangeDO.java
new file mode 100644
index 0000000..82ebd0a
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberProfileChangeDO.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.ums.dal.dataobject.member;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 浼氬憳璧勬枡鍙樻洿鐢宠 DO
+ *
+ * <p>浼氬憳鑷姪淇敼璧勬枡鍚庣敓鎴愪竴鏉″緟瀹℃牳璁板綍锛岀鐞嗗憳瀹℃牳閫氳繃鍚庢墠鍐欏洖浼氬憳涓昏〃銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("ums_member_profile_change")
+@KeySequence("ums_member_profile_change_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UmsMemberProfileChangeDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /** 浼氬憳 ID */
+    private Long memberId;
+
+    /** 浼氬憳缂栧彿锛堝揩鐓э級 */
+    private String memberNo;
+
+    /** 浼氬憳鍚嶇О锛堝揩鐓э級 */
+    private String memberName;
+
+    /** 鎻愪氦浜虹敤鎴� ID */
+    private Long userId;
+
+    /** 鐘舵�侊細0-寰呭鏍� 10-宸查�氳繃 20-宸查┏鍥� */
+    private Integer status;
+
+    /** 鍙樻洿鍚庣殑鑱旂郴浜� */
+    private String contactUser;
+
+    /** 鍙樻洿鍚庣殑鑱旂郴鐢佃瘽 */
+    private String contactPhone;
+
+    /** 鍙樻洿鍚庣殑閭 */
+    private String contactEmail;
+
+    /** 鍙樻洿鍚庣殑宸ヤ綔鍗曚綅 */
+    private String workUnit;
+
+    /** 鍙樻洿鍚庣殑鑱岀О */
+    private String title;
+
+    /** 鍙樻洿鍚庣殑鐮旂┒鏂瑰悜 */
+    private String researchDirection;
+
+    /** 鍙樻洿鍚庣殑鍗曚綅绠�浠� */
+    private String unitIntro;
+
+    /** 鍙樻洿鍚庣殑娉曞畾浠h〃浜� */
+    private String legalPerson;
+
+    /** 鍙樻洿鍚庣殑缁熶竴绀句細淇$敤浠g爜 */
+    private String organizationCode;
+
+    /** 鍙樻洿鍚庣殑鍦板尯缂栫爜 */
+    private String regionCode;
+
+    /** 鍙樻洿鍚庣殑琛屼笟 */
+    private String industry;
+
+    /** 鍙樻洿鎽樿锛堝瓧娈电骇宸紓锛屼緵瀹℃牳鏌ョ湅锛� */
+    private String changeSummary;
+
+    /** 瀹℃牳浜虹敤鎴� ID */
+    private Long auditUserId;
+
+    /** 瀹℃牳鏃堕棿 */
+    private LocalDateTime auditTime;
+
+    /** 瀹℃牳鎰忚锛堥┏鍥炲師鍥狅級 */
+    private String auditRemark;
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/mysql/member/UmsMemberProfileChangeMapper.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/mysql/member/UmsMemberProfileChangeMapper.java
new file mode 100644
index 0000000..8c4dc1b
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/mysql/member/UmsMemberProfileChangeMapper.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.ums.dal.mysql.member;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangePageReqVO;
+import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberProfileChangeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface UmsMemberProfileChangeMapper extends BaseMapperX<UmsMemberProfileChangeDO> {
+
+    default UmsMemberProfileChangeDO selectPendingByMemberId(Long memberId) {
+        return selectOne(new LambdaQueryWrapperX<UmsMemberProfileChangeDO>()
+                .eq(UmsMemberProfileChangeDO::getMemberId, memberId)
+                .eq(UmsMemberProfileChangeDO::getStatus, 0)
+                .last("LIMIT 1"));
+    }
+
+    default UmsMemberProfileChangeDO selectLatestByMemberId(Long memberId) {
+        List<UmsMemberProfileChangeDO> list = selectList(new LambdaQueryWrapperX<UmsMemberProfileChangeDO>()
+                .eq(UmsMemberProfileChangeDO::getMemberId, memberId)
+                .orderByDesc(UmsMemberProfileChangeDO::getId)
+                .last("LIMIT 1"));
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    default PageResult<UmsMemberProfileChangeDO> selectPage(UmsMemberProfileChangePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<UmsMemberProfileChangeDO>()
+                .likeIfPresent(UmsMemberProfileChangeDO::getMemberNo, reqVO.getMemberNo())
+                .likeIfPresent(UmsMemberProfileChangeDO::getMemberName, reqVO.getMemberName())
+                .eqIfPresent(UmsMemberProfileChangeDO::getStatus, reqVO.getStatus())
+                .orderByDesc(UmsMemberProfileChangeDO::getId));
+    }
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/ErrorCodeConstants.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/ErrorCodeConstants.java
index 4763ecd..99deaa6 100644
--- a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/ErrorCodeConstants.java
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/ErrorCodeConstants.java
@@ -25,5 +25,14 @@
     ErrorCode MEMBER_GRADE_NOT_MATCH_TYPE = new ErrorCode(1_030_108_104, "浼氬憳绛夌骇涓庝細鍛樼被鍨嬩笉鍖归厤锛氫釜浜轰細鍛樹笉鑳介�夋嫨鍗曚綅绛夌骇锛屽崟浣嶄細鍛樹笉鑳介�夋嫨涓汉绛夌骇");
     ErrorCode MEMBER_ARCHIVE_FAIL_STATUS = new ErrorCode(1_030_108_105, "褰掓。澶辫触锛氫細鍛�({})褰撳墠鐘舵�佷负銆攞}銆曪紝鍙湁銆屾甯搞�嶇姸鎬佺殑浼氬憳鎵嶈兘褰掓。鍒版。妗堢鐞嗭紝璇峰厛鍚敤璇ヤ細鍛�");
     ErrorCode MEMBER_ALREADY_ARCHIVED = new ErrorCode(1_030_108_106, "閲嶅褰掓。锛氫細鍛�({})宸蹭簬姝ゅ墠褰掓。骞剁敓鎴愭。妗�(缂栧彿={})锛岃鍕块噸澶嶅綊妗o紝濡傞渶鏌ョ湅璇峰墠寰�妗f绠$悊妯″潡");
+    ErrorCode MEMBER_FEE_YEAR_REQUIRED = new ErrorCode(1_030_108_107, "浼氳垂缂寸撼骞村害涓虹┖锛氬凡鏍囪涓恒�屽凡缂寸撼浼氳垂銆嶏紝璇烽�夋嫨缂磋垂骞村害锛堜緥濡� 2026锛夊悗鍐嶄繚瀛�");
+
+    // ========== 浼氬憳璧勬枡鍙樻洿鐢宠锛�1-030-108-108 ~ 108-119锛� ==========
+    ErrorCode MEMBER_PROFILE_NOT_LINKED = new ErrorCode(1_030_108_108, "褰撳墠鐧诲綍璐﹀彿鏈叧鑱斾細鍛橈紝鏃犳硶缁存姢涓汉璧勬枡锛岃鑱旂郴瀛︿細绠$悊鍛樼粦瀹氫細鍛樿处鍙�");
+    ErrorCode MEMBER_PROFILE_CHANGE_NOT_EXISTS = new ErrorCode(1_030_108_109, "璧勬枡鍙樻洿鐢宠涓嶅瓨鍦�");
+    ErrorCode MEMBER_PROFILE_CHANGE_PENDING_EXISTS = new ErrorCode(1_030_108_110, "鎮ㄥ凡鏈変竴鏉″緟瀹℃牳鐨勮祫鏂欏彉鏇寸敵璇凤紝璇风瓑寰呯鐞嗗憳瀹℃牳鍚庡啀鎻愪氦鏂扮殑鍙樻洿");
+    ErrorCode MEMBER_PROFILE_CHANGE_STATUS_INVALID = new ErrorCode(1_030_108_111, "璧勬枡鍙樻洿鐢宠({})褰撳墠鐘舵�佷负銆攞}銆曪紝鍙湁銆屽緟瀹℃牳銆嶇殑鐢宠鎵嶈兘瀹℃牳");
+    ErrorCode MEMBER_PROFILE_CHANGE_REJECT_REASON_REQUIRED = new ErrorCode(1_030_108_112, "椹冲洖璧勬枡鍙樻洿鐢宠鏃跺繀椤诲~鍐欓┏鍥炲師鍥狅紝璇疯ˉ鍏呭師鍥犲悗鍐嶆搷浣�");
+    ErrorCode MEMBER_PROFILE_CHANGE_EMPTY = new ErrorCode(1_030_108_113, "璧勬枡鏈彂鐢熶换浣曞彉鏇达紝鏃犻渶鎻愪氦瀹℃牳");
 
 }
\ No newline at end of file
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/UmsMemberProfileChangeStatusEnum.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/UmsMemberProfileChangeStatusEnum.java
new file mode 100644
index 0000000..c190d0c
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/UmsMemberProfileChangeStatusEnum.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.ums.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 浼氬憳璧勬枡鍙樻洿鐢宠鐘舵��
+ */
+@Getter
+@AllArgsConstructor
+public enum UmsMemberProfileChangeStatusEnum {
+
+    PENDING(0, "寰呭鏍�"),
+    APPROVED(10, "宸查�氳繃"),
+    REJECTED(20, "宸查┏鍥�");
+
+    private final Integer status;
+    private final String name;
+
+    public static String nameOf(Integer status) {
+        for (UmsMemberProfileChangeStatusEnum value : values()) {
+            if (value.getStatus().equals(status)) {
+                return value.getName();
+            }
+        }
+        return String.valueOf(status);
+    }
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeService.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeService.java
new file mode 100644
index 0000000..f623ff4
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeService.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.ums.service.member;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangePageReqVO;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangeSaveReqVO;
+import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberDO;
+import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberProfileChangeDO;
+
+/**
+ * 浼氬憳璧勬枡鍙樻洿鐢宠 Service 鎺ュ彛
+ */
+public interface UmsMemberProfileChangeService {
+
+    /** 鑾峰彇褰撳墠鐧诲綍璐﹀彿鍏宠仈鐨勪細鍛樿祫鏂� */
+    UmsMemberDO getMyProfile();
+
+    /** 鑾峰彇褰撳墠浼氬憳鏈�杩戜竴娆¤祫鏂欏彉鏇寸敵璇凤紙鐢ㄤ簬灞曠ず瀹℃牳鐘舵�侊級 */
+    UmsMemberProfileChangeDO getLatestChange();
+
+    /** 浼氬憳鎻愪氦璧勬枡鍙樻洿鐢宠 */
+    Long applyChange(UmsMemberProfileChangeSaveReqVO reqVO);
+
+    /** 绠$悊鍛樺垎椤垫煡璇㈣祫鏂欏彉鏇寸敵璇� */
+    PageResult<UmsMemberProfileChangeDO> getChangePage(UmsMemberProfileChangePageReqVO reqVO);
+
+    /** 瀹℃牳閫氳繃锛氭妸鍙樻洿鍐呭鍐欏洖浼氬憳涓昏〃 */
+    void approve(Long id);
+
+    /** 瀹℃牳椹冲洖 */
+    void reject(Long id, String remark);
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeServiceImpl.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeServiceImpl.java
new file mode 100644
index 0000000..e50cc8f
--- /dev/null
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberProfileChangeServiceImpl.java
@@ -0,0 +1,167 @@
+package cn.iocoder.yudao.module.ums.service.member;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangePageReqVO;
+import cn.iocoder.yudao.module.ums.controller.admin.member.vo.profile.UmsMemberProfileChangeSaveReqVO;
+import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberDO;
+import cn.iocoder.yudao.module.ums.dal.dataobject.member.UmsMemberProfileChangeDO;
+import cn.iocoder.yudao.module.ums.dal.mysql.member.UmsMemberMapper;
+import cn.iocoder.yudao.module.ums.dal.mysql.member.UmsMemberProfileChangeMapper;
+import cn.iocoder.yudao.module.ums.enums.UmsMemberProfileChangeStatusEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.ums.enums.ErrorCodeConstants.*;
+
+/**
+ * 浼氬憳璧勬枡鍙樻洿鐢宠 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class UmsMemberProfileChangeServiceImpl implements UmsMemberProfileChangeService {
+
+    private static final int SUMMARY_MAX_LENGTH = 1000;
+
+    @Resource
+    private UmsMemberMapper memberMapper;
+
+    @Resource
+    private UmsMemberProfileChangeMapper changeMapper;
+
+    @Override
+    public UmsMemberDO getMyProfile() {
+        return getLoginMember();
+    }
+
+    @Override
+    public UmsMemberProfileChangeDO getLatestChange() {
+        return changeMapper.selectLatestByMemberId(getLoginMember().getId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long applyChange(UmsMemberProfileChangeSaveReqVO reqVO) {
+        UmsMemberDO member = getLoginMember();
+        if (changeMapper.selectPendingByMemberId(member.getId()) != null) {
+            throw exception(MEMBER_PROFILE_CHANGE_PENDING_EXISTS);
+        }
+        String summary = buildChangeSummary(member, reqVO);
+        if (StrUtil.isBlank(summary)) {
+            throw exception(MEMBER_PROFILE_CHANGE_EMPTY);
+        }
+        UmsMemberProfileChangeDO change = BeanUtils.toBean(reqVO, UmsMemberProfileChangeDO.class, in -> in
+                .setMemberId(member.getId())
+                .setMemberNo(member.getNo())
+                .setMemberName(member.getName())
+                .setUserId(getLoginUserId())
+                .setStatus(UmsMemberProfileChangeStatusEnum.PENDING.getStatus())
+                .setChangeSummary(StrUtil.sub(summary, 0, SUMMARY_MAX_LENGTH)));
+        changeMapper.insert(change);
+        return change.getId();
+    }
+
+    @Override
+    public PageResult<UmsMemberProfileChangeDO> getChangePage(UmsMemberProfileChangePageReqVO reqVO) {
+        return changeMapper.selectPage(reqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void approve(Long id) {
+        UmsMemberProfileChangeDO change = validatePendingChange(id);
+        // 鏄惧紡 set 姣忎釜瀛楁锛氬厑璁告妸璧勬枡娓呯┖锛坲pdateById 浼氬拷鐣� null锛�
+        memberMapper.update(null, new LambdaUpdateWrapper<UmsMemberDO>()
+                .eq(UmsMemberDO::getId, change.getMemberId())
+                .set(UmsMemberDO::getContactUser, change.getContactUser())
+                .set(UmsMemberDO::getContactPhone, change.getContactPhone())
+                .set(UmsMemberDO::getContactEmail, change.getContactEmail())
+                .set(UmsMemberDO::getWorkUnit, change.getWorkUnit())
+                .set(UmsMemberDO::getTitle, change.getTitle())
+                .set(UmsMemberDO::getResearchDirection, change.getResearchDirection())
+                .set(UmsMemberDO::getUnitIntro, change.getUnitIntro())
+                .set(UmsMemberDO::getLegalPerson, change.getLegalPerson())
+                .set(UmsMemberDO::getOrganizationCode, change.getOrganizationCode())
+                .set(UmsMemberDO::getRegionCode, change.getRegionCode())
+                .set(UmsMemberDO::getIndustry, change.getIndustry()));
+        updateAudit(change.getId(), UmsMemberProfileChangeStatusEnum.APPROVED, "瀹℃牳閫氳繃");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void reject(Long id, String remark) {
+        UmsMemberProfileChangeDO change = validatePendingChange(id);
+        if (StrUtil.isBlank(remark)) {
+            throw exception(MEMBER_PROFILE_CHANGE_REJECT_REASON_REQUIRED);
+        }
+        updateAudit(change.getId(), UmsMemberProfileChangeStatusEnum.REJECTED, StrUtil.trim(remark));
+    }
+
+    private UmsMemberDO getLoginMember() {
+        Long userId = getLoginUserId();
+        UmsMemberDO member = userId != null ? memberMapper.selectByUserId(userId) : null;
+        if (member == null) {
+            throw exception(MEMBER_PROFILE_NOT_LINKED);
+        }
+        return member;
+    }
+
+    private UmsMemberProfileChangeDO validatePendingChange(Long id) {
+        UmsMemberProfileChangeDO change = changeMapper.selectById(id);
+        if (change == null) {
+            throw exception(MEMBER_PROFILE_CHANGE_NOT_EXISTS);
+        }
+        if (!UmsMemberProfileChangeStatusEnum.PENDING.getStatus().equals(change.getStatus())) {
+            throw exception(MEMBER_PROFILE_CHANGE_STATUS_INVALID, id,
+                    UmsMemberProfileChangeStatusEnum.nameOf(change.getStatus()));
+        }
+        return change;
+    }
+
+    private void updateAudit(Long id, UmsMemberProfileChangeStatusEnum status, String remark) {
+        changeMapper.updateById(new UmsMemberProfileChangeDO()
+                .setId(id)
+                .setStatus(status.getStatus())
+                .setAuditUserId(getLoginUserId())
+                .setAuditTime(LocalDateTime.now())
+                .setAuditRemark(remark));
+    }
+
+    /** 鐢熸垚瀛楁绾у彉鏇存憳瑕侊紝渚涚鐞嗗憳瀹℃牳鏃跺揩閫熸煡鐪� */
+    private String buildChangeSummary(UmsMemberDO member, UmsMemberProfileChangeSaveReqVO reqVO) {
+        List<String> changes = new ArrayList<>();
+        appendChange(changes, "鑱旂郴浜�", member.getContactUser(), reqVO.getContactUser());
+        appendChange(changes, "鑱旂郴鐢佃瘽", member.getContactPhone(), reqVO.getContactPhone());
+        appendChange(changes, "閭", member.getContactEmail(), reqVO.getContactEmail());
+        appendChange(changes, "宸ヤ綔鍗曚綅", member.getWorkUnit(), reqVO.getWorkUnit());
+        appendChange(changes, "鑱岀О", member.getTitle(), reqVO.getTitle());
+        appendChange(changes, "鐮旂┒鏂瑰悜", member.getResearchDirection(), reqVO.getResearchDirection());
+        appendChange(changes, "鍗曚綅绠�浠�", member.getUnitIntro(), reqVO.getUnitIntro());
+        appendChange(changes, "娉曞畾浠h〃浜�", member.getLegalPerson(), reqVO.getLegalPerson());
+        appendChange(changes, "缁熶竴绀句細淇$敤浠g爜", member.getOrganizationCode(), reqVO.getOrganizationCode());
+        appendChange(changes, "鍦板尯缂栫爜", member.getRegionCode(), reqVO.getRegionCode());
+        appendChange(changes, "琛屼笟", member.getIndustry(), reqVO.getIndustry());
+        return String.join("锛�", changes);
+    }
+
+    private void appendChange(List<String> changes, String label, String oldValue, String newValue) {
+        String oldText = StrUtil.blankToDefault(oldValue, "-");
+        String newText = StrUtil.blankToDefault(newValue, "-");
+        if (!oldText.equals(newText)) {
+            changes.add(label + "锛�" + StrUtil.sub(oldText, 0, 60) + " 鈫� " + StrUtil.sub(newText, 0, 60));
+        }
+    }
+
+}
diff --git a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberServiceImpl.java b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberServiceImpl.java
index aa99b99..5b5f9b4 100644
--- a/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberServiceImpl.java
+++ b/yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberServiceImpl.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.ums.service.member;
 
 import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.archive.api.document.ArchiveDocumentApi;
@@ -40,6 +41,13 @@
      * 浼氬憳绯荤粺璐﹀彿榛樿閮ㄩ棬 ID锛坰ystem_dept 涓�屽浼氫細鍛樸�嶏級
      */
     private static final Long MEMBER_DEPT_ID = 129L;
+
+    /**
+     * 鏅�氫細鍛樿鑹� ID锛坰ystem_role銆屾櫘閫氫細鍛樸�嶏級
+     *
+     * <p>璇ヨ鑹插彧鍒嗛厤銆屾垜鐨勮祫鏂欍�嶈彍鍗曪紝涓嶅寘鍚。妗堢鐞嗙瓑鍚庡彴鑿滃崟锛岀敤浜庡疄鐜版潈闄愰殧绂汇��
+     */
+    private static final Long MEMBER_ROLE_ID = 9100L;
 
     public static final String MEMBER_RECORD_TYPE = "ums_member";
 
@@ -117,6 +125,7 @@
 
         // 浼氬憳绛夌骇涓庣被鍨嬪尮閰嶆牎楠�
         validateMemberGrade(createReqVO.getMemberType(), createReqVO.getMemberGrade());
+        validateFeePaid(createReqVO);
 
         UmsMemberDO member = BeanUtils.toBean(createReqVO, UmsMemberDO.class, in -> in
                 .setNo(no)
@@ -135,9 +144,21 @@
     public void updateMember(UmsMemberSaveReqVO updateReqVO) {
         validateMemberExists(updateReqVO.getId());
         validateMemberGrade(updateReqVO.getMemberType(), updateReqVO.getMemberGrade());
+        validateFeePaid(updateReqVO);
+
+        // 鍙栨秷銆屽凡缂寸撼浼氳垂銆嶆椂闇�瑕佹妸缂磋垂骞村害鐪熸娓呯┖锛坲pdateById 浼氬拷鐣� null 瀛楁锛�
+        boolean clearFeeYear = Boolean.FALSE.equals(updateReqVO.getFeePaid());
+        if (clearFeeYear) {
+            updateReqVO.setFeeYear(null);
+        }
 
         UmsMemberDO updateObj = BeanUtils.toBean(updateReqVO, UmsMemberDO.class);
         memberMapper.updateById(updateObj);
+        if (clearFeeYear) {
+            memberMapper.update(null, new LambdaUpdateWrapper<UmsMemberDO>()
+                    .eq(UmsMemberDO::getId, updateReqVO.getId())
+                    .set(UmsMemberDO::getFeeYear, null));
+        }
 
         storageAttachmentApi.updateAttachments("file", MEMBER_RECORD_TYPE,
                 updateReqVO.getId(), updateReqVO.getBlobIds());
@@ -213,6 +234,7 @@
                     .setNickname(nickname)
                     .setDeptId(MEMBER_DEPT_ID)
                     .setPostIds(Collections.emptySet())
+                    .setRoleIds(Collections.singleton(MEMBER_ROLE_ID))
                     .setMobile(mobile)
                     .setPassword(mobile) // 榛樿瀵嗙爜涓烘墜鏈哄彿
                     .setEmail(application.getContactEmail())
@@ -230,6 +252,13 @@
     /**
      * 鏍¢獙浼氬憳绛夌骇涓庣被鍨嬪尮閰嶏細鍗曚綅浼氬憳锛坱ype=2锛変笉鍙�変釜浜轰笓鏈夌瓑绾с��5-鐞嗕簨闀裤��
      */
+    /** 宸茬即绾充細璐规椂蹇呴』濉啓缂磋垂骞村害 */
+    private void validateFeePaid(UmsMemberSaveReqVO reqVO) {
+        if (Boolean.TRUE.equals(reqVO.getFeePaid()) && reqVO.getFeeYear() == null) {
+            throw exception(MEMBER_FEE_YEAR_REQUIRED);
+        }
+    }
+
     private void validateMemberGrade(Integer memberType, String memberGrade) {
         if (memberType == null || StrUtil.isBlank(memberGrade)) {
             return;
diff --git a/yudao-module-vehicle/pom.xml b/yudao-module-vehicle/pom.xml
new file mode 100644
index 0000000..3a887c3
--- /dev/null
+++ b/yudao-module-vehicle/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-module-vehicle</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        vehicle 鍖呬笅锛岀敤杞︾鐞嗘ā鍧楋紙鐢ㄨ溅鐢宠銆侀�愮骇瀹℃壒銆佸鎵圭粨鏋滃弽棣堢敵璇蜂汉锛夈��
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- BPM 宸ヤ綔娴侊紙鐢ㄨ溅瀹℃牳 + 閫愮骇瀹℃壒锛� -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-bpm</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- Web 鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- DB 鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
+        </dependency>
+
+        <!-- 宸ュ叿绫荤浉鍏� -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-excel</artifactId>
+        </dependency>
+
+        <!-- Test 娴嬭瘯鐩稿叧 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/VehicleApplicationController.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/VehicleApplicationController.java
new file mode 100644
index 0000000..ec03d79
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/VehicleApplicationController.java
@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.vehicle.controller.admin.application;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationPageReqVO;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationRespVO;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationSaveReqVO;
+import cn.iocoder.yudao.module.vehicle.dal.dataobject.application.VehicleApplicationDO;
+import cn.iocoder.yudao.module.vehicle.enums.VehicleApplicationStatusEnum;
+import cn.iocoder.yudao.module.vehicle.service.application.VehicleApplicationService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 鐢ㄨ溅鐢宠")
+@RestController
+@RequestMapping("/vehicle/application")
+@Validated
+public class VehicleApplicationController {
+
+    @Resource
+    private VehicleApplicationService applicationService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓鐢ㄨ溅鐢宠")
+    @PreAuthorize("@ss.hasPermission('vehicle:application:create')")
+    public CommonResult<Long> createApplication(@Valid @RequestBody VehicleApplicationSaveReqVO reqVO) {
+        return success(applicationService.createApplication(reqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊鐢ㄨ溅鐢宠")
+    @PreAuthorize("@ss.hasPermission('vehicle:application:update')")
+    public CommonResult<Boolean> updateApplication(@Valid @RequestBody VehicleApplicationSaveReqVO reqVO) {
+        applicationService.updateApplication(reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎鐢ㄨ溅鐢宠")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('vehicle:application:delete')")
+    public CommonResult<Boolean> deleteApplication(@RequestParam("id") Long id) {
+        applicationService.deleteApplication(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鐢ㄨ溅鐢宠")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('vehicle:application:query')")
+    public CommonResult<VehicleApplicationRespVO> getApplication(@RequestParam("id") Long id) {
+        return success(buildRespVO(applicationService.getApplication(id)));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱鐢ㄨ溅鐢宠鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('vehicle:application:query')")
+    public CommonResult<PageResult<VehicleApplicationRespVO>> getApplicationPage(
+            @Valid VehicleApplicationPageReqVO reqVO) {
+        PageResult<VehicleApplicationDO> pageResult = applicationService.getApplicationPage(reqVO);
+        return success(new PageResult<>(buildRespVOList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    @PostMapping("/submit")
+    @Operation(summary = "鎻愪氦鐢ㄨ溅瀹℃壒")
+    @PreAuthorize("@ss.hasPermission('vehicle:application:submit')")
+    public CommonResult<Boolean> submitApplication(@RequestParam("id") Long id,
+                                                   @RequestParam("processDefinitionKey") String processDefinitionKey) {
+        applicationService.submitApplication(id, processDefinitionKey);
+        return success(true);
+    }
+
+    @GetMapping("/approve-process-list")
+    @Operation(summary = "鑾峰彇鐢ㄨ溅瀹℃壒娴佺▼鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('vehicle:application:query')")
+    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
+        return success(applicationService.getApproveProcessDefinitionList());
+    }
+
+    private VehicleApplicationRespVO buildRespVO(VehicleApplicationDO application) {
+        if (application == null) {
+            return null;
+        }
+        return buildRespVOList(java.util.Collections.singletonList(application)).get(0);
+    }
+
+    private List<VehicleApplicationRespVO> buildRespVOList(List<VehicleApplicationDO> list) {
+        return BeanUtils.toBean(list, VehicleApplicationRespVO.class, vo ->
+                vo.setStatusName(VehicleApplicationStatusEnum.nameOf(vo.getStatus())));
+    }
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationPageReqVO.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationPageReqVO.java
new file mode 100644
index 0000000..422b3f8
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationPageReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.vehicle.controller.admin.application.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐢ㄨ溅鐢宠鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class VehicleApplicationPageReqVO extends PageParam {
+
+    @Schema(description = "鐢宠鍗曞彿", example = "YC20261020000001")
+    private String no;
+
+    @Schema(description = "鐢宠浜哄鍚�", example = "寮犱笁")
+    private String applicantName;
+
+    @Schema(description = "鐩殑鍦�", example = "鍗楅�氬競鏀垮姟涓績")
+    private String destination;
+
+    @Schema(description = "鐘舵�侊細0-鑽夌 15-瀹℃壒涓� 20-宸查�氳繃 35-宸查┏鍥�", example = "15")
+    private Integer status;
+
+    @Schema(description = "鐢ㄨ溅鏃堕棿鑼冨洿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime[] useTime;
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationRespVO.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationRespVO.java
new file mode 100644
index 0000000..5e1062f
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationRespVO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.vehicle.controller.admin.application.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐢ㄨ溅鐢宠 Response VO")
+@Data
+public class VehicleApplicationRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鐢宠鍗曞彿", example = "YC20261020000001")
+    private String no;
+
+    @Schema(description = "鐢宠浜虹敤鎴� ID", example = "1024")
+    private Long applicantId;
+
+    @Schema(description = "鐢宠浜哄鍚�", example = "寮犱笁")
+    private String applicantName;
+
+    @Schema(description = "鐢ㄨ溅寮�濮嬫椂闂�")
+    private LocalDateTime useStartTime;
+
+    @Schema(description = "鐢ㄨ溅缁撴潫鏃堕棿")
+    private LocalDateTime useEndTime;
+
+    @Schema(description = "鍑哄彂鍦�", example = "瀛﹂櫌琛屾斂妤�")
+    private String departure;
+
+    @Schema(description = "鐩殑鍦�", example = "鍗楅�氬競鏀垮姟涓績")
+    private String destination;
+
+    @Schema(description = "闅忚浜烘暟", example = "3")
+    private Integer passengers;
+
+    @Schema(description = "鐢ㄨ溅浜嬬敱", example = "鍙傚姞浜ф暀铻嶅悎椤圭洰瀵规帴浼�")
+    private String reason;
+
+    @Schema(description = "杞︾墝鍙�", example = "鑻廎12345")
+    private String vehicleNo;
+
+    @Schema(description = "鐘舵��", example = "15")
+    private Integer status;
+
+    @Schema(description = "鐘舵�佸悕绉�", example = "瀹℃壒涓�")
+    private String statusName;
+
+    @Schema(description = "BPM 娴佺▼瀹炰緥 ID")
+    private String processInstanceId;
+
+    @Schema(description = "瀹℃壒閫氳繃鏃堕棿")
+    private LocalDateTime approveTime;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationSaveReqVO.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationSaveReqVO.java
new file mode 100644
index 0000000..34ab663
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/controller/admin/application/vo/VehicleApplicationSaveReqVO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.vehicle.controller.admin.application.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐢ㄨ溅鐢宠鏂板/淇敼 Request VO")
+@Data
+public class VehicleApplicationSaveReqVO {
+
+    @Schema(description = "缂栧彿锛堜慨鏀规椂蹇呭~锛�", example = "1")
+    private Long id;
+
+    @Schema(description = "鐢ㄨ溅寮�濮嬫椂闂�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "鐢ㄨ溅寮�濮嬫椂闂翠笉鑳戒负绌�")
+    private LocalDateTime useStartTime;
+
+    @Schema(description = "鐢ㄨ溅缁撴潫鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "鐢ㄨ溅缁撴潫鏃堕棿涓嶈兘涓虹┖")
+    private LocalDateTime useEndTime;
+
+    @Schema(description = "鍑哄彂鍦�", requiredMode = Schema.RequiredMode.REQUIRED, example = "瀛﹂櫌琛屾斂妤�")
+    @NotBlank(message = "鍑哄彂鍦颁笉鑳戒负绌�")
+    @Size(max = 128, message = "鍑哄彂鍦颁笉鑳借秴杩� 128 涓瓧绗�")
+    private String departure;
+
+    @Schema(description = "鐩殑鍦�", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍗楅�氬競鏀垮姟涓績")
+    @NotBlank(message = "鐩殑鍦颁笉鑳戒负绌�")
+    @Size(max = 128, message = "鐩殑鍦颁笉鑳借秴杩� 128 涓瓧绗�")
+    private String destination;
+
+    @Schema(description = "闅忚浜烘暟", example = "3")
+    @Min(value = 1, message = "闅忚浜烘暟蹇呴』澶т簬 0")
+    private Integer passengers;
+
+    @Schema(description = "鐢ㄨ溅浜嬬敱", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍙傚姞浜ф暀铻嶅悎椤圭洰瀵规帴浼�")
+    @NotBlank(message = "鐢ㄨ溅浜嬬敱涓嶈兘涓虹┖")
+    @Size(max = 512, message = "鐢ㄨ溅浜嬬敱涓嶈兘瓒呰繃 512 涓瓧绗�")
+    private String reason;
+
+    @Schema(description = "杞︾墝鍙凤紙鍙�夛級", example = "鑻廎12345")
+    @Size(max = 32, message = "杞︾墝鍙蜂笉鑳借秴杩� 32 涓瓧绗�")
+    private String vehicleNo;
+
+    @Schema(description = "澶囨敞")
+    @Size(max = 512, message = "澶囨敞涓嶈兘瓒呰繃 512 涓瓧绗�")
+    private String remark;
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/dataobject/application/VehicleApplicationDO.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/dataobject/application/VehicleApplicationDO.java
new file mode 100644
index 0000000..7831fb3
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/dataobject/application/VehicleApplicationDO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.vehicle.dal.dataobject.application;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鐢ㄨ溅鐢宠 DO
+ */
+@TableName("vehicle_application")
+@KeySequence("vehicle_application_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class VehicleApplicationDO extends BaseDO {
+
+    @TableId
+    private Long id;
+
+    /** 鐢宠鍗曞彿 */
+    private String no;
+
+    /** 鐢宠浜虹敤鎴� ID */
+    private Long applicantId;
+
+    /** 鐢宠浜哄鍚� */
+    private String applicantName;
+
+    /** 鐢ㄨ溅寮�濮嬫椂闂� */
+    private LocalDateTime useStartTime;
+
+    /** 鐢ㄨ溅缁撴潫鏃堕棿 */
+    private LocalDateTime useEndTime;
+
+    /** 鍑哄彂鍦� */
+    private String departure;
+
+    /** 鐩殑鍦� */
+    private String destination;
+
+    /** 闅忚浜烘暟 */
+    private Integer passengers;
+
+    /** 鐢ㄨ溅浜嬬敱 */
+    private String reason;
+
+    /** 杞︾墝鍙凤紙鍙�夛級 */
+    private String vehicleNo;
+
+    /** 鐘舵�侊細0-鑽夌 15-瀹℃壒涓� 20-宸查�氳繃 35-宸查┏鍥� */
+    private Integer status;
+
+    /** BPM 娴佺▼瀹炰緥 ID */
+    private String processInstanceId;
+
+    /** 瀹℃壒閫氳繃鏃堕棿 */
+    private LocalDateTime approveTime;
+
+    /** 澶囨敞 */
+    private String remark;
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/mysql/application/VehicleApplicationMapper.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/mysql/application/VehicleApplicationMapper.java
new file mode 100644
index 0000000..d9401d9
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/mysql/application/VehicleApplicationMapper.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.vehicle.dal.mysql.application;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationPageReqVO;
+import cn.iocoder.yudao.module.vehicle.dal.dataobject.application.VehicleApplicationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface VehicleApplicationMapper extends BaseMapperX<VehicleApplicationDO> {
+
+    default VehicleApplicationDO selectByNo(String no) {
+        return selectOne(VehicleApplicationDO::getNo, no);
+    }
+
+    default PageResult<VehicleApplicationDO> selectPage(VehicleApplicationPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<VehicleApplicationDO>()
+                .likeIfPresent(VehicleApplicationDO::getNo, reqVO.getNo())
+                .likeIfPresent(VehicleApplicationDO::getApplicantName, reqVO.getApplicantName())
+                .likeIfPresent(VehicleApplicationDO::getDestination, reqVO.getDestination())
+                .eqIfPresent(VehicleApplicationDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(VehicleApplicationDO::getUseStartTime, reqVO.getUseTime())
+                .orderByDesc(VehicleApplicationDO::getId));
+    }
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/RedisKeyConstants.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/RedisKeyConstants.java
new file mode 100644
index 0000000..70cd43b
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/RedisKeyConstants.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.vehicle.dal.redis;
+
+/**
+ * Vehicle Redis Key 鏋氫妇绫�
+ */
+public interface RedisKeyConstants {
+
+    /**
+     * 鍗曞彿鑷搴忓垪
+     * <p>
+     * KEY 鏍煎紡锛歷ehicle:no:{prefix}{yyyyMMdd}
+     */
+    String NO = "vehicle:no:";
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/VehicleNoRedisDAO.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/VehicleNoRedisDAO.java
new file mode 100644
index 0000000..ffb9577
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/dal/redis/VehicleNoRedisDAO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.vehicle.dal.redis;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+/**
+ * Vehicle 缂栧彿 Redis DAO
+ */
+@Repository
+public class VehicleNoRedisDAO {
+
+    /** 鐢ㄨ溅鐢宠鍗曞彿鍓嶇紑 */
+    public static final String APPLICATION_NO_PREFIX = "YC";
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    public String generate(String prefix) {
+        String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
+        String key = RedisKeyConstants.NO + noPrefix;
+        Long no = stringRedisTemplate.opsForValue().increment(key);
+        stringRedisTemplate.expire(key, Duration.ofDays(1L));
+        return noPrefix + String.format("%06d", no);
+    }
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/ErrorCodeConstants.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/ErrorCodeConstants.java
new file mode 100644
index 0000000..b0e322b
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/ErrorCodeConstants.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.vehicle.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Vehicle 閿欒鐮佹灇涓剧被
+ * <p>
+ * vehicle 绯荤粺锛屼娇鐢� 1-030-110-000 娈碉紙鎺ュ湪 asset 1-030-109 涔嬪悗锛�
+ */
+public interface ErrorCodeConstants {
+
+    ErrorCode VEHICLE_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_110_000, "鐢ㄨ溅鐢宠涓嶅瓨鍦紝璇峰埛鏂板垪琛ㄥ悗閲嶈瘯");
+    ErrorCode VEHICLE_APPLICATION_NO_EXISTS = new ErrorCode(1_030_110_001, "鐢熸垚鐢ㄨ溅鐢宠鍗曞彿澶辫触锛岃閲嶆柊鎻愪氦");
+    ErrorCode VEHICLE_APPLICATION_UPDATE_FAIL = new ErrorCode(1_030_110_002, "鐢ㄨ溅鐢宠({})褰撳墠鐘舵�佷负銆攞}銆曪紝鍙湁鑽夌鎴栧凡椹冲洖鐘舵�佹墠鑳戒慨鏀�");
+    ErrorCode VEHICLE_APPLICATION_DELETE_FAIL = new ErrorCode(1_030_110_003, "鐢ㄨ溅鐢宠({})褰撳墠鐘舵�佷负銆攞}銆曪紝鍙湁鑽夌鐘舵�佹墠鑳藉垹闄�");
+    ErrorCode VEHICLE_APPLICATION_SUBMIT_FAIL = new ErrorCode(1_030_110_004, "鎻愪氦瀹℃壒澶辫触锛氱敤杞︾敵璇�({})褰撳墠鐘舵�佷负銆攞}銆曪紝鍙湁鑽夌鎴栧凡椹冲洖鐘舵�佹墠鑳芥彁浜�");
+    ErrorCode VEHICLE_APPLICATION_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_110_005, "鐢ㄨ溅瀹℃壒鍒嗙被涓嶅瓨鍦紝璇峰厛鍦� BPM 妯″潡鍒涘缓銆岀敤杞﹀鎵广�嶅垎绫诲苟鍙戝竷瀹℃壒娴佺▼");
+    ErrorCode VEHICLE_APPLICATION_TIME_INVALID = new ErrorCode(1_030_110_006, "鐢ㄨ溅缁撴潫鏃堕棿蹇呴』鏅氫簬寮�濮嬫椂闂达紝璇疯皟鏁寸敤杞︽椂闂存鍚庨噸鏂版彁浜�");
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/VehicleApplicationStatusEnum.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/VehicleApplicationStatusEnum.java
new file mode 100644
index 0000000..4ca85ce
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/enums/VehicleApplicationStatusEnum.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.vehicle.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鐢ㄨ溅鐢宠鐘舵�佹灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum VehicleApplicationStatusEnum {
+
+    DRAFT(0, "鑽夌"),
+    APPROVING(15, "瀹℃壒涓�"),
+    APPROVED(20, "宸查�氳繃"),
+    REJECTED(35, "宸查┏鍥�");
+
+    private final Integer status;
+    private final String name;
+
+    public static String nameOf(Integer status) {
+        for (VehicleApplicationStatusEnum value : values()) {
+            if (value.status.equals(status)) {
+                return value.name;
+            }
+        }
+        return "鏈煡";
+    }
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationService.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationService.java
new file mode 100644
index 0000000..83e8fec
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationService.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.vehicle.service.application;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationPageReqVO;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationSaveReqVO;
+import cn.iocoder.yudao.module.vehicle.dal.dataobject.application.VehicleApplicationDO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鐢ㄨ溅鐢宠 Service 鎺ュ彛
+ */
+public interface VehicleApplicationService {
+
+    Long createApplication(VehicleApplicationSaveReqVO reqVO);
+
+    void updateApplication(VehicleApplicationSaveReqVO reqVO);
+
+    void deleteApplication(Long id);
+
+    VehicleApplicationDO getApplication(Long id);
+
+    PageResult<VehicleApplicationDO> getApplicationPage(VehicleApplicationPageReqVO reqVO);
+
+    /** 鎻愪氦瀹℃壒锛堣蛋 BPM 閫愮骇瀹℃壒锛� */
+    void submitApplication(Long id, String processDefinitionKey);
+
+    /** BPM 瀹℃壒缁撴灉鍥炲啓 */
+    void updateApplicationAuditStatus(Long id, Integer bpmResult);
+
+    /** 鑾峰彇鐢ㄨ溅瀹℃壒鍙敤鐨勬祦绋嬪畾涔夊垪琛� */
+    List<Map<String, Object>> getApproveProcessDefinitionList();
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationServiceImpl.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationServiceImpl.java
new file mode 100644
index 0000000..7f93bcf
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/VehicleApplicationServiceImpl.java
@@ -0,0 +1,218 @@
+package cn.iocoder.yudao.module.vehicle.service.application;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationPageReqVO;
+import cn.iocoder.yudao.module.vehicle.controller.admin.application.vo.VehicleApplicationSaveReqVO;
+import cn.iocoder.yudao.module.vehicle.dal.dataobject.application.VehicleApplicationDO;
+import cn.iocoder.yudao.module.vehicle.dal.mysql.application.VehicleApplicationMapper;
+import cn.iocoder.yudao.module.vehicle.dal.redis.VehicleNoRedisDAO;
+import cn.iocoder.yudao.module.vehicle.enums.VehicleApplicationStatusEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.vehicle.enums.ErrorCodeConstants.*;
+
+/**
+ * 鐢ㄨ溅鐢宠 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class VehicleApplicationServiceImpl implements VehicleApplicationService {
+
+    /** 鐢ㄨ溅瀹℃壒 BPM 鍒嗙被缂栫爜 */
+    public static final String VEHICLE_APPROVE_CATEGORY_CODE = "vehicle_application_approve";
+
+    @Resource
+    private VehicleApplicationMapper applicationMapper;
+
+    @Resource
+    private VehicleNoRedisDAO noRedisDAO;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+
+    @Resource
+    private BpmCategoryService bpmCategoryService;
+
+    @Resource
+    private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createApplication(VehicleApplicationSaveReqVO reqVO) {
+        validateUseTime(reqVO);
+        Long userId = getLoginUserId();
+        AdminUserRespDTO user = userId != null ? adminUserApi.getUser(userId) : null;
+        VehicleApplicationDO application = BeanUtils.toBean(reqVO, VehicleApplicationDO.class, in -> in
+                .setNo(noRedisDAO.generate(VehicleNoRedisDAO.APPLICATION_NO_PREFIX))
+                .setApplicantId(userId)
+                .setApplicantName(user != null ? user.getNickname() : "鏈煡鐢ㄦ埛")
+                .setStatus(VehicleApplicationStatusEnum.DRAFT.getStatus()));
+        applicationMapper.insert(application);
+        return application.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateApplication(VehicleApplicationSaveReqVO reqVO) {
+        VehicleApplicationDO old = validateApplicationExists(reqVO.getId());
+        if (!VehicleApplicationStatusEnum.DRAFT.getStatus().equals(old.getStatus())
+                && !VehicleApplicationStatusEnum.REJECTED.getStatus().equals(old.getStatus())) {
+            throw exception(VEHICLE_APPLICATION_UPDATE_FAIL, old.getNo(),
+                    VehicleApplicationStatusEnum.nameOf(old.getStatus()));
+        }
+        validateUseTime(reqVO);
+        VehicleApplicationDO updateObj = BeanUtils.toBean(reqVO, VehicleApplicationDO.class);
+        applicationMapper.updateById(updateObj);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteApplication(Long id) {
+        VehicleApplicationDO application = validateApplicationExists(id);
+        if (!VehicleApplicationStatusEnum.DRAFT.getStatus().equals(application.getStatus())) {
+            throw exception(VEHICLE_APPLICATION_DELETE_FAIL, application.getNo(),
+                    VehicleApplicationStatusEnum.nameOf(application.getStatus()));
+        }
+        applicationMapper.deleteById(id);
+    }
+
+    @Override
+    public VehicleApplicationDO getApplication(Long id) {
+        return applicationMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<VehicleApplicationDO> getApplicationPage(VehicleApplicationPageReqVO reqVO) {
+        return applicationMapper.selectPage(reqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitApplication(Long id, String processDefinitionKey) {
+        VehicleApplicationDO application = validateApplicationExists(id);
+        if (!VehicleApplicationStatusEnum.DRAFT.getStatus().equals(application.getStatus())
+                && !VehicleApplicationStatusEnum.REJECTED.getStatus().equals(application.getStatus())) {
+            throw exception(VEHICLE_APPLICATION_SUBMIT_FAIL, application.getNo(),
+                    VehicleApplicationStatusEnum.nameOf(application.getStatus()));
+        }
+        String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+        applicationMapper.updateById(new VehicleApplicationDO()
+                .setId(id)
+                .setStatus(VehicleApplicationStatusEnum.APPROVING.getStatus())
+                .setProcessInstanceId(processInstanceId));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateApplicationAuditStatus(Long id, Integer bpmResult) {
+        VehicleApplicationDO application = validateApplicationExists(id);
+        if (!VehicleApplicationStatusEnum.APPROVING.getStatus().equals(application.getStatus())) {
+            log.warn("[updateApplicationAuditStatus] 鐢ㄨ溅鐢宠({}) 涓嶅浜庡鎵逛腑鐘舵��", id);
+            return;
+        }
+        Integer newStatus = convertBpmResultToStatus(bpmResult);
+        if (newStatus == null) {
+            return;
+        }
+        VehicleApplicationDO updateObj = new VehicleApplicationDO().setId(id).setStatus(newStatus);
+        if (VehicleApplicationStatusEnum.APPROVED.getStatus().equals(newStatus)) {
+            updateObj.setApproveTime(LocalDateTime.now());
+        }
+        applicationMapper.updateById(updateObj);
+    }
+
+    @Override
+    public List<Map<String, Object>> getApproveProcessDefinitionList() {
+        validateVehicleApproveCategory();
+        List<BpmProcessDefinitionInfoDO> definitionInfoList =
+                bpmProcessDefinitionService.getProcessDefinitionInfoListByCategory(VEHICLE_APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(definitionInfoList)) {
+            return Collections.emptyList();
+        }
+        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
+        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
+            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
+            if (pd == null || pd.isSuspended()) {
+                continue;
+            }
+            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
+            if (existing == null || pd.getVersion() > existing.getVersion()) {
+                latestVersionMap.put(pd.getKey(), pd);
+            }
+        }
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (ProcessDefinition pd : latestVersionMap.values()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", pd.getId());
+            item.put("key", pd.getKey());
+            item.put("name", pd.getName());
+            result.add(item);
+        }
+        return result;
+    }
+
+    private void validateVehicleApproveCategory() {
+        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
+                Collections.singletonList(VEHICLE_APPROVE_CATEGORY_CODE));
+        if (CollUtil.isEmpty(categories)) {
+            throw exception(VEHICLE_APPLICATION_BPM_CATEGORY_NOT_EXISTS);
+        }
+    }
+
+    private void validateUseTime(VehicleApplicationSaveReqVO reqVO) {
+        if (reqVO.getUseStartTime() != null && reqVO.getUseEndTime() != null
+                && !reqVO.getUseEndTime().isAfter(reqVO.getUseStartTime())) {
+            throw exception(VEHICLE_APPLICATION_TIME_INVALID);
+        }
+    }
+
+    private VehicleApplicationDO validateApplicationExists(Long id) {
+        VehicleApplicationDO application = id != null ? applicationMapper.selectById(id) : null;
+        if (application == null) {
+            throw exception(VEHICLE_APPLICATION_NOT_EXISTS);
+        }
+        return application;
+    }
+
+    private Integer convertBpmResultToStatus(Integer bpmResult) {
+        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+            return VehicleApplicationStatusEnum.APPROVED.getStatus();
+        }
+        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
+            return VehicleApplicationStatusEnum.REJECTED.getStatus();
+        }
+        if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+            return VehicleApplicationStatusEnum.DRAFT.getStatus();
+        }
+        return null;
+    }
+
+}
diff --git a/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/listener/VehicleApplicationStatusListener.java b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/listener/VehicleApplicationStatusListener.java
new file mode 100644
index 0000000..f413658
--- /dev/null
+++ b/yudao-module-vehicle/src/main/java/cn/iocoder/yudao/module/vehicle/service/application/listener/VehicleApplicationStatusListener.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.vehicle.service.application.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.vehicle.service.application.VehicleApplicationService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鐢ㄨ溅鐢宠瀹℃壒鐘舵�佺洃鍚櫒
+ *
+ * <p>鐩戝惉 BPM 娴佺▼瀹炰緥鐘舵�佸彉鏇达紝鎶婂鎵圭粨鏋滃洖鍐欏埌鐢ㄨ溅鐢宠锛岀敵璇蜂汉鍙湪鍒楄〃涓湅鍒板鎵圭粨鏋�
+ */
+@Component
+@Slf4j
+public class VehicleApplicationStatusListener extends BpmProcessInstanceStatusEventListener {
+
+    /** 鐢ㄨ溅瀹℃壒娴佺▼瀹氫箟 key锛圔PM 涓彂甯冩祦绋嬫椂蹇呴』浣跨敤璇� key锛� */
+    private static final String PROCESS_DEFINITION_KEY = "vehicle_application_approve";
+
+    @Resource
+    private VehicleApplicationService applicationService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return PROCESS_DEFINITION_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        Long applicationId;
+        try {
+            applicationId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勭敤杞︾敵璇风紪鍙�", event.getBusinessKey());
+            return;
+        }
+        log.info("[onEvent] 鏀跺埌鐢ㄨ溅瀹℃壒浜嬩欢锛宎pplicationId={}, status={}", applicationId, event.getStatus());
+        try {
+            applicationService.updateApplicationAuditStatus(applicationId, event.getStatus());
+        } catch (Exception e) {
+            log.error("[onEvent] 鍥炲啓鐢ㄨ溅鐢宠瀹℃壒鐘舵�佸け璐ワ紝applicationId={}", applicationId, e);
+        }
+    }
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityController.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityController.java
index 76e21ae..63ebdce 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityController.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityController.java
@@ -10,6 +10,7 @@
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO;
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum;
+import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityTypeEnum;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpSignupStatusEnum;
 import cn.iocoder.yudao.module.wxcp.service.activity.WxCpActivityService;
@@ -163,6 +164,9 @@
             }
             WxCpSignupStatusEnum status = WxCpSignupStatusEnum.valueOf(vo.getStatus());
             vo.setStatusName(status != null ? status.getName() : null);
+            vo.setFieldValues(activityService.getSignupValues(vo.getId()).stream()
+                    .map(value -> BeanUtils.toBean(value, WxCpSignupValueRespVO.class))
+                    .toList());
         });
         return success(result);
     }
@@ -215,16 +219,24 @@
     /**
      * 琛ラ綈鐘舵�佸悕銆佹帹閫佽寖鍥村悕涓庡皝闈㈤檮浠�
      *
-     * 灏侀潰涓嶈惤涓氬姟琛ㄥ瓧娈碉紝缁熶竴浠� system_storage_attachment 涓棿琛ㄨ锛�
-     * 鍓嶇鍙� attachmentList[0].previewURL 灞曠ず銆�
+     * 灏侀潰/璇︽儏鍥句笉钀戒笟鍔¤〃瀛楁锛岀粺涓�浠� system_storage_attachment 涓棿琛ㄨ锛�
+     * 鍓嶇鍙� attachmentList[0].previewURL 灞曠ず灏侀潰锛宒etailAttachmentList 鍥炴樉璇︽儏鍥俱��
      */
     private void fillActivityExtra(WxCpActivityRespVO vo) {
         WxCpActivityStatusEnum status = WxCpActivityStatusEnum.valueOf(vo.getStatus());
         vo.setStatusName(status != null ? status.getName() : null);
         WxCpPushScopeEnum scope = WxCpPushScopeEnum.valueOf(vo.getPushScope());
         vo.setPushScopeName(scope != null ? scope.getName() : null);
+        WxCpActivityTypeEnum type = WxCpActivityTypeEnum.valueOf(vo.getActivityType());
+        vo.setActivityTypeName(type != null ? type.getName() : null);
         if (vo.getId() != null) {
             vo.setAttachmentList(activityService.getActivityAttachments(vo.getId()));
+            vo.setDetailAttachmentList(activityService.getActivityDetailAttachments(vo.getId()));
+            vo.setImageAttachmentList(activityService.getActivityImageAttachments(vo.getId()));
+            vo.setFileAttachmentList(activityService.getActivityFileAttachments(vo.getId()));
+            vo.setFieldList(activityService.getActivityFields(vo.getId()).stream()
+                    .map(field -> BeanUtils.toBean(field, WxCpActivityFieldRespVO.class))
+                    .toList());
         }
     }
 
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityPublicController.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityPublicController.java
index 6acbdcb..1517cca 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityPublicController.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/WxCpActivityPublicController.java
@@ -6,7 +6,9 @@
 import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO;
 import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.WxCpSignupPublicReqVO;
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO;
+import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityFieldDO;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum;
+import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityTypeEnum;
 import cn.iocoder.yudao.module.wxcp.service.activity.WxCpActivityService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -22,6 +24,7 @@
 import java.net.URI;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -57,9 +60,13 @@
         if (activity == null) {
             return renderMessagePage("娲诲姩涓嶅瓨鍦�", "娲诲姩鍙兘宸茶鍒犻櫎锛岃鑱旂郴娲诲姩鍙戣捣浜虹‘璁ゃ��");
         }
-        if (!isSignupOpen(activity)) {
-            return renderMessagePage("鎶ュ悕宸茬粨鏉�",
-                    "璇ユ椿鍔ㄥ綋鍓嶄笉鍙姤鍚嶏紙鍙兘鏈彂甯冦�佸凡缁撴潫鎴栦笉鍦ㄦ姤鍚嶆椂闂村唴锛夈��");
+        if (!WxCpActivityStatusEnum.PUBLISHED.getStatus().equals(activity.getStatus())) {
+            return renderMessagePage("娲诲姩鏈紑鏀�",
+                    "璇ユ椿鍔ㄥ皻鏈彂甯冿紙鎴栧凡缁撴潫/鍙栨秷锛夛紝璇疯仈绯绘椿鍔ㄥ彂璧蜂汉纭銆�");
+        }
+        // 鍙湁鏁版嵁閲囬泦妯℃澘鏈夎〃鍗曪紱鍏跺畠妯℃澘鍙睍绀鸿鎯呭唴瀹�
+        if (WxCpActivityTypeEnum.needSignup(activity.getActivityType()) && !isInSignupPeriod(activity)) {
+            return renderMessagePage("鎶ュ悕宸茬粨鏉�", "璇ユ椿鍔ㄥ綋鍓嶄笉鍦ㄦ姤鍚嶆椂闂村唴锛岃鑱旂郴娲诲姩鍙戣捣浜虹‘璁ゃ��");
         }
         return renderSignupPage(activity, wxUserId);
     }
@@ -72,12 +79,9 @@
     }
 
     /**
-     * 鎶ュ悕鏄惁寮�鏀撅細娲诲姩闇�涓哄凡鍙戝竷锛屼笖澶勪簬鎶ュ悕鏃堕棿绐楀彛鍐�
+     * 鏄惁澶勪簬鎶ュ悕鏃堕棿绐楀彛鍐�
      */
-    private boolean isSignupOpen(WxCpActivityDO activity) {
-        if (!WxCpActivityStatusEnum.PUBLISHED.getStatus().equals(activity.getStatus())) {
-            return false;
-        }
+    private boolean isInSignupPeriod(WxCpActivityDO activity) {
         LocalDateTime now = LocalDateTime.now();
         if (activity.getSignupStartTime() != null && now.isBefore(activity.getSignupStartTime())) {
             return false;
@@ -96,6 +100,9 @@
                 .append("background:#f5f6f8;color:#1f2329;font-size:15px;line-height:1.6}")
                 .append(".card{background:#fff;border-radius:12px;padding:18px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.06)}")
                 .append(".cover{display:block;width:100%;max-height:260px;object-fit:cover;border-radius:8px;margin-bottom:14px;background:#f2f3f5}")
+                // 璇︽儏鍥惧繀椤绘寜鍘熷浘姣斾緥瀹屾暣灞曠ず锛氫簩缁寸爜/娴锋姤琚杈瑰氨浼氬け鏁堬紝鎵�浠ヨ繖閲屼笉鑳界敤 object-fit:cover
+                .append(".detail-img{display:block;margin:12px 0 0}")
+                .append(".detail-img img{display:block;width:100%;height:auto;border-radius:8px;background:#f2f3f5}")
                 .append("h1{font-size:19px;margin:0 0 14px;font-weight:600;word-break:break-all}")
                 .append(".meta{color:#646a73;font-size:14px}")
                 .append(".meta div{margin-bottom:6px;display:flex}")
@@ -104,8 +111,13 @@
                 .append(".desc{white-space:pre-wrap;word-break:break-word;color:#3f444d}")
                 .append("label{display:block;font-size:14px;color:#646a73;margin:14px 0 6px}")
                 .append("input,textarea{width:100%;padding:11px 12px;border:1px solid #dee0e3;border-radius:8px;font-size:15px;outline:none;background:#fff;font-family:inherit}")
-                .append("input:focus,textarea:focus{border-color:#3370ff}")
+                .append("input:focus,textarea:focus,select:focus{border-color:#3370ff}")
+                .append("select{width:100%;padding:11px 12px;border:1px solid #dee0e3;border-radius:8px;font-size:15px;outline:none;background:#fff;font-family:inherit}")
                 .append("textarea{resize:vertical;min-height:84px}")
+                .append(".file-title{font-weight:600;margin-bottom:8px}")
+                .append(".file-list a{color:#3370ff;text-decoration:none;word-break:break-all}")
+                .append(".choice-row{display:flex;flex-wrap:wrap;gap:14px;margin-top:4px}")
+                .append(".choice{display:inline-flex;align-items:center;gap:5px;margin:0;color:#1f2329;font-size:15px}")
                 .append(".file-row{margin-top:8px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}")
                 .append(".file-btn{display:inline-block;padding:8px 14px;border:1px dashed #c9cdd4;border-radius:8px;color:#3370ff;font-size:14px;background:#fff}")
                 .append(".file-list{margin-top:8px;font-size:13px;color:#646a73;word-break:break-all}")
@@ -148,8 +160,80 @@
         if (StrUtil.isNotBlank(activity.getContent())) {
             sb.append("<div class=\"desc\">").append(esc(activity.getContent())).append("</div>");
         }
+        // 璇︽儏鍥剧墖锛氱揣璺熷湪娲诲姩璇︽儏鏂囧瓧涓嬫柟锛堝悓涓�寮犲崱鐗囧唴锛夛紝鎸夊師鍥炬瘮渚嬪叏瀹藉睍绀猴紝
+        // 鐐逛竴涓嬬敤娴忚鍣ㄦ墦寮�鍘熷浘锛屾柟渚挎垚鍛樻斁澶х湅浜岀淮鐮�/娴锋姤
+        for (String detailImgUrl : resolveDetailImageUrls(activity.getId())) {
+            sb.append("<a class=\"detail-img\" href=\"").append(esc(detailImgUrl))
+                    .append("\" target=\"_blank\" rel=\"noopener\">")
+                    .append("<img src=\"").append(esc(detailImgUrl)).append("\" alt=\"娲诲姩璇︽儏鍥剧墖\">")
+                    .append("</a>");
+        }
         sb.append("</div>");
 
+        // 鍥剧墖閫氱煡妯℃澘锛氬睍绀轰笂浼犵殑鍥剧墖锛屾寜鍘熷浘姣斾緥瀹屾暣灞曠ず
+        appendImageGallery(sb, activity);
+        // 闄勪欢閫氱煡妯℃澘锛氬垪鍑洪檮浠讹紝鎴愬憳鐐瑰紑鍗冲彲涓嬭浇
+        appendFileList(sb, activity);
+
+        // 鍙湁銆屾暟鎹噰闆�/璇勪环銆嶆ā鏉挎墠鏈夊~鍐欒〃鍗�
+        if (WxCpActivityTypeEnum.needSignup(activity.getActivityType())) {
+            appendSignupForm(sb, activity, wxUserId);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 鍥剧墖閫氱煡妯℃澘鐨勫浘鐗囧睍绀哄尯
+     */
+    private void appendImageGallery(StringBuilder sb, WxCpActivityDO activity) {
+        List<SystemStorageBlobRespVO> images = activityService.getActivityImageAttachments(activity.getId());
+        if (CollUtil.isEmpty(images)) {
+            return;
+        }
+        sb.append("<div class=\"card\">");
+        for (SystemStorageBlobRespVO image : images) {
+            String raw = StrUtil.blankToDefault(image.getPreviewURL(), image.getUrl());
+            if (StrUtil.isBlank(raw)) {
+                continue;
+            }
+            String url = toSameOriginUrl(raw);
+            sb.append("<a class=\"detail-img\" href=\"").append(esc(url))
+                    .append("\" target=\"_blank\" rel=\"noopener\">")
+                    .append("<img src=\"").append(esc(url)).append("\" alt=\"娲诲姩鍥剧墖\">")
+                    .append("</a>");
+        }
+        sb.append("</div>");
+    }
+
+    /**
+     * 闄勪欢閫氱煡妯℃澘鐨勯檮浠跺垪琛ㄥ尯
+     */
+    private void appendFileList(StringBuilder sb, WxCpActivityDO activity) {
+        List<SystemStorageBlobRespVO> files = activityService.getActivityFileAttachments(activity.getId());
+        if (CollUtil.isEmpty(files)) {
+            return;
+        }
+        sb.append("<div class=\"card\"><div class=\"file-title\">闄勪欢涓嬭浇</div>")
+                .append("<div class=\"file-list\">");
+        for (SystemStorageBlobRespVO file : files) {
+            String raw = StrUtil.blankToDefault(file.getDownloadURL(),
+                    StrUtil.blankToDefault(file.getUrl(), file.getPreviewURL()));
+            if (StrUtil.isBlank(raw)) {
+                continue;
+            }
+            String url = toSameOriginUrl(raw);
+            String name = StrUtil.blankToDefault(file.getOriginalFilename(),
+                    StrUtil.blankToDefault(file.getName(), "闄勪欢"));
+            sb.append("<div><a href=\"").append(esc(url)).append("\" target=\"_blank\" rel=\"noopener\">")
+                    .append(esc(name)).append("</a></div>");
+        }
+        sb.append("</div></div>");
+    }
+
+    /**
+     * 鏁版嵁閲囬泦/璇勪环妯℃澘鐨勫~鍐欒〃鍗�
+     */
+    private void appendSignupForm(StringBuilder sb, WxCpActivityDO activity, String wxUserId) {
         // 鎶ュ悕琛ㄥ崟锛氬鍚嶃�佹墜鏈哄彿鍧囦负閫夊~锛屽彧濉簡鎵嬫満鍙锋墠鏍¢獙鏍煎紡
         sb.append("<div class=\"card\">")
                 .append("<label>濮撳悕</label>")
@@ -157,13 +241,14 @@
                 .append("<label>鎵嬫満鍙�</label>")
                 .append("<input id=\"mobile\" type=\"tel\" inputmode=\"numeric\" placeholder=\"璇疯緭鍏� 11 浣嶆墜鏈哄彿锛堥�夊~锛塡" maxlength=\"11\" autocomplete=\"tel\">")
                 .append("<label>鍙備笌璇存槑</label>")
-                .append("<textarea id=\"content\" placeholder=\"鍙~鍐欏娉ㄣ�侀渶姹傛垨鍏朵粬璇存槑锛堥�夊~锛塡" maxlength=\"1000\"></textarea>")
-                .append("<label>鍥剧墖 / 闄勪欢</label>")
+                .append("<textarea id=\"content\" placeholder=\"鍙~鍐欏娉ㄣ�侀渶姹傛垨鍏朵粬璇存槑锛堥�夊~锛塡" maxlength=\"1000\"></textarea>");
+        appendCollectFields(sb, activity);
+        sb.append("<label>鍥剧墖 / 闄勪欢</label>")
                 .append("<div class=\"file-row\">")
                 .append("<label class=\"file-btn\" for=\"files\">+ 閫夋嫨鍥剧墖鎴栨枃浠�</label>")
                 .append("<input id=\"files\" type=\"file\" multiple accept=\"image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt\" style=\"display:none\">")
                 .append("</div><div class=\"file-list\" id=\"fileList\"></div>")
-                .append("<button id=\"submit\">鎻愪氦鎶ュ悕</button>")
+                .append("<button id=\"submit\">鎻愪氦</button>")
                 .append("<div class=\"tip\">鎻愪氦鍚庡闇�淇敼锛岃鑱旂郴娲诲姩鍙戣捣浜�</div>")
                 .append("</div><div class=\"toast\" id=\"toast\"></div>");
 
@@ -171,10 +256,27 @@
         sb.append("<script>(function(){")
                 .append("var AID=").append(activity.getId()).append(";")
                 .append("var WXUID=").append(wxUserId == null ? "null" : "\"" + esc(jsStr(wxUserId)) + "\"").append(";")
+                .append("var FIELDS=").append(buildFieldsJson(activity)).append(";")
+                .append("var RATING_ENABLED=").append(Boolean.TRUE.equals(activity.getRatingEnabled())).append(";")
                 .append("var btn=document.getElementById('submit'),toast=document.getElementById('toast');")
                 .append("var fileInput=document.getElementById('files'),fileList=document.getElementById('fileList');")
                 .append("function tip(m){toast.textContent=m;toast.style.display='block';setTimeout(function(){toast.style.display='none'},2200)}")
-                .append("function reset(){btn.disabled=false;btn.textContent='鎻愪氦鎶ュ悕'}")
+                .append("function reset(){btn.disabled=false;btn.textContent='鎻愪氦'}")
+                .append("function collectFields(){")
+                .append("var arr=[],bad=null;")
+                .append("for(var i=0;i<FIELDS.length;i++){var f=FIELDS[i],v='';")
+                .append("if(f.type==='checkbox'){var cs=document.querySelectorAll('input[name=\"f_'+f.id+'\"]:checked'),vs=[];")
+                .append("for(var j=0;j<cs.length;j++){vs.push(cs[j].value)}v=vs.join(',')}")
+                .append("else if(f.type==='radio'){var r=document.querySelector('input[name=\"f_'+f.id+'\"]:checked');v=r?r.value:''}")
+                .append("else{var el=document.getElementById('f_'+f.id);v=el?el.value.trim():''}")
+                .append("if(f.required&&!v){bad=f.label;break}")
+                .append("if(v){arr.push({fieldId:f.id,fieldValue:v})}}")
+                .append("if(bad){tip('銆�'+bad+'銆戜负蹇呭~椤癸紝璇峰~鍐欏悗鍐嶆彁浜�');return null}return arr}")
+                .append("function getRating(){")
+                .append("if(!RATING_ENABLED){return 0}")
+                .append("var r=document.querySelector('input[name=\"rating\"]:checked');")
+                .append("if(!r){tip('璇峰厛瀹屾垚璇勪环鎵撳垎鍚庡啀鎻愪氦');return -1}")
+                .append("return parseInt(r.value,10)}")
                 .append("fileInput.addEventListener('change',function(){")
                 .append("var fs=fileInput.files,h='';")
                 .append("for(var i=0;i<fs.length;i++){h+='<div>路 '+fs[i].name+'</div>'}")
@@ -184,6 +286,8 @@
                 .append("var m=document.getElementById('mobile').value.trim();")
                 .append("var c=document.getElementById('content').value.trim();")
                 .append("if(m&&!/^1[3-9]\\d{9}$/.test(m)){tip('鎵嬫満鍙锋牸寮忎笉姝g‘锛岃濉� 11 浣嶆墜鏈哄彿鎴栫暀绌�');return}")
+                .append("var fv=collectFields();if(fv===null){return}")
+                .append("var rs=getRating();if(rs<0){return}")
                 .append("btn.disabled=true;")
                 .append("var fs=fileInput.files;")
                 .append("var upload=Promise.resolve([]);")
@@ -202,6 +306,8 @@
                 .append("if(c){body.content=c}")
                 .append("if(WXUID){body.wxUserId=WXUID}")
                 .append("if(blobIds.length>0){body.blobIds=blobIds}")
+                .append("if(fv.length>0){body.fieldValues=fv}")
+                .append("if(rs>0){body.ratingScore=rs}")
                 .append("return fetch('signup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})")
                 .append(".then(function(r){return r.json()})")
                 .append(".then(function(r){")
@@ -209,7 +315,131 @@
                 .append("else{tip(r.msg||'鎶ュ悕澶辫触');reset()}})}")
                 .append(".catch(function(e){tip((e&&e.message)||'缃戠粶寮傚父锛岃绋嶅悗閲嶈瘯');reset()})")
                 .append("})})();</script></body></html>");
-        return sb.toString();
+    }
+
+    /**
+     * 娓叉煋鏁版嵁閲囬泦妯℃澘鐨勮嚜瀹氫箟瀛楁涓庤瘎浠�
+     */
+    private void appendCollectFields(StringBuilder sb, WxCpActivityDO activity) {
+        String base = "f_";
+        for (WxCpActivityFieldDO field : activityService.getActivityFields(activity.getId())) {
+            String id = base + field.getId();
+            String label = esc(field.getFieldLabel());
+            String required = Boolean.TRUE.equals(field.getRequired()) ? " *" : "";
+            String placeholder = esc(StrUtil.blankToDefault(field.getPlaceholder(),
+                    "璇疯緭鍏�" + field.getFieldLabel()));
+            String type = StrUtil.blankToDefault(field.getFieldType(), WxCpActivityFieldDO.TYPE_TEXT);
+            switch (type) {
+                case WxCpActivityFieldDO.TYPE_TEXTAREA ->
+                        sb.append("<label>").append(label).append(required).append("</label>")
+                                .append("<textarea id=\"").append(id).append("\" placeholder=\"")
+                                .append(placeholder).append("\" maxlength=\"2000\"></textarea>");
+                case WxCpActivityFieldDO.TYPE_NUMBER ->
+                        sb.append("<label>").append(label).append(required).append("</label>")
+                                .append("<input id=\"").append(id)
+                                .append("\" type=\"number\" placeholder=\"").append(placeholder).append("\">");
+                case WxCpActivityFieldDO.TYPE_DATE ->
+                        sb.append("<label>").append(label).append(required).append("</label>")
+                                .append("<input id=\"").append(id).append("\" type=\"date\">");
+                case WxCpActivityFieldDO.TYPE_SELECT -> {
+                    sb.append("<label>").append(label).append(required).append("</label>")
+                            .append("<select id=\"").append(id).append("\"><option value=\"\">璇烽�夋嫨</option>");
+                    for (String option : splitOptions(field.getFieldOptions())) {
+                        sb.append("<option value=\"").append(esc(option)).append("\">")
+                                .append(esc(option)).append("</option>");
+                    }
+                    sb.append("</select>");
+                }
+                case WxCpActivityFieldDO.TYPE_RADIO -> {
+                    sb.append("<label>").append(label).append(required).append("</label><div class=\"choice-row\">");
+                    for (String option : splitOptions(field.getFieldOptions())) {
+                        sb.append("<label class=\"choice\"><input type=\"radio\" name=\"")
+                                .append(id).append("\" value=\"").append(esc(option)).append("\">")
+                                .append(esc(option)).append("</label>");
+                    }
+                    sb.append("</div>");
+                }
+                case WxCpActivityFieldDO.TYPE_CHECKBOX -> {
+                    sb.append("<label>").append(label).append(required).append("</label><div class=\"choice-row\">");
+                    for (String option : splitOptions(field.getFieldOptions())) {
+                        sb.append("<label class=\"choice\"><input type=\"checkbox\" name=\"")
+                                .append(id).append("\" value=\"").append(esc(option)).append("\">")
+                                .append(esc(option)).append("</label>");
+                    }
+                    sb.append("</div>");
+                }
+                case WxCpActivityFieldDO.TYPE_RATING -> {
+                    sb.append("<label>").append(label).append(required).append("</label><div class=\"choice-row\">");
+                    int max = activity.getRatingMax() != null && activity.getRatingMax() > 0 ? activity.getRatingMax() : 5;
+                    for (int i = 1; i <= max; i++) {
+                        sb.append("<label class=\"choice\"><input type=\"radio\" name=\"")
+                                .append(id).append("\" value=\"").append(i).append("\">")
+                                .append(i).append(" 鏄�</label>");
+                    }
+                    sb.append("</div>");
+                }
+                default ->
+                        sb.append("<label>").append(label).append(required).append("</label>")
+                                .append("<input id=\"").append(id).append("\" type=\"text\" placeholder=\"")
+                                .append(placeholder).append("\" maxlength=\"255\">");
+            }
+        }
+        // 鐙珛璇勪环椤癸細寮�鍚悗蹇呴』閫夋嫨鏄熺骇
+        if (Boolean.TRUE.equals(activity.getRatingEnabled())) {
+            int max = activity.getRatingMax() != null && activity.getRatingMax() > 0
+                    ? activity.getRatingMax() : 5;
+            sb.append("<label>").append(esc(StrUtil.blankToDefault(activity.getRatingTitle(), "娲诲姩璇勪环")))
+                    .append(" *</label><div class=\"choice-row\">");
+            for (int i = 1; i <= max; i++) {
+                sb.append("<label class=\"choice\"><input type=\"radio\" name=\"rating\" value=\"")
+                        .append(i).append("\">").append(i).append(" 鏄�</label>");
+            }
+            sb.append("</div>");
+        }
+    }
+
+    private List<String> splitOptions(String options) {
+        if (StrUtil.isBlank(options)) {
+            return Collections.emptyList();
+        }
+        return StrUtil.split(options, ',', true, true);
+    }
+
+    /**
+     * 鎶婅嚜瀹氫箟瀛楁瀹氫箟杞垚鍓嶇鎻愪氦鑴氭湰鍙敤鐨� JSON
+     */
+    private String buildFieldsJson(WxCpActivityDO activity) {
+        List<WxCpActivityFieldDO> fields = activityService.getActivityFields(activity.getId());
+        StringBuilder json = new StringBuilder("[");
+        for (int i = 0; i < fields.size(); i++) {
+            WxCpActivityFieldDO field = fields.get(i);
+            if (i > 0) {
+                json.append(',');
+            }
+            json.append("{\"id\":").append(field.getId())
+                    .append(",\"label\":\"").append(esc(jsStr(field.getFieldLabel()))).append("\"")
+                    .append(",\"type\":\"").append(esc(jsStr(field.getFieldType()))).append("\"")
+                    .append(",\"required\":").append(Boolean.TRUE.equals(field.getRequired())).append('}');
+        }
+        return json.append(']').toString();
+    }
+
+    /**
+     * 鍙栨椿鍔ㄨ鎯呭浘鐗囧湴鍧�鍒楄〃锛堟寜涓婁紶鍏堝悗鎺掑簭锛屽湴鍧�鍚屾牱杞垚鍚屾簮鐩稿璺緞锛�
+     *
+     * 涓庡皝闈㈠垎寮�鍙栵細灏侀潰鎸� 16:9 瑁佸壀灞曠ず锛岃鎯呭浘鎸夊師鍥炬瘮渚嬪畬鏁村睍绀猴紝
+     * 浜岀淮鐮佽繖绫讳笉鑳借鐨勫浘瑕佹斁璇︽儏鍥鹃噷銆�
+     */
+    private List<String> resolveDetailImageUrls(Long activityId) {
+        List<SystemStorageBlobRespVO> attachments = activityService.getActivityDetailAttachments(activityId);
+        if (CollUtil.isEmpty(attachments)) {
+            return Collections.emptyList();
+        }
+        return attachments.stream()
+                .map(attachment -> StrUtil.blankToDefault(attachment.getPreviewURL(), attachment.getUrl()))
+                .filter(StrUtil::isNotBlank)
+                .map(this::toSameOriginUrl)
+                .toList();
     }
 
     /**
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldRespVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldRespVO.java
new file mode 100644
index 0000000..541a4cd
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldRespVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 娲诲姩閲囬泦瀛楁 Response VO")
+@Data
+public class WxCpActivityFieldRespVO {
+
+    @Schema(description = "瀛楁缂栧彿")
+    private Long id;
+
+    @Schema(description = "瀛楁鍚嶇О", example = "鎵�鍦ㄩ儴闂�")
+    private String fieldLabel;
+
+    @Schema(description = "瀛楁绫诲瀷", example = "select")
+    private String fieldType;
+
+    @Schema(description = "閫夐」锛屽涓敤鑻辨枃閫楀彿鍒嗛殧")
+    private String fieldOptions;
+
+    @Schema(description = "鏄惁蹇呭~")
+    private Boolean required;
+
+    @Schema(description = "杈撳叆鎻愮ず")
+    private String placeholder;
+
+    @Schema(description = "鎺掑簭鍙�")
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldSaveReqVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldSaveReqVO.java
new file mode 100644
index 0000000..efaa602
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityFieldSaveReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 娲诲姩閲囬泦瀛楁 鍒涘缓/淇敼 Request VO")
+@Data
+public class WxCpActivityFieldSaveReqVO {
+
+    @Schema(description = "瀛楁缂栧彿锛堜慨鏀规椂浼狅級")
+    private Long id;
+
+    @Schema(description = "瀛楁鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鎵�鍦ㄩ儴闂�")
+    @NotBlank(message = "瀛楁鍚嶇О涓嶈兘涓虹┖")
+    @Size(max = 128, message = "瀛楁鍚嶇О闀垮害涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String fieldLabel;
+
+    @Schema(description = "瀛楁绫诲瀷锛歵ext/textarea/number/date/select/radio/checkbox/rating",
+            requiredMode = Schema.RequiredMode.REQUIRED, example = "select")
+    @NotBlank(message = "瀛楁绫诲瀷涓嶈兘涓虹┖")
+    private String fieldType;
+
+    @Schema(description = "閫夐」锛屽涓敤鑻辨枃閫楀彿鍒嗛殧锛泂elect/radio/checkbox 蹇呭~", example = "鏁欏,琛屾斂,鍚庡嫟")
+    @Size(max = 1000, message = "閫夐」闀垮害涓嶈兘瓒呰繃 1000 涓瓧绗�")
+    private String fieldOptions;
+
+    @Schema(description = "鏄惁蹇呭~", example = "true")
+    private Boolean required;
+
+    @Schema(description = "杈撳叆鎻愮ず", example = "璇烽�夋嫨鎵�鍦ㄩ儴闂�")
+    @Size(max = 255, message = "杈撳叆鎻愮ず闀垮害涓嶈兘瓒呰繃 255 涓瓧绗�")
+    private String placeholder;
+
+    @Schema(description = "鎺掑簭鍙�", example = "1")
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityRespVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityRespVO.java
index 13266ec..5ea6e98 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityRespVO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivityRespVO.java
@@ -17,12 +17,42 @@
     @Schema(description = "娲诲姩鏍囬", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026 骞存槬瀛e洟寤�")
     private String title;
 
+    @Schema(description = "娲诲姩妯℃澘绫诲瀷锛�1绾�氱煡 2鍗曟潯娑堟伅 3鍥剧墖閫氱煡 4闄勪欢閫氱煡 5鏁版嵁閲囬泦/璇勪环")
+    private Integer activityType;
+
+    @Schema(description = "娲诲姩妯℃澘绫诲瀷鍚�", example = "鏁版嵁閲囬泦/璇勪环")
+    private String activityTypeName;
+
     @Schema(description = "娲诲姩璇︽儏")
     private String content;
 
+    @Schema(description = "鍗$墖鎸夐挳鏂囨")
+    private String buttonText;
+
+    @Schema(description = "鏄惁寮�鍚瘎浠�")
+    private Boolean ratingEnabled;
+
+    @Schema(description = "璇勪环鏍囬")
+    private String ratingTitle;
+
+    @Schema(description = "璇勪环鏈�楂樻槦绾�")
+    private Integer ratingMax;
+
     @Schema(description = "灏侀潰鍥鹃檮浠跺垪琛紙鏈�澶� 1 寮狅級")
     private List<SystemStorageBlobRespVO> attachmentList;
 
+    @Schema(description = "璇︽儏鍥剧墖闄勪欢鍒楄〃锛堟渶澶� 5 寮狅紝鎸変笂浼犲厛鍚庢帓搴忥級")
+    private List<SystemStorageBlobRespVO> detailAttachmentList;
+
+    @Schema(description = "鍥剧墖閫氱煡鐨勫浘鐗囬檮浠跺垪琛紙鏈�澶� 9 寮狅級")
+    private List<SystemStorageBlobRespVO> imageAttachmentList;
+
+    @Schema(description = "闄勪欢閫氱煡鐨勯檮浠跺垪琛紙鏈�澶� 5 涓級")
+    private List<SystemStorageBlobRespVO> fileAttachmentList;
+
+    @Schema(description = "鏁版嵁閲囬泦妯℃澘鐨勮嚜瀹氫箟瀛楁")
+    private List<WxCpActivityFieldRespVO> fieldList;
+
     @Schema(description = "娲诲姩鍦扮偣", example = "鍏徃澶т細璁")
     private String location;
 
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivitySaveReqVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivitySaveReqVO.java
index 6680c22..73cdc18 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivitySaveReqVO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpActivitySaveReqVO.java
@@ -23,12 +23,46 @@
     @Size(max = 128, message = "娲诲姩鏍囬闀垮害涓嶈兘瓒呰繃 128 涓瓧绗�")
     private String title;
 
+    @Schema(description = "娲诲姩妯℃澘绫诲瀷锛�1绾�氱煡 2鍗曟潯娑堟伅 3鍥剧墖閫氱煡 4闄勪欢閫氱煡 5鏁版嵁閲囬泦/璇勪环",
+            requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "娲诲姩妯℃澘绫诲瀷涓嶈兘涓虹┖")
+    private Integer activityType;
+
     @Schema(description = "娲诲姩璇︽儏")
     private String content;
+
+    @Schema(description = "鍗$墖鎸夐挳鏂囨锛堝崟鏉℃秷鎭�/鏁版嵁閲囬泦妯℃澘浣跨敤锛�", example = "绔嬪嵆鎶ュ悕")
+    @Size(max = 32, message = "鎸夐挳鏂囨闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�")
+    private String buttonText;
+
+    @Schema(description = "鏄惁寮�鍚瘎浠凤紙鏁版嵁閲囬泦妯℃澘浣跨敤锛�", example = "true")
+    private Boolean ratingEnabled;
+
+    @Schema(description = "璇勪环鏍囬", example = "娲诲姩婊℃剰搴﹁瘎浠�")
+    @Size(max = 128, message = "璇勪环鏍囬闀垮害涓嶈兘瓒呰繃 128 涓瓧绗�")
+    private String ratingTitle;
+
+    @Schema(description = "璇勪环鏈�楂樻槦绾э紝榛樿 5", example = "5")
+    private Integer ratingMax;
+
+    @Schema(description = "鍥剧墖閫氱煡鐨勫浘鐗囨枃浠剁紪鍙峰垪琛紙鏈�澶� 9 寮狅級", example = "[1,2]")
+    @Size(max = 9, message = "鍥剧墖鏈�澶氫笂浼� 9 寮�")
+    private List<Long> imageBlobIds;
+
+    @Schema(description = "闄勪欢閫氱煡鐨勯檮浠舵枃浠剁紪鍙峰垪琛紙鏈�澶� 5 涓級", example = "[1,2]")
+    @Size(max = 5, message = "闄勪欢鏈�澶氫笂浼� 5 涓�")
+    private List<Long> fileBlobIds;
+
+    @Schema(description = "鏁版嵁閲囬泦妯℃澘鐨勮嚜瀹氫箟瀛楁鍒楄〃锛沶ull = 涓嶆敼鍔紝[] = 娓呯┖")
+    private List<WxCpActivityFieldSaveReqVO> fieldList;
 
     @Schema(description = "灏侀潰鍥炬枃浠剁紪鍙峰垪琛紙鏈�澶� 1 寮狅級", example = "[1]")
     @Size(max = 1, message = "灏侀潰鍥炬渶澶氫笂浼� 1 寮�")
     private List<Long> blobIds;
+
+    @Schema(description = "璇︽儏鍥剧墖鏂囦欢缂栧彿鍒楄〃锛堟渶澶� 5 寮狅級锛涗笉浼� = 涓嶆敼鍔紝浼� [] = 娓呯┖", example = "[2,3]")
+    @Size(max = 5, message = "璇︽儏鍥剧墖鏈�澶氫笂浼� 5 寮�")
+    private List<Long> detailBlobIds;
 
     @Schema(description = "娲诲姩鍦扮偣", example = "鍏徃澶т細璁")
     @Size(max = 255, message = "娲诲姩鍦扮偣闀垮害涓嶈兘瓒呰繃 255 涓瓧绗�")
@@ -46,8 +80,7 @@
     @Schema(description = "鎶ュ悕缁撴潫鏃堕棿")
     private LocalDateTime signupEndTime;
 
-    @Schema(description = "鎶ュ悕浜烘暟涓婇檺锛�0 琛ㄧず涓嶉檺", example = "50")
-    @NotNull(message = "鎶ュ悕浜烘暟涓婇檺涓嶈兘涓虹┖锛屼笉闄愯濉� 0")
+    @Schema(description = "鎶ュ悕浜烘暟涓婇檺锛�0 琛ㄧず涓嶉檺锛涗粎鏁版嵁閲囬泦妯℃澘浣跨敤锛屼笉浼犻粯璁� 0", example = "50")
     private Integer signupLimit;
 
     @Schema(description = "鎺ㄩ�佽寖鍥达紙閫夊~锛屼笉浼犻粯璁ゅ叏鍛橈級锛涙帹閫佹椂浠嶅彲鍦ㄦ帹閫佸脊绐楅噷閲嶆柊閫夋嫨", example = "0")
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupPublicReqVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupPublicReqVO.java
index 5d8929e..53464e3 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupPublicReqVO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupPublicReqVO.java
@@ -35,6 +35,12 @@
     @Schema(description = "鍙備笌璇存槑锛堟垚鍛樺~鍐欑殑鏂囧瓧鍐呭锛�")
     private String content;
 
+    @Schema(description = "璇勪环鏄熺骇锛�1~5锛屾暟鎹噰闆�/璇勪环妯℃澘浣跨敤锛�", example = "5")
+    private Integer ratingScore;
+
+    @Schema(description = "鑷畾涔夐噰闆嗗瓧娈靛�煎垪琛�")
+    private List<WxCpSignupValueReqVO> fieldValues;
+
     @Schema(description = "闄勪欢鏂囦欢缂栧彿鍒楄〃", example = "[1,2]")
     private List<Long> blobIds;
 
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupRespVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupRespVO.java
index 97fab2c..508346e 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupRespVO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupRespVO.java
@@ -4,6 +4,7 @@
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 浼佷笟寰俊娲诲姩鎶ュ悕 Response VO")
 @Data
@@ -30,6 +31,15 @@
     @Schema(description = "閮ㄩ棬鍚嶇О")
     private String deptName;
 
+    @Schema(description = "鍙備笌璇存槑")
+    private String content;
+
+    @Schema(description = "璇勪环鏄熺骇", example = "5")
+    private Integer ratingScore;
+
+    @Schema(description = "鑷畾涔夐噰闆嗗瓧娈靛��")
+    private List<WxCpSignupValueRespVO> fieldValues;
+
     @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
     private Integer status;
 
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueReqVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueReqVO.java
new file mode 100644
index 0000000..215820d
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "鐢ㄦ埛 App - 娲诲姩鎶ュ悕閲囬泦瀛楁鍊�")
+@Data
+public class WxCpSignupValueReqVO {
+
+    @Schema(description = "瀛楁缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "閲囬泦瀛楁缂栧彿涓嶈兘涓虹┖")
+    private Long fieldId;
+
+    @Schema(description = "瀛楁鍊硷紱澶氶�夌敤鑻辨枃閫楀彿鍒嗛殧", example = "鏁欏")
+    @Size(max = 2000, message = "瀛楁鍊奸暱搴︿笉鑳借秴杩� 2000 涓瓧绗�")
+    private String fieldValue;
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueRespVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueRespVO.java
new file mode 100644
index 0000000..fc601fa
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/activity/vo/WxCpSignupValueRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 娲诲姩鎶ュ悕閲囬泦瀛楁鍊� Response VO")
+@Data
+public class WxCpSignupValueRespVO {
+
+    @Schema(description = "瀛楁缂栧彿")
+    private Long fieldId;
+
+    @Schema(description = "瀛楁鍚嶇О")
+    private String fieldLabel;
+
+    @Schema(description = "瀛楁绫诲瀷")
+    private String fieldType;
+
+    @Schema(description = "瀛楁鍊�")
+    private String fieldValue;
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/WxCpNoticeController.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/WxCpNoticeController.java
index 9021b4d..7e71f27 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/WxCpNoticeController.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/WxCpNoticeController.java
@@ -11,6 +11,7 @@
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.notice.WxCpNoticeDO;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
 import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeStatusEnum;
+import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeTypeEnum;
 import cn.iocoder.yudao.module.wxcp.service.notice.WxCpNoticeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -117,6 +118,8 @@
     private void fillNames(WxCpNoticeRespVO vo) {
         WxCpNoticeStatusEnum status = WxCpNoticeStatusEnum.valueOf(vo.getStatus());
         vo.setStatusName(status != null ? status.getName() : null);
+        WxCpNoticeTypeEnum type = WxCpNoticeTypeEnum.valueOf(vo.getNoticeType());
+        vo.setNoticeTypeName(type != null ? type.getName() : null);
         WxCpPushScopeEnum scope = WxCpPushScopeEnum.valueOf(vo.getPushScope());
         vo.setPushScopeName(scope != null ? scope.getName() : null);
     }
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeRespVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeRespVO.java
index c2226e6..9bfb372 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeRespVO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeRespVO.java
@@ -18,6 +18,30 @@
     @Schema(description = "閫氱煡姝f枃")
     private String content;
 
+    @Schema(description = "閫氱煡妯℃澘绫诲瀷", example = "1")
+    private Integer noticeType;
+
+    @Schema(description = "閫氱煡妯℃澘绫诲瀷鍚嶇О", example = "绾枃鏈�氱煡")
+    private String noticeTypeName;
+
+    @Schema(description = "鎽樿")
+    private String summary;
+
+    @Schema(description = "璺宠浆閾炬帴")
+    private String linkUrl;
+
+    @Schema(description = "閾炬帴鎸夐挳鏂囨")
+    private String linkBtnText;
+
+    @Schema(description = "鍥炬枃娑堟伅灏侀潰鍥惧湴鍧�")
+    private String coverUrl;
+
+    @Schema(description = "鏂囦欢閫氱煡鐨勯檮浠� Blob ID")
+    private Long fileBlobId;
+
+    @Schema(description = "鏂囦欢閫氱煡鐨勯檮浠舵枃浠跺悕")
+    private String fileName;
+
     @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer status;
 
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeSaveReqVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeSaveReqVO.java
index 9c45df2..67c67c3 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeSaveReqVO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/notice/vo/WxCpNoticeSaveReqVO.java
@@ -2,6 +2,7 @@
 
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
+import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.Size;
@@ -19,10 +20,37 @@
     @Size(max = 128, message = "閫氱煡鏍囬闀垮害涓嶈兘瓒呰繃 128 涓瓧绗�")
     private String title;
 
-    @Schema(description = "閫氱煡姝f枃", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotEmpty(message = "閫氱煡姝f枃涓嶈兘涓虹┖")
+    @Schema(description = "閫氱煡姝f枃锛涚函鏂囨湰閫氱煡蹇呭~锛屽叾瀹冪被鍨嬩綔涓鸿ˉ鍏呰鏄�")
+    @Size(max = 10000, message = "閫氱煡姝f枃闀垮害涓嶈兘瓒呰繃 10000 涓瓧绗�")
     private String content;
 
+    @Schema(description = "閫氱煡妯℃澘绫诲瀷锛�1-绾枃鏈� 2-鍥炬枃鍗$墖(濉姤/閾炬帴) 3-鏂囦欢閫氱煡 4-鍥炬枃娑堟伅", example = "1")
+    @InEnum(WxCpNoticeTypeEnum.class)
+    private Integer noticeType;
+
+    @Schema(description = "鎽樿锛堝浘鏂囧崱鐗�/鍥炬枃娑堟伅浣跨敤锛�", example = "璇蜂簬鏈懆浜斿墠瀹屾垚濉姤")
+    @Size(max = 512, message = "鎽樿闀垮害涓嶈兘瓒呰繃 512 涓瓧绗�")
+    private String summary;
+
+    @Schema(description = "璺宠浆閾炬帴锛堝~鎶ャ�佹煡鐪嬭鎯呯瓑锛�", example = "https://example.com/form/1")
+    @Size(max = 1024, message = "璺宠浆閾炬帴闀垮害涓嶈兘瓒呰繃 1024 涓瓧绗�")
+    private String linkUrl;
+
+    @Schema(description = "閾炬帴鎸夐挳鏂囨", example = "鍘诲~鎶�")
+    @Size(max = 32, message = "鎸夐挳鏂囨闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�")
+    private String linkBtnText;
+
+    @Schema(description = "鍥炬枃娑堟伅灏侀潰鍥惧湴鍧�", example = "https://example.com/cover.png")
+    @Size(max = 1024, message = "灏侀潰鍥惧湴鍧�闀垮害涓嶈兘瓒呰繃 1024 涓瓧绗�")
+    private String coverUrl;
+
+    @Schema(description = "鏂囦欢閫氱煡鐨勯檮浠� Blob ID锛堝厛璋冪敤 /system/storage-blob/upload 涓婁紶锛�", example = "1024")
+    private Long fileBlobId;
+
+    @Schema(description = "鏂囦欢閫氱煡鐨勯檮浠舵枃浠跺悕", example = "鍏充簬寮�灞昘X宸ヤ綔鐨勯�氱煡.pdf")
+    @Size(max = 255, message = "鏂囦欢鍚嶉暱搴︿笉鑳借秴杩� 255 涓瓧绗�")
+    private String fileName;
+
     @Schema(description = "鎺ㄩ�佽寖鍥达紙閫夊~锛屼笉浼犻粯璁ゅ叏鍛橈級锛涙帹閫佹椂浠嶅彲鍦ㄦ帹閫佸脊绐楅噷閲嶆柊閫夋嫨", example = "0")
     @InEnum(WxCpPushScopeEnum.class)
     private Integer pushScope;
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/vo/WxCpPushReqVO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/vo/WxCpPushReqVO.java
index 51feca6..af3cd22 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/vo/WxCpPushReqVO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/controller/admin/vo/WxCpPushReqVO.java
@@ -23,4 +23,31 @@
     @Size(max = 1024, message = "鎺ㄩ�佺洰鏍囬暱搴︿笉鑳借秴杩� 1024 涓瓧绗�")
     private String pushTarget;
 
+    @Schema(description = "鎺ㄩ�佹椂閫夋嫨鐨勬ā鏉跨被鍨嬶細1绾枃鏈� 2鍥炬枃鍗$墖 3鏂囦欢閫氱煡 4鍥炬枃娑堟伅锛涗笉浼犳部鐢ㄩ�氱煡閰嶇疆", example = "2")
+    @InEnum(cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeTypeEnum.class)
+    private Integer noticeType;
+
+    @Schema(description = "鎽樿锛堝浘鏂囧崱鐗�/鍥炬枃娑堟伅锛�")
+    @Size(max = 512, message = "鎽樿闀垮害涓嶈兘瓒呰繃 512 涓瓧绗�")
+    private String summary;
+
+    @Schema(description = "璺宠浆閾炬帴锛堝浘鏂囧崱鐗�/鍥炬枃娑堟伅锛�")
+    @Size(max = 1024, message = "璺宠浆閾炬帴闀垮害涓嶈兘瓒呰繃 1024 涓瓧绗�")
+    private String linkUrl;
+
+    @Schema(description = "鎸夐挳鏂囨锛堝浘鏂囧崱鐗囷級")
+    @Size(max = 32, message = "鎸夐挳鏂囨闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�")
+    private String linkBtnText;
+
+    @Schema(description = "灏侀潰鍥惧湴鍧�锛堝浘鏂囨秷鎭級")
+    @Size(max = 1024, message = "灏侀潰鍥惧湴鍧�闀垮害涓嶈兘瓒呰繃 1024 涓瓧绗�")
+    private String coverUrl;
+
+    @Schema(description = "鏂囦欢閫氱煡鐨勯檮浠� Blob ID")
+    private Long fileBlobId;
+
+    @Schema(description = "鏂囦欢閫氱煡鐨勯檮浠舵枃浠跺悕")
+    @Size(max = 255, message = "鏂囦欢鍚嶉暱搴︿笉鑳借秴杩� 255 涓瓧绗�")
+    private String fileName;
+
 }
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityDO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityDO.java
index 47be2be..28ab0aa 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityDO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityDO.java
@@ -35,10 +35,32 @@
      */
     private String title;
     /**
+     * 娲诲姩妯℃澘绫诲瀷
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityTypeEnum}
+     */
+    private Integer activityType;
+    /**
      * 娲诲姩璇︽儏
      */
     private String content;
     /**
+     * 鍗$墖鎸夐挳鏂囨锛堝崟鏉℃秷鎭�/鏁版嵁閲囬泦妯℃澘浣跨敤锛�
+     */
+    private String buttonText;
+    /**
+     * 鏄惁寮�鍚瘎浠凤紙鏁版嵁閲囬泦妯℃澘浣跨敤锛�
+     */
+    private Boolean ratingEnabled;
+    /**
+     * 璇勪环鏍囬
+     */
+    private String ratingTitle;
+    /**
+     * 璇勪环鏈�楂樻槦绾э紝榛樿 5
+     */
+    private Integer ratingMax;
+    /**
      * 娲诲姩鍦扮偣
      */
     private String location;
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityFieldDO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityFieldDO.java
new file mode 100644
index 0000000..7c5bb65
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivityFieldDO.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.wxcp.dal.dataobject.activity;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 浼佷笟寰俊娲诲姩 - 鑷畾涔夐噰闆嗗瓧娈� DO
+ *
+ * 浠呫�屾暟鎹噰闆�/璇勪环銆嶆ā鏉夸娇鐢紝鐢ㄤ簬瀹氫箟鎶ュ悕椤典笂闇�瑕佹垚鍛樺~鍐欑殑瀛楁銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("wxcp_activity_field")
+@KeySequence("wxcp_activity_field_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxCpActivityFieldDO extends BaseDO {
+
+    /** 瀛楁绫诲瀷锛氭枃鏈� */
+    public static final String TYPE_TEXT = "text";
+    /** 瀛楁绫诲瀷锛氬琛屾枃鏈� */
+    public static final String TYPE_TEXTAREA = "textarea";
+    /** 瀛楁绫诲瀷锛氭暟瀛� */
+    public static final String TYPE_NUMBER = "number";
+    /** 瀛楁绫诲瀷锛氭棩鏈� */
+    public static final String TYPE_DATE = "date";
+    /** 瀛楁绫诲瀷锛氫笅鎷夊崟閫� */
+    public static final String TYPE_SELECT = "select";
+    /** 瀛楁绫诲瀷锛氬崟閫� */
+    public static final String TYPE_RADIO = "radio";
+    /** 瀛楁绫诲瀷锛氬閫� */
+    public static final String TYPE_CHECKBOX = "checkbox";
+    /** 瀛楁绫诲瀷锛氳瘎鍒嗭紙1~maxScore 鏄燂級 */
+    public static final String TYPE_RATING = "rating";
+
+    @TableId
+    private Long id;
+    /**
+     * 娲诲姩缂栧彿
+     */
+    private Long activityId;
+    /**
+     * 瀛楁鍚嶇О
+     */
+    private String fieldLabel;
+    /**
+     * 瀛楁绫诲瀷锛岃鏈被甯搁噺
+     */
+    private String fieldType;
+    /**
+     * 閫夐」锛屽涓敤鑻辨枃閫楀彿鍒嗛殧锛坰elect/radio/checkbox 浣跨敤锛�
+     */
+    private String fieldOptions;
+    /**
+     * 鏄惁蹇呭~
+     */
+    private Boolean required;
+    /**
+     * 杈撳叆鎻愮ず
+     */
+    private String placeholder;
+    /**
+     * 鎺掑簭鍙�
+     */
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupDO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupDO.java
index ca4932f..49a1ca3 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupDO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupDO.java
@@ -56,6 +56,10 @@
      * 鍙備笌璇存槑锛堟垚鍛樻彁浜ょ殑鏂囧瓧鍐呭锛�
      */
     private String content;
+    /**
+     * 璇勪环鏄熺骇锛堟暟鎹噰闆�/璇勪环妯℃澘浣跨敤锛�
+     */
+    private Integer ratingScore;
 
     /**
      * 鐘舵��
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupValueDO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupValueDO.java
new file mode 100644
index 0000000..f9156a3
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/activity/WxCpActivitySignupValueDO.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.wxcp.dal.dataobject.activity;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 浼佷笟寰俊娲诲姩 - 鎶ュ悕閲囬泦鍊� DO
+ *
+ * 涓�鏉℃姤鍚嶈褰曢噷姣忎釜鑷畾涔夊瓧娈电殑鍊煎瓨涓�琛岋紝瀛楁鍚�/绫诲瀷鍋氬揩鐓э紝
+ * 鍚庣画娲诲姩淇敼瀛楁涔熶笉浼氬奖鍝嶅巻鍙叉暟鎹殑鍚箟銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("wxcp_activity_signup_value")
+@KeySequence("wxcp_activity_signup_value_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxCpActivitySignupValueDO extends BaseDO {
+
+    @TableId
+    private Long id;
+    /**
+     * 鎶ュ悕璁板綍缂栧彿
+     */
+    private Long signupId;
+    /**
+     * 娲诲姩缂栧彿
+     */
+    private Long activityId;
+    /**
+     * 瀛楁缂栧彿
+     */
+    private Long fieldId;
+    /**
+     * 瀛楁鍚嶇О锛堝揩鐓э級
+     */
+    private String fieldLabel;
+    /**
+     * 瀛楁绫诲瀷锛堝揩鐓э級
+     */
+    private String fieldType;
+    /**
+     * 瀛楁鍊硷紱澶氶�夌敤鑻辨枃閫楀彿鎷兼帴
+     */
+    private String fieldValue;
+    /**
+     * 鎺掑簭鍙�
+     */
+    private Integer sortNo;
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/notice/WxCpNoticeDO.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/notice/WxCpNoticeDO.java
index 58d51e8..53551c4 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/notice/WxCpNoticeDO.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/dataobject/notice/WxCpNoticeDO.java
@@ -40,6 +40,37 @@
     private String content;
 
     /**
+     * 閫氱煡妯℃澘绫诲瀷
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeTypeEnum}
+     */
+    private Integer noticeType;
+    /**
+     * 鎽樿锛堝浘鏂囧崱鐗�/鍥炬枃娑堟伅浣跨敤锛�
+     */
+    private String summary;
+    /**
+     * 璺宠浆閾炬帴锛堝~鎶ャ�佹煡鐪嬭鎯呯瓑锛�
+     */
+    private String linkUrl;
+    /**
+     * 閾炬帴鎸夐挳鏂囨
+     */
+    private String linkBtnText;
+    /**
+     * 鍥炬枃娑堟伅灏侀潰鍥惧湴鍧�
+     */
+    private String coverUrl;
+    /**
+     * 鏂囦欢閫氱煡鐨勯檮浠� Blob ID
+     */
+    private Long fileBlobId;
+    /**
+     * 鏂囦欢閫氱煡鐨勯檮浠舵枃浠跺悕锛堝揩鐓э級
+     */
+    private String fileName;
+
+    /**
      * 鐘舵��
      *
      * 鏋氫妇 {@link WxCpNoticeStatusEnum}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivityFieldMapper.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivityFieldMapper.java
new file mode 100644
index 0000000..41d8050
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivityFieldMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.wxcp.dal.mysql.activity;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityFieldDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 浼佷笟寰俊娲诲姩閲囬泦瀛楁 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface WxCpActivityFieldMapper extends BaseMapperX<WxCpActivityFieldDO> {
+
+    default List<WxCpActivityFieldDO> selectListByActivityId(Long activityId) {
+        return selectList(new LambdaQueryWrapperX<WxCpActivityFieldDO>()
+                .eq(WxCpActivityFieldDO::getActivityId, activityId)
+                .orderByAsc(WxCpActivityFieldDO::getSortNo)
+                .orderByAsc(WxCpActivityFieldDO::getId));
+    }
+
+    default int deleteByActivityId(Long activityId) {
+        return delete(new LambdaQueryWrapperX<WxCpActivityFieldDO>()
+                .eq(WxCpActivityFieldDO::getActivityId, activityId));
+    }
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivitySignupValueMapper.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivitySignupValueMapper.java
new file mode 100644
index 0000000..eab744d
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/dal/mysql/activity/WxCpActivitySignupValueMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.wxcp.dal.mysql.activity;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupValueDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 浼佷笟寰俊娲诲姩鎶ュ悕閲囬泦鍊� Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface WxCpActivitySignupValueMapper extends BaseMapperX<WxCpActivitySignupValueDO> {
+
+    default List<WxCpActivitySignupValueDO> selectListBySignupId(Long signupId) {
+        return selectList(new LambdaQueryWrapperX<WxCpActivitySignupValueDO>()
+                .eq(WxCpActivitySignupValueDO::getSignupId, signupId)
+                .orderByAsc(WxCpActivitySignupValueDO::getSortNo)
+                .orderByAsc(WxCpActivitySignupValueDO::getId));
+    }
+
+    default int deleteBySignupId(Long signupId) {
+        return delete(new LambdaQueryWrapperX<WxCpActivitySignupValueDO>()
+                .eq(WxCpActivitySignupValueDO::getSignupId, signupId));
+    }
+
+    default int deleteByActivityId(Long activityId) {
+        return delete(new LambdaQueryWrapperX<WxCpActivitySignupValueDO>()
+                .eq(WxCpActivitySignupValueDO::getActivityId, activityId));
+    }
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/ErrorCodeConstants.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/ErrorCodeConstants.java
index 8422ac3..febce37 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/ErrorCodeConstants.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/ErrorCodeConstants.java
@@ -18,6 +18,14 @@
     ErrorCode ACTIVITY_STATUS_FINISHED = new ErrorCode(1_050_000_003, "娲诲姩宸茬粨鏉熸垨宸插彇娑堬紝鏃犳硶缁х画鎿嶄綔");
     ErrorCode ACTIVITY_PUSH_TARGET_EMPTY = new ErrorCode(1_050_000_004, "鎺ㄩ�佽寖鍥撮�夋嫨銆屾寚瀹氭垚鍛樸�嶆垨銆屾寚瀹氶儴闂ㄣ�嶆椂锛屾帹閫佺洰鏍囦笉鑳戒负绌�");
     ErrorCode ACTIVITY_PUSH_FAILED = new ErrorCode(1_050_000_005, "娲诲姩鎺ㄩ�佸け璐�");
+    ErrorCode ACTIVITY_IMAGE_REQUIRED = new ErrorCode(1_050_000_006, "鍥剧墖閫氱煡妯℃澘蹇呴』鑷冲皯涓婁紶 1 寮犲浘鐗囷紝璇蜂笂浼犲悗鍐嶆帹閫�");
+    ErrorCode ACTIVITY_FILE_REQUIRED = new ErrorCode(1_050_000_007, "闄勪欢閫氱煡妯℃澘蹇呴』鑷冲皯涓婁紶 1 涓檮浠讹紝璇蜂笂浼犲悗鍐嶆帹閫�");
+    ErrorCode ACTIVITY_FILE_NOT_EXISTS = new ErrorCode(1_050_000_008, "娲诲姩闄勪欢涓嶅瓨鍦ㄦ垨宸茶鍒犻櫎锛岃閲嶆柊涓婁紶");
+    ErrorCode ACTIVITY_FILE_TOO_LARGE = new ErrorCode(1_050_000_009, "娲诲姩闄勪欢瓒呰繃浼佷笟寰俊 20MB 闄愬埗锛岃鍘嬬缉鍚庨噸鏂颁笂浼�");
+    ErrorCode ACTIVITY_FIELD_LABEL_REQUIRED = new ErrorCode(1_050_000_010, "绗� {} 涓噰闆嗗瓧娈电殑鍚嶇О涓嶈兘涓虹┖锛岃濉啓瀛楁鍚嶇О");
+    ErrorCode ACTIVITY_FIELD_OPTIONS_REQUIRED = new ErrorCode(1_050_000_011,
+            "閲囬泦瀛楁銆恵}銆戞槸閫夋嫨鍨嬪瓧娈碉紝蹇呴』濉啓閫夐」锛屽涓�夐」鐢ㄨ嫳鏂囬�楀彿鍒嗛殧");
+    ErrorCode ACTIVITY_IMAGE_ONLY = new ErrorCode(1_050_000_012, "鍥剧墖閫氱煡鍙兘涓婁紶鍥剧墖鏂囦欢锛岃閲嶆柊閫夋嫨");
 
     // ========== 鎶ュ悕 1-050-001-000 ==========
     ErrorCode SIGNUP_ACTIVITY_NOT_PUBLISHED = new ErrorCode(1_050_001_000, "娲诲姩鏈彂甯冿紝鏆備笉鑳芥姤鍚�");
@@ -29,6 +37,10 @@
     ErrorCode SIGNUP_ALREADY_CANCELED = new ErrorCode(1_050_001_006, "璇ユ姤鍚嶅凡鍙栨秷");
     ErrorCode SIGNUP_ALREADY_CHECKED_IN = new ErrorCode(1_050_001_007, "璇ユ姤鍚嶅凡绛惧埌锛岃鍕块噸澶嶆搷浣�");
     ErrorCode SIGNUP_WX_USER_REQUIRED = new ErrorCode(1_050_001_008, "鏈幏鍙栧埌浼佷笟寰俊鎴愬憳韬唤锛岃浠庝紒涓氬井淇″唴鎵撳紑鎶ュ悕椤�");
+    ErrorCode SIGNUP_NOT_SUPPORTED = new ErrorCode(1_050_001_009, "璇ユ椿鍔ㄦā鏉夸笉闇�瑕佹姤鍚�/濉啓锛屾棤娉曟彁浜よ〃鍗�");
+    ErrorCode SIGNUP_FIELD_REQUIRED = new ErrorCode(1_050_001_010, "銆恵}銆戜负蹇呭~椤癸紝璇峰~鍐欏悗鍐嶆彁浜�");
+    ErrorCode SIGNUP_RATING_REQUIRED = new ErrorCode(1_050_001_011, "璇峰厛瀹屾垚璇勪环鎵撳垎鍚庡啀鎻愪氦");
+    ErrorCode SIGNUP_RATING_INVALID = new ErrorCode(1_050_001_012, "璇勪环鏄熺骇蹇呴』鍦� 1~{} 涔嬮棿锛岃閲嶆柊閫夋嫨");
 
     // ========== 浜岀淮鐮� 1-050-002-000 ==========
     ErrorCode QRCODE_GENERATE_FAILED = new ErrorCode(1_050_002_000, "鐢熸垚鎶ュ悕浜岀淮鐮佸け璐�");
@@ -40,8 +52,13 @@
     ErrorCode NOTICE_CANNOT_UPDATE = new ErrorCode(1_050_003_001, "閫氱煡宸叉挙鍥烇紝鏃犳硶淇敼");
     ErrorCode NOTICE_STATUS_REVOKED = new ErrorCode(1_050_003_002, "閫氱煡宸叉挙鍥烇紝鏃犳硶鍙戝竷");
     ErrorCode NOTICE_STATUS_NOT_PUBLISHED = new ErrorCode(1_050_003_003, "閫氱煡灏氭湭鍙戝竷锛岃鍏堝彂甯冨悗鍐嶆帹閫�");
+    ErrorCode NOTICE_CONTENT_REQUIRED = new ErrorCode(1_050_003_004, "绾枃鏈�氱煡鐨勬鏂囦笉鑳戒负绌猴紝璇峰~鍐欓�氱煡鍐呭");
+    ErrorCode NOTICE_LINK_URL_REQUIRED = new ErrorCode(1_050_003_005, "鍥炬枃鍗$墖/鍥炬枃娑堟伅鐨勮烦杞摼鎺ヤ笉鑳戒负绌猴紝璇峰~鍐欓摼鎺ュ湴鍧�");
+    ErrorCode NOTICE_FILE_REQUIRED = new ErrorCode(1_050_003_006, "鏂囦欢閫氱煡蹇呴』涓婁紶鏂囦欢锛岃涓婁紶鍚庡啀鎺ㄩ��");
+    ErrorCode NOTICE_FILE_NOT_EXISTS = new ErrorCode(1_050_003_007, "閫氱煡鏂囦欢涓嶅瓨鍦ㄦ垨宸茶鍒犻櫎锛岃閲嶆柊涓婁紶");
+    ErrorCode NOTICE_FILE_TOO_LARGE = new ErrorCode(1_050_003_008, "閫氱煡鏂囦欢瓒呰繃浼佷笟寰俊 20MB 闄愬埗锛岃鍘嬬缉鎴栨敼鐢ㄥ浘鏂囧崱鐗囨帹閫佷笅杞介摼鎺�");
 
     // ========== 浼佷笟寰俊閫氳褰� 1-050-004-000 ==========
-    ErrorCode WXCP_API_ERROR = new ErrorCode(1_050_004_000, "璋冪敤浼佷笟寰俊鎺ュ彛澶辫触");
+    ErrorCode WXCP_API_ERROR = new ErrorCode(1_050_004_000, "璋冪敤浼佷笟寰俊鎺ュ彛澶辫触锛歟rrcode={}锛宔rrmsg={}");
 
 }
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/activity/WxCpActivityTypeEnum.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/activity/WxCpActivityTypeEnum.java
new file mode 100644
index 0000000..810e4c4
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/activity/WxCpActivityTypeEnum.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.wxcp.enums.activity;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 浼佷笟寰俊娲诲姩妯℃澘绫诲瀷
+ *
+ * 涓嶅悓妯℃澘鍐冲畾锛氳〃鍗曢渶瑕佸~鍝簺瀛楁銆佹帹閫佹椂鍙戜粈涔堝舰鎬佺殑娑堟伅銆佹槸鍚﹂渶瑕佹姤鍚嶉〉銆�
+ */
+@Getter
+@AllArgsConstructor
+public enum WxCpActivityTypeEnum implements ArrayValuable<Integer> {
+
+    TEXT(1, "绾�氱煡", "鍙帹閫佷竴鏉$函鏂囨湰娑堟伅锛屼笉灞曠ず鎶ュ悕琛ㄥ崟"),
+    MESSAGE(2, "鍗曟潯娑堟伅", "鎺ㄩ�佷竴寮犲彲鐐瑰嚮鐨勫崱鐗囨秷鎭紝鐐瑰嚮鎵撳紑娲诲姩璇︽儏椤�"),
+    IMAGE(3, "鍥剧墖閫氱煡", "涓婁紶鍥剧墖鍚庢帹閫佸浘鐗囨秷鎭紝鍙檮甯︽枃瀛楄鏄�"),
+    FILE(4, "闄勪欢閫氱煡", "涓婁紶鏂囦欢鍚庢帹閫佹枃浠舵秷鎭紝鎺ユ敹浜哄彲鐩存帴涓嬭浇鏌ョ湅"),
+    FORM(5, "鏁版嵁閲囬泦/璇勪环", "鎺ㄩ�佸崱鐗囧埌閲囬泦椤碉紝鎴愬憳濉啓鑷畾涔夊瓧娈靛苟鍙彁浜よ瘎浠�"),
+    ;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(WxCpActivityTypeEnum::getType).toArray(Integer[]::new);
+
+    private final Integer type;
+    private final String name;
+    /**
+     * 妯℃澘璇存槑锛屽墠绔�夋嫨妯℃澘鏃跺睍绀�
+     */
+    private final String description;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+    public static WxCpActivityTypeEnum valueOf(Integer type) {
+        return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
+    }
+
+    /**
+     * 鏄惁闇�瑕佸睍绀烘姤鍚�/閲囬泦琛ㄥ崟
+     */
+    public static boolean needSignup(Integer type) {
+        return FORM.getType().equals(type);
+    }
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/notice/WxCpNoticeTypeEnum.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/notice/WxCpNoticeTypeEnum.java
new file mode 100644
index 0000000..38b8da3
--- /dev/null
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/enums/notice/WxCpNoticeTypeEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.wxcp.enums.notice;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 浼佷笟寰俊閫氱煡妯℃澘绫诲瀷
+ */
+@Getter
+@AllArgsConstructor
+public enum WxCpNoticeTypeEnum implements ArrayValuable<Integer> {
+
+    TEXT(1, "绾枃鏈�氱煡"),
+    CARD(2, "鍥炬枃鍗$墖锛堝~鎶�/閾炬帴锛�"),
+    FILE(3, "鏂囦欢閫氱煡"),
+    NEWS(4, "鍥炬枃娑堟伅"),
+    ;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(WxCpNoticeTypeEnum::getType).toArray(Integer[]::new);
+
+    private final Integer type;
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+    public static WxCpNoticeTypeEnum valueOf(Integer type) {
+        return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
+    }
+
+}
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityService.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityService.java
index b8cad24..a35dc41 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityService.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityService.java
@@ -6,7 +6,9 @@
 import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO;
 import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO;
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO;
+import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityFieldDO;
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO;
+import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupValueDO;
 import jakarta.validation.Valid;
 
 import java.util.List;
@@ -67,6 +69,46 @@
     List<SystemStorageBlobRespVO> getActivityAttachments(Long activityId);
 
     /**
+     * 鑾峰緱娲诲姩璇︽儏鍥剧墖闄勪欢鍒楄〃锛堟渶澶� 5 寮狅紝鎸変笂浼犲厛鍚庢帓搴忥級
+     *
+     * @param activityId 娲诲姩缂栧彿
+     * @return 闄勪欢锛堝惈璁块棶鍦板潃锛�
+     */
+    List<SystemStorageBlobRespVO> getActivityDetailAttachments(Long activityId);
+
+    /**
+     * 鑾峰緱鍥剧墖閫氱煡妯℃澘鐨勫浘鐗囬檮浠跺垪琛紙鏈�澶� 9 寮狅級
+     *
+     * @param activityId 娲诲姩缂栧彿
+     * @return 闄勪欢锛堝惈璁块棶鍦板潃锛�
+     */
+    List<SystemStorageBlobRespVO> getActivityImageAttachments(Long activityId);
+
+    /**
+     * 鑾峰緱闄勪欢閫氱煡妯℃澘鐨勯檮浠跺垪琛紙鏈�澶� 5 涓級
+     *
+     * @param activityId 娲诲姩缂栧彿
+     * @return 闄勪欢锛堝惈璁块棶鍦板潃锛�
+     */
+    List<SystemStorageBlobRespVO> getActivityFileAttachments(Long activityId);
+
+    /**
+     * 鑾峰緱娲诲姩鑷畾涔夐噰闆嗗瓧娈�
+     *
+     * @param activityId 娲诲姩缂栧彿
+     * @return 瀛楁鍒楄〃锛堟寜鎺掑簭鍙峰崌搴忥級
+     */
+    List<WxCpActivityFieldDO> getActivityFields(Long activityId);
+
+    /**
+     * 鑾峰緱鏌愭潯鎶ュ悕璁板綍鐨勮嚜瀹氫箟閲囬泦鍊�
+     *
+     * @param signupId 鎶ュ悕璁板綍缂栧彿
+     * @return 瀛楁鍊煎垪琛�
+     */
+    List<WxCpActivitySignupValueDO> getSignupValues(Long signupId);
+
+    /**
      * 鍙戝竷娲诲姩锛堣崏绋� -&gt; 宸插彂甯冿級
      *
      * @param id 娲诲姩缂栧彿
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityServiceImpl.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityServiceImpl.java
index ffc4d26..c8bdb5c 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityServiceImpl.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/activity/WxCpActivityServiceImpl.java
@@ -7,13 +7,20 @@
 import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentSaveReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageBlobDO;
 import cn.iocoder.yudao.module.system.service.storage.SystemStorageAttachmentService;
+import cn.iocoder.yudao.module.system.service.storage.SystemStorageBlobService;
 import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.*;
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO;
+import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityFieldDO;
 import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO;
+import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupValueDO;
+import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivityFieldMapper;
 import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivityMapper;
 import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivitySignupMapper;
+import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivitySignupValueMapper;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum;
+import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityTypeEnum;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpSignupStatusEnum;
 import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO;
@@ -28,14 +35,19 @@
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.io.File;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.*;
@@ -53,6 +65,11 @@
     private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
 
     /**
+     * 浼佷笟寰俊鏂囨湰娑堟伅绾� 2048 瀛楄妭锛屼腑鏂囨寜 3 瀛楄妭绠楋紝杩欓噷鎸夊瓧绗︽暟鐣欎綑閲�
+     */
+    private static final int MAX_PUSH_CONTENT_LENGTH = 600;
+
+    /**
      * 鎶ュ悕闄勪欢鐨勫叧鑱旇褰曠被鍨嬶紝瀵瑰簲 system_storage_attachment.record_type
      */
     public static final String SIGNUP_RECORD_TYPE = "wxcp_activity_signup";
@@ -63,17 +80,51 @@
     public static final String ACTIVITY_RECORD_TYPE = "wxcp_activity";
 
     /**
+     * 娲诲姩璇︽儏鍥剧墖鐨勫叧鑱旇褰曠被鍨嬶紝瀵瑰簲 system_storage_attachment.record_type
+     *
+     * 涓庡皝闈㈠垎寮�瀛橈細灏侀潰鍙厑璁� 1 寮犮�佹姤鍚嶉〉椤堕儴瑁佸壀灞曠ず锛�
+     * 璇︽儏鍥惧彲浠ュ寮犮�佹姤鍚嶉〉鎸夊師鍥炬瘮渚嬪畬鏁村睍绀猴紙浜岀淮鐮�/娴锋姤杩欑被涓嶈兘瑁佺殑鍥炬斁杩欓噷锛夈��
+     */
+    public static final String ACTIVITY_DETAIL_RECORD_TYPE = "wxcp_activity_detail";
+
+    /**
+     * 鍥剧墖閫氱煡妯℃澘鐨勫浘鐗囧叧鑱旇褰曠被鍨�
+     */
+    public static final String ACTIVITY_IMAGE_RECORD_TYPE = "wxcp_activity_image";
+
+    /**
+     * 闄勪欢閫氱煡妯℃澘鐨勯檮浠跺叧鑱旇褰曠被鍨�
+     */
+    public static final String ACTIVITY_FILE_RECORD_TYPE = "wxcp_activity_file";
+
+    /**
      * 闄勪欢鐢ㄩ�旓紝涓庡叾瀹冩ā鍧椾繚鎸佷竴鑷�
      */
     private static final String ATTACHMENT_APPLICATION = "file";
+
+    /**
+     * 浼佷笟寰俊鏂囦欢娑堟伅涓婇檺 20MB
+     */
+    private static final long MAX_FILE_SIZE = 20L * 1024L * 1024L;
+
+    /**
+     * 榛樿璇勪环鏈�楂樻槦绾�
+     */
+    private static final int DEFAULT_RATING_MAX = 5;
 
     @Resource
     private WxCpActivityMapper wxcpActivityMapper;
     @Resource
     private WxCpActivitySignupMapper wxcpSignupMapper;
+    @Resource
+    private WxCpActivityFieldMapper activityFieldMapper;
+    @Resource
+    private WxCpActivitySignupValueMapper signupValueMapper;
 
     @Resource
     private SystemStorageAttachmentService storageAttachmentService;
+    @Resource
+    private SystemStorageBlobService storageBlobService;
 
     @Resource
     private WxCpMessagePushService messagePushService;
@@ -90,6 +141,23 @@
         activity.setStatus(WxCpActivityStatusEnum.DRAFT.getStatus());
         activity.setSignupCount(0);
         activity.setPushStatus(0);
+        // 妯℃澘绫诲瀷锛氶粯璁ゃ�屾暟鎹噰闆�/璇勪环銆嶏紝涓庢敼閫犲墠鐨勬姤鍚嶆椿鍔ㄨ涓轰繚鎸佷竴鑷�
+        if (activity.getActivityType() == null) {
+            activity.setActivityType(WxCpActivityTypeEnum.FORM.getType());
+        }
+        if (activity.getRatingMax() == null || activity.getRatingMax() <= 0) {
+            activity.setRatingMax(DEFAULT_RATING_MAX);
+        }
+        if (activity.getRatingEnabled() == null) {
+            activity.setRatingEnabled(Boolean.FALSE);
+        }
+        // 鎶ュ悕涓婇檺鍙鏁版嵁閲囬泦妯℃澘鏈夋剰涔夛紱鍏跺畠妯℃澘涓嶄紶鏃舵寜涓嶉檺澶勭悊锛岄伩鍏嶆帴鍙e己鏍¢獙
+        if (activity.getSignupLimit() == null) {
+            activity.setSignupLimit(0);
+        }
+        if (StrUtil.isBlank(activity.getButtonText())) {
+            activity.setButtonText(defaultButtonText(activity.getActivityType()));
+        }
         // 鎺ㄩ�佽寖鍥撮�夊~锛屼笉浼犻粯璁ゅ叏鍛橈紱鎺ㄩ�佹椂杩樿兘鍦ㄥ脊绐楅噷鏀�
         if (activity.getPushScope() == null) {
             activity.setPushScope(WxCpPushScopeEnum.ALL.getScope());
@@ -98,6 +166,21 @@
         // 灏侀潰璧� system 鐨勯檮浠朵腑闂磋〃锛屼笟鍔¤〃涓嶈惤鏂囦欢鍦板潃瀛楁
         if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) {
             bindActivityAttachments(activity.getId(), createReqVO.getBlobIds());
+        }
+        // 璇︽儏鍥剧墖鍚屾牱璧伴檮浠朵腑闂磋〃锛屼笌灏侀潰鍒嗗紑瀛橈紝浜掍笉褰卞搷
+        if (CollUtil.isNotEmpty(createReqVO.getDetailBlobIds())) {
+            bindActivityDetailAttachments(activity.getId(), createReqVO.getDetailBlobIds());
+        }
+        // 鍥剧墖閫氱煡 / 闄勪欢閫氱煡鐨勭礌鏉愪篃璧伴檮浠朵腑闂磋〃锛屽悇鑷嫭绔� recordType
+        if (CollUtil.isNotEmpty(createReqVO.getImageBlobIds())) {
+            bindAttachments(ACTIVITY_IMAGE_RECORD_TYPE, activity.getId(), createReqVO.getImageBlobIds());
+        }
+        if (CollUtil.isNotEmpty(createReqVO.getFileBlobIds())) {
+            bindAttachments(ACTIVITY_FILE_RECORD_TYPE, activity.getId(), createReqVO.getFileBlobIds());
+        }
+        // 鏁版嵁閲囬泦妯℃澘鐨勮嚜瀹氫箟瀛楁
+        if (CollUtil.isNotEmpty(createReqVO.getFieldList())) {
+            saveActivityFields(activity.getId(), createReqVO.getFieldList());
         }
         return activity.getId();
     }
@@ -122,6 +205,23 @@
         if (updateReqVO.getBlobIds() != null) {
             bindActivityAttachments(activity.getId(), updateReqVO.getBlobIds());
         }
+        // 璇︽儏鍥剧墖鍚岀悊锛歯ull = 涓嶆敼鍔紝绌烘暟缁� = 娓呯┖
+        if (updateReqVO.getDetailBlobIds() != null) {
+            bindActivityDetailAttachments(activity.getId(), updateReqVO.getDetailBlobIds());
+        }
+        if (updateReqVO.getImageBlobIds() != null) {
+            bindAttachments(ACTIVITY_IMAGE_RECORD_TYPE, activity.getId(), updateReqVO.getImageBlobIds());
+        }
+        if (updateReqVO.getFileBlobIds() != null) {
+            bindAttachments(ACTIVITY_FILE_RECORD_TYPE, activity.getId(), updateReqVO.getFileBlobIds());
+        }
+        // 瀛楁鍒楄〃锛歯ull = 涓嶆敼鍔紝绌烘暟缁� = 娓呯┖
+        if (updateReqVO.getFieldList() != null) {
+            activityFieldMapper.deleteByActivityId(activity.getId());
+            if (CollUtil.isNotEmpty(updateReqVO.getFieldList())) {
+                saveActivityFields(activity.getId(), updateReqVO.getFieldList());
+            }
+        }
     }
 
     @Override
@@ -129,8 +229,13 @@
     public void deleteActivity(Long id) {
         validateActivityExists(id);
         wxcpActivityMapper.deleteById(id);
-        // 娲诲姩鍒犱簡锛屽皝闈㈤檮浠跺叧绯讳竴骞舵竻鎺夛紝閬垮厤娈嬬暀鎴愬鍎块檮浠�
+        // 娲诲姩鍒犱簡锛屾墍鏈夐檮浠跺叧绯讳笌閲囬泦瀛楁涓�骞舵竻鎺夛紝閬垮厤娈嬬暀鎴愬鍎挎暟鎹�
         storageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId(ACTIVITY_RECORD_TYPE, id);
+        storageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId(ACTIVITY_DETAIL_RECORD_TYPE, id);
+        storageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId(ACTIVITY_IMAGE_RECORD_TYPE, id);
+        storageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId(ACTIVITY_FILE_RECORD_TYPE, id);
+        activityFieldMapper.deleteByActivityId(id);
+        signupValueMapper.deleteByActivityId(id);
     }
 
     @Override
@@ -148,6 +253,69 @@
         return listAttachments(ACTIVITY_RECORD_TYPE, activityId);
     }
 
+    @Override
+    public List<SystemStorageBlobRespVO> getActivityDetailAttachments(Long activityId) {
+        List<SystemStorageBlobRespVO> attachments = listAttachments(ACTIVITY_DETAIL_RECORD_TYPE, activityId);
+        // 澶氬紶鍥惧繀椤讳繚璇佸睍绀洪『搴忕ǔ瀹氾細搴曞眰鏄� selectByIds锛屼笉鎵胯杩斿洖椤哄簭锛�
+        // 杩欓噷鎸� blobId 鍗囧簭锛堝嵆涓婁紶鍏堝悗锛夋帓涓�閬嶏紝閬垮厤鍚屼竴鎵瑰浘姣忔鍒锋柊鎹綅缃��
+        return attachments.stream()
+                .sorted(Comparator.comparing(SystemStorageBlobRespVO::getId,
+                        Comparator.nullsLast(Comparator.naturalOrder())))
+                .toList();
+    }
+
+    @Override
+    public List<SystemStorageBlobRespVO> getActivityImageAttachments(Long activityId) {
+        return listAttachments(ACTIVITY_IMAGE_RECORD_TYPE, activityId);
+    }
+
+    @Override
+    public List<SystemStorageBlobRespVO> getActivityFileAttachments(Long activityId) {
+        return listAttachments(ACTIVITY_FILE_RECORD_TYPE, activityId);
+    }
+
+    @Override
+    public List<WxCpActivityFieldDO> getActivityFields(Long activityId) {
+        return activityFieldMapper.selectListByActivityId(activityId);
+    }
+
+    @Override
+    public List<WxCpActivitySignupValueDO> getSignupValues(Long signupId) {
+        return signupValueMapper.selectListBySignupId(signupId);
+    }
+
+    /**
+     * 淇濆瓨娲诲姩鑷畾涔夐噰闆嗗瓧娈�
+     *
+     * 鍏堟暣浣撴竻绌哄啀鎻掑叆锛屼繚璇佸墠绔彁浜ょ殑椤哄簭涓庡唴瀹瑰氨鏄渶缁堢粨鏋滐紱
+     * 宸蹭骇鐢熺殑鎶ュ悕鍊煎仛瀛楁鍚�/绫诲瀷蹇収锛屼笉浼氬洜涓鸿繖閲岄噸寤哄瓧娈佃�屼涪澶卞惈涔夈��
+     */
+    private void saveActivityFields(Long activityId, List<WxCpActivityFieldSaveReqVO> fieldList) {
+        int index = 1;
+        for (WxCpActivityFieldSaveReqVO field : fieldList) {
+            if (field == null || StrUtil.isBlank(field.getFieldLabel())) {
+                throw exception(ACTIVITY_FIELD_LABEL_REQUIRED, index);
+            }
+            String fieldType = StrUtil.blankToDefault(field.getFieldType(), WxCpActivityFieldDO.TYPE_TEXT);
+            boolean needOptions = WxCpActivityFieldDO.TYPE_SELECT.equals(fieldType)
+                    || WxCpActivityFieldDO.TYPE_RADIO.equals(fieldType)
+                    || WxCpActivityFieldDO.TYPE_CHECKBOX.equals(fieldType);
+            if (needOptions && StrUtil.isBlank(field.getFieldOptions())) {
+                throw exception(ACTIVITY_FIELD_OPTIONS_REQUIRED, field.getFieldLabel());
+            }
+            activityFieldMapper.insert(WxCpActivityFieldDO.builder()
+                    .activityId(activityId)
+                    .fieldLabel(StrUtil.trim(field.getFieldLabel()))
+                    .fieldType(fieldType)
+                    .fieldOptions(StrUtil.trim(field.getFieldOptions()))
+                    .required(Boolean.TRUE.equals(field.getRequired()))
+                    .placeholder(StrUtil.trim(field.getPlaceholder()))
+                    .sortNo(field.getSortNo() != null ? field.getSortNo() : index)
+                    .build());
+            index++;
+        }
+    }
+
     /**
      * 缁戝畾娲诲姩灏侀潰闄勪欢
      *
@@ -159,9 +327,20 @@
      * 閭d釜鏂规硶浼氳繛 blob 涓�璧峰垹鎺夛紝鍚屼竴涓枃浠堕噸鏂扮粦瀹氭椂浼氬洜涓烘枃浠跺凡涓嶅瓨鍦ㄨ�屾煡涓嶅嚭鏉ャ��
      */
     private void bindActivityAttachments(Long activityId, List<Long> blobIds) {
+        bindAttachments(ACTIVITY_RECORD_TYPE, activityId, blobIds);
+    }
+
+    /**
+     * 缁戝畾娲诲姩璇︽儏鍥剧墖闄勪欢锛坮ecord_type = wxcp_activity_detail锛夛紝瑙勫垯鍚屽皝闈�
+     */
+    private void bindActivityDetailAttachments(Long activityId, List<Long> blobIds) {
+        bindAttachments(ACTIVITY_DETAIL_RECORD_TYPE, activityId, blobIds);
+    }
+
+    private void bindAttachments(String recordType, Long activityId, List<Long> blobIds) {
         SystemStorageAttachmentSaveReqVO bindReqVO = new SystemStorageAttachmentSaveReqVO();
         bindReqVO.setApplication(ATTACHMENT_APPLICATION);
-        bindReqVO.setRecordType(ACTIVITY_RECORD_TYPE);
+        bindReqVO.setRecordType(recordType);
         bindReqVO.setRecordId(activityId);
         bindReqVO.setBlobItems(toBlobItems(blobIds));
         storageAttachmentService.bindAttachments(bindReqVO);
@@ -235,10 +414,28 @@
             scope = WxCpPushScopeEnum.ALL.getScope();
         }
 
-        // 鏂囨湰鍗$墖娑堟伅锛氭寜閽洿杈炬姤鍚嶉〉锛岀偣娑堟伅鎴栨壂鐮侀兘鑳芥姤鍚�
-        WxCpPushResultVO result = messagePushService.pushCard(
-                activity.getTitle(), buildPushDescription(activity),
-                buildSignupUrl(activity.getId()), "绔嬪嵆鎶ュ悕", scope, target);
+        // 鎸夋椿鍔ㄦā鏉跨被鍨嬮�夋嫨鎺ㄩ�佸舰鎬�
+        Integer activityType = activity.getActivityType() != null
+                ? activity.getActivityType() : WxCpActivityTypeEnum.FORM.getType();
+        WxCpPushResultVO result;
+        if (Objects.equals(activityType, WxCpActivityTypeEnum.TEXT.getType())) {
+            result = messagePushService.pushText(buildTextContent(activity), scope, target);
+        } else if (Objects.equals(activityType, WxCpActivityTypeEnum.IMAGE.getType())) {
+            result = pushImages(activity, scope, target);
+        } else if (Objects.equals(activityType, WxCpActivityTypeEnum.FILE.getType())) {
+            result = pushFiles(activity, scope, target);
+        } else if (Objects.equals(activityType, WxCpActivityTypeEnum.MESSAGE.getType())) {
+            String url = tryBuildSignupUrl(activity.getId());
+            result = StrUtil.isBlank(url)
+                    ? messagePushService.pushText(buildTextContent(activity), scope, target)
+                    : messagePushService.pushCard(activity.getTitle(), buildMessageDescription(activity),
+                    url, pushButtonText(activity), scope, target);
+        } else {
+            // 鏁版嵁閲囬泦/璇勪环锛氬崱鐗囩洿杈鹃噰闆嗛〉锛岀偣娑堟伅鎴栨壂鐮侀兘鑳藉~鍐�
+            result = messagePushService.pushCard(
+                    activity.getTitle(), buildPushDescription(activity),
+                    buildSignupUrl(activity.getId()), pushButtonText(activity), scope, target);
+        }
 
         if (Boolean.TRUE.equals(result.getSuccess())) {
             wxcpActivityMapper.updateById(WxCpActivityDO.builder()
@@ -280,6 +477,150 @@
         return sb.toString();
     }
 
+    /**
+     * 绾枃鏈�/鍥剧墖/闄勪欢妯℃澘鐨勬鏂囷細鏍囬 + 鏃堕棿鍦扮偣 + 娲诲姩璇︽儏锛岃繃闀挎埅鏂伩鍏嶆暣鏉℃秷鎭彂閫佸け璐�
+     */
+    private String buildTextContent(WxCpActivityDO activity) {
+        StringBuilder body = new StringBuilder();
+        if (activity.getStartTime() != null) {
+            body.append("娲诲姩鏃堕棿锛�").append(activity.getStartTime().format(TIME_FMT));
+            if (activity.getEndTime() != null) {
+                body.append(" ~ ").append(activity.getEndTime().format(TIME_FMT));
+            }
+            body.append("\n");
+        }
+        if (StrUtil.isNotBlank(activity.getLocation())) {
+            body.append("娲诲姩鍦扮偣锛�").append(activity.getLocation()).append("\n");
+        }
+        if (StrUtil.isNotBlank(activity.getContent())) {
+            body.append(activity.getContent());
+        }
+        String content = "銆�" + activity.getTitle() + "銆慭n" + body.toString().trim();
+        if (content.length() > MAX_PUSH_CONTENT_LENGTH) {
+            content = content.substring(0, MAX_PUSH_CONTENT_LENGTH)
+                    + "\n鈥︹�︼紙鍐呭杩囬暱宸叉埅鏂紝璇峰埌绯荤粺鍐呮煡鐪嬪畬鏁村唴瀹癸級";
+        }
+        return content;
+    }
+
+    /**
+     * 鍗曟潯娑堟伅妯℃澘鐨勫崱鐗囨鏂囷細娲诲姩鎽樿 + 寮曞鐐瑰嚮
+     */
+    private String buildMessageDescription(WxCpActivityDO activity) {
+        String body = buildTextContent(activity);
+        // textcard 鐨� description 绾� 512 瀛楄妭锛岀暀鍑轰綑閲忥紝閬垮厤鏁存潯鍗$墖鍙戦�佸け璐�
+        if (body.length() > 300) {
+            body = body.substring(0, 300) + "鈥︹��";
+        }
+        return body + "\n鐐瑰嚮涓嬫柟鎸夐挳鏌ョ湅璇︽儏";
+    }
+
+    private String pushButtonText(WxCpActivityDO activity) {
+        return StrUtil.blankToDefault(activity.getButtonText(),
+                defaultButtonText(activity.getActivityType()));
+    }
+
+    private String defaultButtonText(Integer activityType) {
+        if (Objects.equals(activityType, WxCpActivityTypeEnum.MESSAGE.getType())) {
+            return "鏌ョ湅璇︽儏";
+        }
+        return "绔嬪嵆鎶ュ悕";
+    }
+
+    /**
+     * 鎷艰娲诲姩椤靛湴鍧�锛涙湭閰嶇疆澶栫綉鍓嶇紑鏃惰繑鍥� null锛岀敱璋冪敤鏂归檷绾т负鏂囨湰鎺ㄩ��
+     */
+    private String tryBuildSignupUrl(Long id) {
+        try {
+            return buildSignupUrl(id);
+        } catch (Exception e) {
+            log.warn("[tryBuildSignupUrl][鏈厤缃椿鍔ㄩ〉澶栫綉鍦板潃锛岄檷绾т负鏂囨湰鎺ㄩ�� id={}]", id);
+            return null;
+        }
+    }
+
+    /**
+     * 鍥剧墖閫氱煡锛氬厛鍙戞枃瀛楄鏄庯紝鍐嶉�愬紶鍙戦�佸浘鐗�
+     */
+    private WxCpPushResultVO pushImages(WxCpActivityDO activity, Integer scope, String target) {
+        List<SystemStorageBlobRespVO> images = getActivityImageAttachments(activity.getId());
+        if (CollUtil.isEmpty(images)) {
+            throw exception(ACTIVITY_IMAGE_REQUIRED);
+        }
+        List<WxCpPushResultVO> results = new ArrayList<>();
+        // 鏈夋鏂囧氨鍏堝彂涓�鏉℃枃瀛楄鏄庯紱鏂囧瓧澶辫触涓嶅奖鍝嶅悗缁浘鐗囧彂閫�
+        if (StrUtil.isNotBlank(activity.getContent())) {
+            results.add(messagePushService.pushText(buildTextContent(activity), scope, target));
+        }
+        for (SystemStorageBlobRespVO image : images) {
+            SystemStorageBlobDO blob = storageBlobService.getStorageBlob(image.getId());
+            if (blob == null) {
+                throw exception(ACTIVITY_FILE_NOT_EXISTS);
+            }
+            if (StrUtil.isNotBlank(blob.getContentType())
+                    && !StrUtil.startWithIgnoreCase(blob.getContentType(), "image/")) {
+                throw exception(ACTIVITY_IMAGE_ONLY);
+            }
+            File file = storageBlobService.getPublicFile(blob.getUidFilename(), blob.getResourceKey());
+            if (file == null || !file.exists()) {
+                throw exception(ACTIVITY_FILE_NOT_EXISTS);
+            }
+            results.add(messagePushService.pushImage(file, scope, target));
+        }
+        return aggregatePushResults(results, "鍥剧墖");
+    }
+
+    /**
+     * 闄勪欢閫氱煡锛氬厛鍙戞枃瀛楄鏄庯紝鍐嶉�愪釜鍙戦�佹枃浠�
+     */
+    private WxCpPushResultVO pushFiles(WxCpActivityDO activity, Integer scope, String target) {
+        List<SystemStorageBlobRespVO> files = getActivityFileAttachments(activity.getId());
+        if (CollUtil.isEmpty(files)) {
+            throw exception(ACTIVITY_FILE_REQUIRED);
+        }
+        List<WxCpPushResultVO> results = new ArrayList<>();
+        if (StrUtil.isNotBlank(activity.getContent())) {
+            results.add(messagePushService.pushText(buildTextContent(activity), scope, target));
+        }
+        for (SystemStorageBlobRespVO item : files) {
+            SystemStorageBlobDO blob = storageBlobService.getStorageBlob(item.getId());
+            if (blob == null) {
+                throw exception(ACTIVITY_FILE_NOT_EXISTS);
+            }
+            if (blob.getByteSize() != null && blob.getByteSize() > MAX_FILE_SIZE) {
+                throw exception(ACTIVITY_FILE_TOO_LARGE);
+            }
+            File file = storageBlobService.getPublicFile(blob.getUidFilename(), blob.getResourceKey());
+            if (file == null || !file.exists()) {
+                throw exception(ACTIVITY_FILE_NOT_EXISTS);
+            }
+            results.add(messagePushService.pushFile(file, blob.getOriginalFilename(), null, scope, target));
+        }
+        return aggregatePushResults(results, "闄勪欢");
+    }
+
+    /**
+     * 澶氭潯娑堟伅鍚堝苟缁撴灉锛氫换涓�鏉″け璐ュ嵆瑙嗕负鏁翠綋澶辫触锛屽苟鎶婂け璐ュ師鍥犳眹鎬荤粰鍓嶇
+     */
+    private WxCpPushResultVO aggregatePushResults(List<WxCpPushResultVO> results, String label) {
+        if (CollUtil.isEmpty(results)) {
+            return new WxCpPushResultVO(false, null, label + "鎺ㄩ�佸唴瀹逛负绌�", null, null);
+        }
+        List<WxCpPushResultVO> failures = results.stream()
+                .filter(r -> !Boolean.TRUE.equals(r.getSuccess()))
+                .toList();
+        if (failures.isEmpty()) {
+            return new WxCpPushResultVO(true, 0, "鎺ㄩ�佹垚鍔�", null, null);
+        }
+        String message = failures.stream()
+                .map(WxCpPushResultVO::getMessage)
+                .filter(StrUtil::isNotBlank)
+                .distinct()
+                .collect(Collectors.joining("锛�"));
+        return new WxCpPushResultVO(false, failures.get(0).getErrCode(),
+                label + "鎺ㄩ�佸け璐ワ細" + message, null, null);
+    }
+
     @Override
     public byte[] generateSignupQrCode(Long id, int size) {
         validateActivityExists(id);
@@ -311,6 +652,12 @@
         if (!Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.PUBLISHED.getStatus())) {
             throw exception(SIGNUP_ACTIVITY_NOT_PUBLISHED);
         }
+        // 鍙湁銆屾暟鎹噰闆�/璇勪环銆嶆ā鏉挎湁琛ㄥ崟锛屽叾瀹冩ā鏉垮彧鍋氭秷鎭帹閫�
+        Integer activityType = activity.getActivityType() != null
+                ? activity.getActivityType() : WxCpActivityTypeEnum.FORM.getType();
+        if (!Objects.equals(activityType, WxCpActivityTypeEnum.FORM.getType())) {
+            throw exception(SIGNUP_NOT_SUPPORTED);
+        }
 
         // 2. 鏍¢獙鎶ュ悕鏃堕棿绐楀彛
         LocalDateTime now = LocalDateTime.now();
@@ -326,6 +673,12 @@
                 && activity.getSignupCount() != null && activity.getSignupCount() >= activity.getSignupLimit()) {
             throw exception(SIGNUP_FULL);
         }
+
+        // 3.1 鏍¢獙鑷畾涔夐噰闆嗗瓧娈典笌璇勪环锛岄伩鍏嶆妸绌哄�煎啓杩涘簱
+        List<WxCpActivityFieldDO> fields = getActivityFields(activity.getId());
+        List<WxCpActivitySignupValueDO> fieldValues =
+                buildSignupValues(activity.getId(), fields, reqVO.getFieldValues());
+        validateRating(activity, reqVO.getRatingScore());
 
         // 4. 鍘婚噸锛氭湁浼佷笟寰俊璐﹀彿鎸夎处鍙锋煡锛涙病鏈夎处鍙蜂絾鏈夋墜鏈哄彿鍒欐寜鎵嬫満鍙锋煡锛�
         //    涓よ�呴兘娌℃湁鏃舵棤娉曞垽瀹氳韩浠斤紝涓嶅仛鍘婚噸鈥斺�斿惁鍒欑┖鎵嬫満鍙蜂細浜掔浉璇垽鎴愬悓涓�浜�
@@ -348,13 +701,16 @@
                     .mobile(mobile)
                     .deptName(reqVO.getDeptName())
                     .content(reqVO.getContent())
+                    .ratingScore(reqVO.getRatingScore())
                     .remark(reqVO.getRemark())
                     .status(WxCpSignupStatusEnum.SIGNED_UP.getStatus())
                     .signupTime(now)
                     .checkInTime(null)
                     .build());
-            // 閲嶆柊鎶ュ悕鏃舵棫闄勪欢浣滃簾锛岀洿鎺ラ噸鏂扮粦瀹氾紙bindAttachments 浼氬厛娓呮棫鍏宠仈锛屼笖涓嶄細鍒犳帀鏂囦欢鏈韩锛�
+            // 閲嶆柊鎶ュ悕鏃舵棫闄勪欢/閲囬泦鍊间綔搴燂紝鎸夋湰娆℃彁浜よ鐩�
             bindSignupAttachments(exists.getId(), reqVO.getBlobIds());
+            signupValueMapper.deleteBySignupId(exists.getId());
+            insertSignupValues(fieldValues, exists.getId());
             incrementSignupCount(activity.getId());
             return exists.getId();
         }
@@ -365,12 +721,14 @@
         // 濮撳悕銆佹墜鏈哄彿鏀逛负閫夊~锛氱粺涓�瀛樼┖涓茶�屼笉鏄� null锛岄伩鍏嶅悗缁寜鎵嬫満鍙锋煡閲嶆椂绌哄�艰涔夋贩涔�
         signup.setName(StrUtil.trimToEmpty(reqVO.getName()));
         signup.setMobile(mobile);
+        signup.setRatingScore(reqVO.getRatingScore());
         signup.setStatus(WxCpSignupStatusEnum.SIGNED_UP.getStatus());
         signup.setSignupTime(now);
         wxcpSignupMapper.insert(signup);
 
-        // 6. 缁戝畾鎶ュ悕浜轰笂浼犵殑鍥剧墖/闄勪欢
+        // 6. 缁戝畾鎶ュ悕浜轰笂浼犵殑鍥剧墖/闄勪欢涓庨噰闆嗗瓧娈靛��
         bindSignupAttachments(signup.getId(), reqVO.getBlobIds());
+        insertSignupValues(fieldValues, signup.getId());
 
         // 7. 鐢� SQL 鑷锛岄伩鍏嶅苟鍙戜笅鐨勮鏀瑰啓涓㈠け鏇存柊
         incrementSignupCount(activity.getId());
@@ -398,6 +756,65 @@
         return listAttachments(SIGNUP_RECORD_TYPE, signupId);
     }
 
+    /**
+     * 缁勮鎶ュ悕閲囬泦鍊硷細鎸夊瓧娈靛畾涔夐�愰」鏍¢獙蹇呭~锛屾病濉殑瀛楁涓嶈惤琛�
+     */
+    private List<WxCpActivitySignupValueDO> buildSignupValues(Long activityId, List<WxCpActivityFieldDO> fields,
+                                                               List<WxCpSignupValueReqVO> reqValues) {
+        if (CollUtil.isEmpty(fields)) {
+            return Collections.emptyList();
+        }
+        Map<Long, String> valueMap = CollUtil.isEmpty(reqValues) ? Collections.emptyMap()
+                : reqValues.stream()
+                .filter(v -> v != null && v.getFieldId() != null)
+                .collect(Collectors.toMap(WxCpSignupValueReqVO::getFieldId,
+                        v -> StrUtil.trimToEmpty(v.getFieldValue()), (a, b) -> b));
+        List<WxCpActivitySignupValueDO> result = new ArrayList<>();
+        for (WxCpActivityFieldDO field : fields) {
+            String value = StrUtil.trimToEmpty(valueMap.get(field.getId()));
+            if (Boolean.TRUE.equals(field.getRequired()) && StrUtil.isBlank(value)) {
+                throw exception(SIGNUP_FIELD_REQUIRED, field.getFieldLabel());
+            }
+            if (StrUtil.isBlank(value)) {
+                continue;
+            }
+            result.add(WxCpActivitySignupValueDO.builder()
+                    .activityId(activityId)
+                    .fieldId(field.getId())
+                    .fieldLabel(field.getFieldLabel())
+                    .fieldType(field.getFieldType())
+                    .fieldValue(value)
+                    .sortNo(field.getSortNo())
+                    .build());
+        }
+        return result;
+    }
+
+    private void insertSignupValues(List<WxCpActivitySignupValueDO> values, Long signupId) {
+        if (CollUtil.isEmpty(values)) {
+            return;
+        }
+        values.forEach(value -> value.setSignupId(signupId));
+        signupValueMapper.insertBatch(values);
+    }
+
+    /**
+     * 寮�鍚瘎浠锋椂鏍¢獙鏄熺骇鑼冨洿
+     */
+    private void validateRating(WxCpActivityDO activity, Integer score) {
+        if (!Boolean.TRUE.equals(activity.getRatingEnabled())) {
+            return;
+        }
+        if (score == null) {
+            throw exception(SIGNUP_RATING_REQUIRED);
+        }
+        int max = activity.getRatingMax() != null && activity.getRatingMax() > 0
+                ? activity.getRatingMax() : DEFAULT_RATING_MAX;
+        if (score < 1 || score > max) {
+            throw exception(SIGNUP_RATING_INVALID, max);
+        }
+    }
+
     private void incrementSignupCount(Long activityId) {
         wxcpActivityMapper.update(null, new LambdaUpdateWrapper<WxCpActivityDO>()
                 .setSql("signup_count = signup_count + 1")
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/member/WxCpMemberServiceImpl.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/member/WxCpMemberServiceImpl.java
index efdfdd1..2b32fdb 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/member/WxCpMemberServiceImpl.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/member/WxCpMemberServiceImpl.java
@@ -4,6 +4,7 @@
 import cn.iocoder.yudao.module.wxcp.controller.admin.member.vo.WxCpMemberRespVO;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.bean.WxCpDepart;
 import me.chanjar.weixin.cp.bean.WxCpUser;
@@ -73,7 +74,8 @@
             users = wxCpService.getUserService().listSimpleByDepartment(deptId, child, null);
         } catch (Exception e) {
             log.error("[getMemberList][鑾峰彇浼佷笟寰俊鎴愬憳澶辫触 deptId={} fetchChild={}]", deptId, child, e);
-            throw exception(WXCP_API_ERROR);
+            throwWxCpApiError(e);
+            return List.of();
         }
 
         // 閮ㄩ棬鍚嶆槧灏勶紝鐢ㄤ簬鎶婃垚鍛樼殑閮ㄩ棬ID缈昏瘧鎴愬彲璇诲悕绉�
@@ -94,10 +96,22 @@
             return departs != null ? departs : List.of();
         } catch (Exception e) {
             log.error("[listDeparts][鑾峰彇浼佷笟寰俊閮ㄩ棬澶辫触]", e);
-            throw exception(WXCP_API_ERROR);
+            throwWxCpApiError(e);
+            return List.of();
         }
     }
 
+    /**
+     * 鎶婁紒涓氬井淇℃帴鍙e紓甯歌浆鎴愬甫 errcode/errmsg 鐨勫彲璇绘彁绀猴紝渚夸簬瀹氫綅 IP 鐧藉悕鍗曘�丼ecret 绛夐棶棰�
+     */
+    private void throwWxCpApiError(Exception e) {
+        if (e instanceof WxErrorException wxError && wxError.getError() != null) {
+            throw exception(WXCP_API_ERROR,
+                    wxError.getError().getErrorCode(), wxError.getError().getErrorMsg());
+        }
+        throw exception(WXCP_API_ERROR, "unknown", e.getMessage());
+    }
+
     private WxCpDepartmentRespVO toDeptVO(WxCpDepart depart) {
         WxCpDepartmentRespVO vo = new WxCpDepartmentRespVO();
         vo.setId(depart.getId());
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/notice/WxCpNoticeServiceImpl.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/notice/WxCpNoticeServiceImpl.java
index 494e29e..25fcabf 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/notice/WxCpNoticeServiceImpl.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/notice/WxCpNoticeServiceImpl.java
@@ -11,12 +11,16 @@
 import cn.iocoder.yudao.module.wxcp.dal.mysql.notice.WxCpNoticeMapper;
 import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum;
 import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeStatusEnum;
+import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeTypeEnum;
 import cn.iocoder.yudao.module.wxcp.service.push.WxCpMessagePushService;
+import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageBlobDO;
+import cn.iocoder.yudao.module.system.service.storage.SystemStorageBlobService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.io.File;
 import java.time.LocalDateTime;
 import java.util.Objects;
 
@@ -43,10 +47,18 @@
     private WxCpNoticeMapper wxcpNoticeMapper;
     @Resource
     private WxCpMessagePushService messagePushService;
+    @Resource
+    private SystemStorageBlobService storageBlobService;
+
+    /** 浼佷笟寰俊鏂囦欢娑堟伅涓婇檺 20MB */
+    private static final long MAX_FILE_SIZE = 20L * 1024L * 1024L;
 
     @Override
     public Long createNotice(WxCpNoticeSaveReqVO createReqVO) {
         WxCpNoticeDO notice = BeanUtils.toBean(createReqVO, WxCpNoticeDO.class);
+        if (notice.getNoticeType() == null) {
+            notice.setNoticeType(WxCpNoticeTypeEnum.TEXT.getType());
+        }
         notice.setStatus(WxCpNoticeStatusEnum.DRAFT.getStatus());
         notice.setPushStatus(0);
         // 鎺ㄩ�佽寖鍥撮�夊~锛屼笉浼犻粯璁ゅ叏鍛橈紱鎺ㄩ�佹椂杩樿兘鍦ㄥ脊绐楅噷鏀�
@@ -121,9 +133,45 @@
             scope = WxCpPushScopeEnum.ALL.getScope();
         }
 
-        // 閫氱煡鐢ㄧ函鏂囨湰鎺ㄩ�侊細娌℃湁鍙烦杞殑璇︽儏椤碉紝鑰� textcard 绫诲瀷寮哄埗瑕佹眰 url
-        WxCpPushResultVO result = messagePushService.pushText(
-                buildPushContent(notice), scope, target);
+        // 鎺ㄩ�佹椂鍏佽閲嶆柊閫夋嫨妯℃澘绫诲瀷锛涙湭閫夋嫨鍒欐部鐢ㄩ�氱煡涓婁繚瀛樼殑妯℃澘
+        Integer noticeType = pushReqVO.getNoticeType() != null
+                ? pushReqVO.getNoticeType()
+                : (notice.getNoticeType() != null ? notice.getNoticeType() : WxCpNoticeTypeEnum.TEXT.getType());
+        String summary = StrUtil.blankToDefault(pushReqVO.getSummary(), notice.getSummary());
+        String linkUrl = StrUtil.blankToDefault(pushReqVO.getLinkUrl(), notice.getLinkUrl());
+        String linkBtnText = StrUtil.blankToDefault(pushReqVO.getLinkBtnText(), notice.getLinkBtnText());
+        String coverUrl = StrUtil.blankToDefault(pushReqVO.getCoverUrl(), notice.getCoverUrl());
+        Long fileBlobId = pushReqVO.getFileBlobId() != null ? pushReqVO.getFileBlobId() : notice.getFileBlobId();
+        String fileName = StrUtil.blankToDefault(pushReqVO.getFileName(), notice.getFileName());
+
+        // 鎸夋湰娆¢�夋嫨鐨勬ā鏉跨被鍨嬫牎楠屽繀濉」
+        if (Objects.equals(noticeType, WxCpNoticeTypeEnum.TEXT.getType())
+                && StrUtil.isBlank(notice.getContent())) {
+            throw exception(NOTICE_CONTENT_REQUIRED);
+        }
+        if ((Objects.equals(noticeType, WxCpNoticeTypeEnum.CARD.getType())
+                || Objects.equals(noticeType, WxCpNoticeTypeEnum.NEWS.getType()))
+                && StrUtil.isBlank(linkUrl)) {
+            throw exception(NOTICE_LINK_URL_REQUIRED);
+        }
+        if (Objects.equals(noticeType, WxCpNoticeTypeEnum.FILE.getType()) && fileBlobId == null) {
+            throw exception(NOTICE_FILE_REQUIRED);
+        }
+
+        WxCpPushResultVO result;
+        if (Objects.equals(noticeType, WxCpNoticeTypeEnum.CARD.getType())) {
+            result = messagePushService.pushCard(notice.getTitle(),
+                    StrUtil.blankToDefault(summary, notice.getContent()),
+                    linkUrl, StrUtil.blankToDefault(linkBtnText, "鍘诲~鎶�"), scope, target);
+        } else if (Objects.equals(noticeType, WxCpNoticeTypeEnum.FILE.getType())) {
+            result = pushFileNotice(fileBlobId, fileName, notice, scope, target);
+        } else if (Objects.equals(noticeType, WxCpNoticeTypeEnum.NEWS.getType())) {
+            result = messagePushService.pushNews(notice.getTitle(),
+                    StrUtil.blankToDefault(summary, notice.getContent()),
+                    linkUrl, coverUrl, scope, target);
+        } else {
+            result = messagePushService.pushText(buildPushContent(notice), scope, target);
+        }
 
         if (Boolean.TRUE.equals(result.getSuccess())) {
             wxcpNoticeMapper.updateById(WxCpNoticeDO.builder()
@@ -138,6 +186,50 @@
     }
 
     /**
+     * 鏂囦欢閫氱煡锛氫粠绯荤粺闄勪欢涓績鍙栨枃浠讹紝浜ょ粰鎺ㄩ�佹湇鍔′笂浼犱紒涓氬井淇″苟鍙戦��
+     */
+    private WxCpPushResultVO pushFileNotice(Long fileBlobId, String fileName, WxCpNoticeDO notice,
+                                            Integer scope, String target) {
+        if (fileBlobId == null) {
+            throw exception(NOTICE_FILE_REQUIRED);
+        }
+        SystemStorageBlobDO blob = storageBlobService.getStorageBlob(fileBlobId);
+        if (blob == null) {
+            throw exception(NOTICE_FILE_NOT_EXISTS);
+        }
+        if (blob.getByteSize() != null && blob.getByteSize() > MAX_FILE_SIZE) {
+            throw exception(NOTICE_FILE_TOO_LARGE);
+        }
+        File file = storageBlobService.getPublicFile(blob.getUidFilename(), blob.getResourceKey());
+        if (file == null || !file.exists()) {
+            throw exception(NOTICE_FILE_NOT_EXISTS);
+        }
+        String actualFileName = StrUtil.blankToDefault(fileName, blob.getOriginalFilename());
+        return messagePushService.pushFile(file, actualFileName, buildPushContent(notice), scope, target);
+    }
+
+    /**
+     * 鎸夋ā鏉跨被鍨嬫牎楠屽繀濉」
+     */
+    private void validateNoticeByType(WxCpNoticeSaveReqVO reqVO) {
+        Integer type = reqVO.getNoticeType() != null
+                ? reqVO.getNoticeType() : WxCpNoticeTypeEnum.TEXT.getType();
+        if (Objects.equals(type, WxCpNoticeTypeEnum.TEXT.getType())
+                && StrUtil.isBlank(reqVO.getContent())) {
+            throw exception(NOTICE_CONTENT_REQUIRED);
+        }
+        if ((Objects.equals(type, WxCpNoticeTypeEnum.CARD.getType())
+                || Objects.equals(type, WxCpNoticeTypeEnum.NEWS.getType()))
+                && StrUtil.isBlank(reqVO.getLinkUrl())) {
+            throw exception(NOTICE_LINK_URL_REQUIRED);
+        }
+        if (Objects.equals(type, WxCpNoticeTypeEnum.FILE.getType())
+                && reqVO.getFileBlobId() == null) {
+            throw exception(NOTICE_FILE_REQUIRED);
+        }
+    }
+
+    /**
      * 鎷艰鎺ㄩ�佹鏂囷細鏍囬鍔犵矖鏍峰紡鐢ㄤ笉浜嗭紝鏀圭敤銆愩�戝寘瑁癸紱杩囬暱鍒欐埅鏂苟鎻愮ず
      */
     private String buildPushContent(WxCpNoticeDO notice) {
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushService.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushService.java
index 7343ca5..32540a9 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushService.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushService.java
@@ -35,4 +35,41 @@
      */
     WxCpPushResultVO pushText(String content, Integer scope, String target);
 
+    /**
+     * 鎺ㄩ�佸浘鏂囨秷鎭紙news锛�
+     *
+     * @param title       鏍囬
+     * @param description 鎽樿
+     * @param url         鐐瑰嚮璺宠浆鍦板潃
+     * @param picUrl      灏侀潰鍥惧湴鍧�锛堥�夊~锛�
+     * @param scope       鎺ㄩ�佽寖鍥�
+     * @param target      鎺ㄩ�佺洰鏍�
+     * @return 鎺ㄩ�佺粨鏋�
+     */
+    WxCpPushResultVO pushNews(String title, String description, String url, String picUrl,
+                              Integer scope, String target);
+
+    /**
+     * 鎺ㄩ�佹枃浠舵秷鎭細鍏堟妸鏂囦欢涓婁紶鍒颁紒涓氬井淇℃崲鍙� mediaId锛屽啀鍙戦�佹枃浠�
+     *
+     * @param file        鏂囦欢
+     * @param fileName    鏂囦欢鍚嶏紙浠呯敤浜庢棩蹇楋級
+     * @param textContent 鏂囦欢鍓嶇殑璇存槑鏂囨湰锛屽彲涓虹┖
+     * @param scope       鎺ㄩ�佽寖鍥�
+     * @param target      鎺ㄩ�佺洰鏍�
+     * @return 鎺ㄩ�佺粨鏋�
+     */
+    WxCpPushResultVO pushFile(java.io.File file, String fileName, String textContent,
+                              Integer scope, String target);
+
+    /**
+     * 鎺ㄩ�佸浘鐗囨秷鎭細鍏堟妸鍥剧墖涓婁紶鍒颁紒涓氬井淇℃崲鍙� mediaId锛屽啀鍙戦�佸浘鐗�
+     *
+     * @param file   鍥剧墖鏂囦欢
+     * @param scope  鎺ㄩ�佽寖鍥�
+     * @param target 鎺ㄩ�佺洰鏍�
+     * @return 鎺ㄩ�佺粨鏋�
+     */
+    WxCpPushResultVO pushImage(java.io.File file, Integer scope, String target);
+
 }
diff --git a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushServiceImpl.java b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushServiceImpl.java
index 59a8a6e..2feb14c 100644
--- a/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushServiceImpl.java
+++ b/yudao-module-wxcp/src/main/java/cn/iocoder/yudao/module/wxcp/service/push/WxCpMessagePushServiceImpl.java
@@ -7,6 +7,8 @@
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.cp.bean.article.NewArticle;
 import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
 import org.springframework.stereotype.Service;
@@ -58,6 +60,77 @@
         return doSend(message, scope, target, "text");
     }
 
+    @Override
+    public WxCpPushResultVO pushNews(String title, String description, String url, String picUrl,
+                                     Integer scope, String target) {
+        if (StrUtil.isBlank(url)) {
+            throw new IllegalArgumentException("鎺ㄩ�佸浘鏂囨秷鎭椂 url 涓嶈兘涓虹┖");
+        }
+        NewArticle article = NewArticle.builder()
+                .title(title)
+                .description(StrUtil.blankToDefault(description, ""))
+                .url(url)
+                .picUrl(picUrl)
+                .build();
+        WxCpMessage message = WxCpMessage.NEWS()
+                .agentId(agentId())
+                .addArticle(article)
+                .build();
+        return doSend(message, scope, target, "news");
+    }
+
+    @Override
+    public WxCpPushResultVO pushFile(java.io.File file, String fileName, String textContent,
+                                     Integer scope, String target) {
+        if (file == null || !file.exists()) {
+            return new WxCpPushResultVO(false, null, "鏂囦欢涓嶅瓨鍦紝鏃犳硶鎺ㄩ��", null, null);
+        }
+        try {
+            // 1. 涓婁紶鏂囦欢鍒颁紒涓氬井淇★紝鎹㈠彇 mediaId
+            WxMediaUploadResult upload = wxCpService.getMediaService().upload("file", file);
+            if (upload == null || StrUtil.isBlank(upload.getMediaId())) {
+                return new WxCpPushResultVO(false, null, "鏂囦欢涓婁紶浼佷笟寰俊澶辫触", null, null);
+            }
+            // 2. 鍏堝彂閫佽鏄庢枃鏈紝鍐嶅彂閫佹枃浠讹紱鏂囨湰澶辫触涓嶉樆濉炴枃浠跺彂閫�
+            if (StrUtil.isNotBlank(textContent)) {
+                pushText(textContent, scope, target);
+            }
+            // 3. 鍙戦�佹枃浠舵秷鎭�
+            WxCpMessage message = WxCpMessage.FILE()
+                    .agentId(agentId())
+                    .mediaId(upload.getMediaId())
+                    .build();
+            WxCpPushResultVO result = doSend(message, scope, target, "file");
+            log.info("[pushFile][鏂囦欢鎺ㄩ�佸畬鎴� fileName={} success={}]", fileName, result.getSuccess());
+            return result;
+        } catch (Exception e) {
+            log.error("[pushFile][鏂囦欢涓婁紶鎴栨帹閫佸紓甯� fileName={}]", fileName, e);
+            return new WxCpPushResultVO(false, null, "鏂囦欢鎺ㄩ�佸紓甯革細" + e.getMessage(), null, null);
+        }
+    }
+
+    @Override
+    public WxCpPushResultVO pushImage(java.io.File file, Integer scope, String target) {
+        if (file == null || !file.exists()) {
+            return new WxCpPushResultVO(false, null, "鍥剧墖鏂囦欢涓嶅瓨鍦紝鏃犳硶鎺ㄩ��", null, null);
+        }
+        try {
+            // 浼佷笟寰俊鍥剧墖娑堟伅瑕佹眰鍏堢敤 image 绫诲瀷涓婁紶鎹㈠彇 mediaId
+            WxMediaUploadResult upload = wxCpService.getMediaService().upload("image", file);
+            if (upload == null || StrUtil.isBlank(upload.getMediaId())) {
+                return new WxCpPushResultVO(false, null, "鍥剧墖涓婁紶浼佷笟寰俊澶辫触", null, null);
+            }
+            WxCpMessage message = WxCpMessage.IMAGE()
+                    .agentId(agentId())
+                    .mediaId(upload.getMediaId())
+                    .build();
+            return doSend(message, scope, target, "image");
+        } catch (Exception e) {
+            log.error("[pushImage][鍥剧墖涓婁紶鎴栨帹閫佸紓甯� file={}]", file.getName(), e);
+            return new WxCpPushResultVO(false, null, "鍥剧墖鎺ㄩ�佸紓甯革細" + e.getMessage(), null, null);
+        }
+    }
+
     /**
      * 缁熶竴澶勭悊鎺ユ敹鑰呰В鏋愩�佸彂閫佷笌閿欒鍖呰
      */
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index cc28bf8..e567161 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -206,6 +206,27 @@
             <version>${revision}</version>
         </dependency>
 
+        <!-- 璧勪骇绠$悊妯″潡 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-asset</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 鐢ㄨ溅绠$悊妯″潡 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-vehicle</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 閲囪喘绠$悊妯″潡 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-purchase</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- spring boot 閰嶇疆鎵�闇�渚濊禆 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml
index 8cd79b1..8f914d5 100644
--- a/yudao-server/src/main/resources/application-local.yaml
+++ b/yudao-server/src/main/resources/application-local.yaml
@@ -65,16 +65,16 @@
       primary: master
       datasource:
         master:
-          url: jdbc:mysql://localhost:3306/mom-jssm?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 杩炴帴鐨勭ず渚�
+          url: jdbc:mysql://36.213.150.237:10000/mom-jssm?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # 鐩磋繛娴嬭瘯鏈嶅姟鍣� MySQL锛堝涓绘満 10000 -> 瀹瑰櫒 3306锛夛紝涓嶅啀渚濊禆鏈湴 VMware 铏氭嫙鏈�
           username: root
-          password: 123456
+          password: xd@123456..
 
   # Redis 閰嶇疆銆俁edisson 榛樿鐨勯厤缃冻澶熶娇鐢紝涓�鑸笉闇�瑕佽繘琛岃皟浼�
   data:
     redis:
-      host: localhost # 鍦板潃
-      port: 6379 # 绔彛
-      database: 5 # 鏁版嵁搴撶储寮�
+      host: 36.213.150.237 # 鍦板潃锛堟祴璇曟湇鍔″櫒 Redis锛屽涓绘満 9999 -> 瀹瑰櫒 6379锛屾棤瀵嗙爜锛�
+      port: 9999 # 绔彛
+      database: 8 # 鏁版嵁搴撶储寮曘�傛湇鍔″櫒涓� db5 宸茶 mom-chdl 鍗犵敤銆乨b3 琚� mom-jssm 绾夸笂瀹炰緥鍗犵敤锛屾湰鍦扮敤绌洪棽鐨� db8 閬垮厤涓叉暟鎹�
 #      password: sugaozhixing.cn # 瀵嗙爜锛屽缓璁敓浜х幆澧冨紑鍚�
 
 --- #################### 瀹氭椂浠诲姟鐩稿叧閰嶇疆 ####################
@@ -192,7 +192,7 @@
   storage: # 鏂囦欢涓婁紶閰嶇疆
     path: D:/uploads # 鏂囦欢瀛樺偍鏍圭洰褰曪紙鎸夊疄闄呮湇鍔″櫒璺緞閰嶇疆锛�
     url-prefix: /admin-api/system/storage-blob # URL璺緞鍓嶇紑
-    domain: http://192.168.1.6:48080 # 鍩熷悕鍓嶇紑锛堟寜瀹為檯鍩熷悕閰嶇疆锛�
+    domain: ${STORAGE_DOMAIN:http://127.0.0.1:48080} # 鏂囦欢璁块棶鍩熷悕鍓嶇紑锛涙湰鍦伴粯璁ゆ湰鏈猴紝閮ㄧ讲鏃剁敤 STORAGE_DOMAIN 瑕嗙洊
     expired: 120 # token杩囨湡鏃堕棿锛堝垎閽燂級
     use-limit: 10 # token浣跨敤娆℃暟涓婇檺
     jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWT瀵嗛挜锛堣嚦灏�32瀛楃锛�
@@ -220,11 +220,11 @@
       render-dpi: 200 # 鎵弿椤佃浆鍥剧殑 DPI锛屼綆浜� 200 涓枃瀛楀舰浼氱硦
       max-xlsx-rows: 300 # Excel 鍗曡〃鏈�澶氳鍙栫殑琛屾暟
   database-backup: # 鏁版嵁搴撳浠介厤缃�
-    host: localhost # 澶囦唤鐩爣 MySQL 涓绘満锛岀暀绌鸿嚜鍔ㄤ粠鏁版嵁婧� JDBC URL 瑙f瀽
-    port: 3306 # 澶囦唤鐩爣 MySQL 绔彛锛岀暀绌鸿嚜鍔ㄤ粠鏁版嵁婧� JDBC URL 瑙f瀽
-    database: ruoyi-vue-pro # 澶囦唤鐩爣鏁版嵁搴擄紝鐣欑┖鑷姩浠庢暟鎹簮 JDBC URL 瑙f瀽
+    host: 36.213.150.237 # 澶囦唤鐩爣 MySQL 涓绘満锛岀暀绌鸿嚜鍔ㄤ粠鏁版嵁婧� JDBC URL 瑙f瀽
+    port: 10000 # 澶囦唤鐩爣 MySQL 绔彛锛岀暀绌鸿嚜鍔ㄤ粠鏁版嵁婧� JDBC URL 瑙f瀽
+    database: mom-jssm # 澶囦唤鐩爣鏁版嵁搴擄紝鐣欑┖鑷姩浠庢暟鎹簮 JDBC URL 瑙f瀽
     username: root # 澶囦唤璐﹀彿锛岀暀绌轰娇鐢ㄦ暟鎹簮璐﹀彿
-    password: '123456' # 澶囦唤瀵嗙爜锛岀暀绌轰娇鐢ㄦ暟鎹簮瀵嗙爜
+    password: 'xd@123456..' # 澶囦唤瀵嗙爜锛岀暀绌轰娇鐢ㄦ暟鎹簮瀵嗙爜
     command: # mysqldump 鍛戒护锛岀暀绌鸿嚜鍔ㄦ娴嬮儴缃茬幆澧冿紙鏈満 mysqldump 鎴� Docker MySQL 瀹瑰櫒锛夛紱鍙墜鍔ㄦ寚瀹氾紝濡傦細docker exec mysql8 mysqldump
 
 --- #################### 浼佷笟寰俊閰嶇疆 ####################

--
Gitblit v1.9.3