From 06f814c1275892783291d4b26b3271d5c2b33ffb Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 29 九月 2026 14:18:09 +0800
Subject: [PATCH] feat(archive): 添加档案管理模块及相关功能

---
 src/views/archive/stat/index.vue                          |  123 ++
 src/api/research/application/index.ts                     |    9 
 src/api/archive/stat/index.ts                             |   36 
 src/views/ums/member/index.vue                            |   37 
 src/api/archive/category/index.ts                         |   57 +
 src/views/archive/document/modules/form.vue               |  133 ++
 src/views/archive/borrow/detail/approve/index.vue         |  125 ++
 src/views/archive/category/data.ts                        |  192 +++
 src/views/archive/borrow/modules/form.vue                 |  133 ++
 src/views/archive/borrow/modules/process-select-modal.vue |   77 +
 src/api/finance/reimbursement/index.ts                    |    6 
 src/views/finance/reimbursement/index.vue                 |    4 
 src/views/archive/borrow/index.vue                        |  256 +++++
 src/views/archive/category/modules/form.vue               |   98 ++
 src/views/archive/borrow/modules/return-form.vue          |   82 +
 /dev/null                                                 |  143 --
 src/views/archive/stat/data.ts                            |   68 +
 src/api/archive/borrow/index.ts                           |  106 ++
 src/views/archive/document/index.vue                      |  163 +++
 src/api/archive/document/index.ts                         |   75 +
 src/views/archive/document/data.ts                        |  442 +++++++++
 src/views/archive/category/index.vue                      |  163 +++
 src/api/ums/member/index.ts                               |    9 
 src/views/archive/borrow/data.ts                          |  307 ++++++
 src/packages/constants/src/dict-enum.ts                   |   11 
 src/views/research/application/index.vue                  |   26 
 26 files changed, 2,732 insertions(+), 149 deletions(-)

diff --git a/.playwright-mcp/_e2e_attachment.txt b/.playwright-mcp/_e2e_attachment.txt
deleted file mode 100644
index a8359df..0000000
--- a/.playwright-mcp/_e2e_attachment.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-璇鹃鐢虫姤涔﹂檮浠讹紙E2E 娴嬭瘯锛�
-
-璇鹃鍚嶇О锛氶潰鍚戞櫤鑳藉伐鍘傜殑鏁板瓧瀛敓鍏抽敭鎶�鏈爺绌�
-鐢虫姤浜猴細寮犱紵
-
-鏈枃浠剁敤浜庣鍒扮楠岃瘉闄勪欢涓婁紶涓庣粦瀹氭祦绋嬨��
diff --git a/.playwright-mcp/page-2026-09-28T10-11-52-064Z.yml b/.playwright-mcp/page-2026-09-28T10-11-52-064Z.yml
deleted file mode 100644
index 3ae6b7c..0000000
--- a/.playwright-mcp/page-2026-09-28T10-11-52-064Z.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-- generic [active] [ref=f34e1]:
-  - banner [ref=f34e2]:
-    - generic [ref=f34e3]: MOM
-    - heading "椤圭洰涓氬姟鍏ㄦ櫙" [level=1] [ref=f34e5]
-    - textbox "鎼滅储涓氬姟锛氶攢鍞鍗� / 宸ュ崟 / IQC / 搴撳瓨 / 鍥炴鈥�" [ref=f34e7]
-  - navigation [ref=f34e8]:
-    - generic [ref=f34e9]: 鎬昏
-    - generic [ref=f34e10] [cursor=pointer]: 椤圭洰姒傝
-    - generic [ref=f34e12] [cursor=pointer]: 涓氬姟鍏ㄦ櫙鍥�
-    - generic [ref=f34e14]: 鏍稿績涓氬姟娴佺▼
-    - generic [ref=f34e15] [cursor=pointer]: 閿�鍞祦绋�
-    - generic [ref=f34e17] [cursor=pointer]: 閲囪喘娴佺▼
-    - generic [ref=f34e19] [cursor=pointer]: 鐢熶骇娴佺▼
-    - generic [ref=f34e21] [cursor=pointer]: 璐ㄩ噺娴佺▼
-    - generic [ref=f34e23] [cursor=pointer]: 浠撳偍娴佺▼
-    - generic [ref=f34e25] [cursor=pointer]: 鍞悗娴佺▼
-    - generic [ref=f34e27] [cursor=pointer]: 璐㈠姟鎶ラ攢娴佺▼
-    - generic [ref=f34e29] [cursor=pointer]: 浼氬憳鍏ヤ細娴佺▼
-    - generic [ref=f34e31] [cursor=pointer]: 璇鹃绠$悊娴佺▼
-    - generic [ref=f34e33]: 缁撴瀯涓庢暟鎹�
-    - generic [ref=f34e34] [cursor=pointer]: 绯荤粺鍏崇郴
-    - generic [ref=f34e36] [cursor=pointer]: 鏁版嵁娴�
-    - generic [ref=f34e38] [cursor=pointer]: 涓氬姟瀵硅薄
-    - generic [ref=f34e40] [cursor=pointer]: 瑙掕壊鏉冮檺
-    - generic [ref=f34e42]: 鎵╁睍
-    - generic [ref=f34e43] [cursor=pointer]: AI 涓氬姟
-    - generic [ref=f34e45] [cursor=pointer]: 寰呯‘璁や簨椤�
-  - main [ref=f34e47]
-  - contentinfo [ref=f34e48]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-09-28T10-11-57-412Z.yml b/.playwright-mcp/page-2026-09-28T10-11-57-412Z.yml
deleted file mode 100644
index 279bf8d..0000000
--- a/.playwright-mcp/page-2026-09-28T10-11-57-412Z.yml
+++ /dev/null
@@ -1,143 +0,0 @@
-- generic [active] [ref=f34e1]:
-  - banner [ref=f34e2]:
-    - generic [ref=f34e3]: MOM
-    - generic [ref=f34e4]:
-      - heading "MOM 宸ュ巶鏁板瓧鍖栧钩鍙帮紙mom-pro2锛� 路 涓氬姟鍏ㄦ櫙" [level=1] [ref=f34e49]
-      - generic [ref=f34e50]: 娣峰悎绯荤粺 | MOM 鍒堕�犺繍钀ョ鐞嗙郴缁�
-    - textbox "鎼滅储涓氬姟锛氶攢鍞鍗� / 宸ュ崟 / IQC / 搴撳瓨 / 鍥炴鈥�" [ref=f34e7]
-  - navigation [ref=f34e8]:
-    - generic [ref=f34e9]: 鎬昏
-    - generic [ref=f34e10] [cursor=pointer]: 椤圭洰姒傝
-    - generic [ref=f34e12] [cursor=pointer]: 涓氬姟鍏ㄦ櫙鍥�
-    - generic [ref=f34e14]: 鏍稿績涓氬姟娴佺▼
-    - generic [ref=f34e15] [cursor=pointer]: 閿�鍞祦绋�
-    - generic [ref=f34e17] [cursor=pointer]: 閲囪喘娴佺▼
-    - generic [ref=f34e19] [cursor=pointer]: 鐢熶骇娴佺▼
-    - generic [ref=f34e21] [cursor=pointer]: 璐ㄩ噺娴佺▼
-    - generic [ref=f34e23] [cursor=pointer]: 浠撳偍娴佺▼
-    - generic [ref=f34e25] [cursor=pointer]: 鍞悗娴佺▼
-    - generic [ref=f34e27] [cursor=pointer]: 璐㈠姟鎶ラ攢娴佺▼
-    - generic [ref=f34e29] [cursor=pointer]: 浼氬憳鍏ヤ細娴佺▼
-    - generic [ref=f34e31] [cursor=pointer]: 璇鹃绠$悊娴佺▼
-    - generic [ref=f34e33]: 缁撴瀯涓庢暟鎹�
-    - generic [ref=f34e34] [cursor=pointer]: 绯荤粺鍏崇郴
-    - generic [ref=f34e36] [cursor=pointer]: 鏁版嵁娴�
-    - generic [ref=f34e38] [cursor=pointer]: 涓氬姟瀵硅薄
-    - generic [ref=f34e40] [cursor=pointer]: 瑙掕壊鏉冮檺
-    - generic [ref=f34e42]: 鎵╁睍
-    - generic [ref=f34e43] [cursor=pointer]: AI 涓氬姟
-    - generic [ref=f34e45] [cursor=pointer]: 寰呯‘璁や簨椤�
-  - main [ref=f34e47]:
-    - generic [ref=f34e184]:
-      - heading "璇鹃绠$悊娴佺▼" [level=2] [ref=f34e185]
-      - paragraph [ref=f34e186]: 鐙珛绉戠爺妯″潡锛坹udao-module-research锛夛細瀛︿細鍙戝竷璇鹃鎸囧崡锛堥噸鐐�/涓�鑸垎绫伙級鈫� 浼氬憳鍦ㄧ嚎鎻愪氦璇鹃鐢虫姤涔� 鈫� 鎻愪氦 BPM 涓夌骇瀹℃壒锛堢涔﹀褰㈠紡瀹℃煡 鈫� 涓撳璇勫 鈫� 瀛︿細绔嬮」瀹℃壒锛夛紝涓撳璇勫垎浠ョ粨鏋勫寲鏄庣粏瀛愯〃璁板綍骞剁敱鍚庣鑷姩姹囨�诲姞鏉冨潎鍒嗐�佹帓鍚嶄笌鎺ㄨ崘缁撹 鈫� 瀹℃壒閫氳繃鐢熸垚绔嬮」缂栧彿骞跺彂甯冪郴缁熷叕鍛� 鈫� 鎸夋壒娆$櫥璁扮粡璐规嫧浠樿褰曪紙鍚嚟璇侀檮浠讹級銆傞櫌绾т簩鏈熷啀琛ョ粨椤归獙鏀朵笌鎴愭灉褰掓。銆�
-      - generic [ref=f34e187]:
-        - generic [ref=f34e188]:
-          - generic [ref=f34e189]: 璇鹃绠$悊娴佺▼ 路 mermaid/14-research-flow.mmd
-          - generic [ref=f34e190]:
-            - button "锛�" [ref=f34e191] [cursor=pointer]
-            - button "锛�" [ref=f34e192] [cursor=pointer]
-            - button "閲嶇疆" [ref=f34e193] [cursor=pointer]
-            - button "鍏ㄥ睆" [ref=f34e194] [cursor=pointer]
-        - document [ref=f34e198]:
-          - generic [ref=f34e200]:
-            - generic [ref=f34e201]:
-              - paragraph [ref=f34e208]: 绔嬮」瀹℃壒 BPM research_topic_approve
-              - paragraph [ref=f34e215]: 鍦ㄧ嚎鐢虫姤
-              - paragraph [ref=f34e222]: 璇鹃鎸囧崡鍙戝竷
-            - generic [ref=f34e253]:
-              - paragraph [ref=f34e259]: 宸蹭骇鐢熺敵鎶ュ崟鍒欑姝㈠垹闄�
-              - paragraph [ref=f34e265]: publish 鍙戝竷
-              - paragraph [ref=f34e271]: close 鎴
-              - paragraph [ref=f34e277]: 鎸囧崡宸插彂甯冩墠鍙綔涓虹敵鎶ュ墠鎻�
-              - paragraph [ref=f34e283]: delete 鍒犻櫎路浠呰崏绋� 0 鏀捐路杩炲甫娓呯悊闄勪欢
-              - paragraph [ref=f34e289]: submit 閫夊鎵规祦绋� processDefinitionKey
-              - paragraph [ref=f34e295]: "submit 鍓嶉�愭潯鏍¢獙: 鈶� 鎸囧崡蹇呴』宸插彂甯� 鈶� 褰撳墠鏃堕棿蹇呴』鍦ㄧ敵鎶ヨ捣姝㈡湡鍐�"
-              - paragraph [ref=f34e301]: 浠呭鎵逛腑鍙鍒犳敼涓撳璇勫垎
-              - paragraph [ref=f34e307]: 鍚庣閫愯閲嶇畻 compositeScore = 閫夐0.3 + 鏂规0.3 + 鍥㈤槦0.2 + 鎴愭灉0.2
-              - paragraph [ref=f34e313]: "鍥炲啓涓诲崟: reviewAvgScore=鍏ㄩ儴涓撳鍧囧垎 路 reviewPass=鍧囧垎杈炬爣 涓� 澶氭暟缁撹涓烘帹鑽愮珛椤�"
-              - paragraph [ref=f34e319]: 绉樹功澶勬眹鎬荤敵鎶ヤ功涓庤瘎瀹$粨鏋�
-              - paragraph [ref=f34e325]: 閫氳繃
-              - paragraph [ref=f34e331]: 閫氳繃
-              - paragraph [ref=f34e337]: BPM 瀹℃壒閫氳繃
-              - paragraph [ref=f34e343]: BPM 瀹℃壒椹冲洖
-              - paragraph [ref=f34e349]: BPM 瀹℃壒鍙栨秷
-              - paragraph [ref=f34e355]: 椹冲洖
-              - paragraph [ref=f34e361]: 淇敼鍚庨噸鏂� submit
-              - paragraph [ref=f34e367]: publishNotice 鍙戝竷绔嬮」閫氱煡
-              - paragraph [ref=f34e373]: 鐧昏鎷ㄦ璁板綍 research_fund_record
-              - paragraph [ref=f34e379]: "閲戦鏍¢獙: 宸茬櫥璁伴噾棰� + 鏈閲戦 涓嶅緱瓒呭嚭鐢虫姤鍗曠敵璇风粡璐孤疯秴鍑烘椂鎶ュ嚭鏈 宸茬櫥璁� 鍚堣 棰勭畻鍥涢」鏁板��"
-              - paragraph [ref=f34e385]: 浠呭緟鎷ㄤ粯 0 鍙敼鍙垹
-              - paragraph [ref=f34e391]: 鐘舵�佹敼涓哄凡鎷ㄤ粯鏃跺繀椤诲凡鏈夊嚟璇侀檮浠�
-            - generic [ref=f34e392]:
-              - paragraph [ref=f34e399]: 鏂板缓鎸囧崡路閫夌被鍒� 1閲嶇偣璇鹃 / 2涓�鑸棰�
-              - paragraph [ref=f34e406]: 濉寚鍗楀悕绉� 鐢虫姤璧锋鏃堕棿 鎸囧崡姝f枃 闄勪欢
-              - paragraph [ref=f34e413]: 鎸囧崡缂栧彿 GL + yyyyMMdd + 6浣嶆祦姘�(Redis路姣忔棩閲嶇疆)
-              - paragraph [ref=f34e420]: 鑽夌 0路浠呰崏绋垮彲鏀瑰彲鍒�
-              - paragraph [ref=f34e427]: 鎻愮ず璇ユ寚鍗椾笅宸叉湁 N 寮犵敵鎶ュ崟路闇�鍏堝鐞嗙敵鎶ュ崟
-              - paragraph [ref=f34e434]: 宸插彂甯� 20路鍥炲啓 publishTime
-              - paragraph [ref=f34e441]: 宸叉埅姝� 30
-              - paragraph [ref=f34e448]: 閫夋寚鍗椔疯姹傚凡鍙戝竷 涓斿湪鐢虫姤鏈熷唴
-              - paragraph [ref=f34e455]: 閫変細鍛� ums_member 鑷姩甯﹀嚭 濮撳悕 宸ヤ綔鍗曚綅 鑱岀О 鐢佃瘽 閭
-              - paragraph [ref=f34e462]: 濉棰樺悕绉� 璇鹃缁勬垚鍛� 鐢宠缁忚垂 鐢虫姤涔︽鏂� 鐢虫姤闄勪欢
-              - paragraph [ref=f34e469]: 鐢虫姤缂栧彿 SB + yyyyMMdd + 6浣嶆祦姘绰风被鍒啑浣欒嚜鎸囧崡涓嶄俊浠诲墠绔紶鍊�
-              - paragraph [ref=f34e476]: 鑽夌 0
-              - paragraph [ref=f34e483]: 瀹℃壒涓� 15路鍥炲啓 processInstanceId
-              - paragraph [ref=f34e490]: 浠讳竴涓嶆弧瓒冲嵆鎷掔粷鎻愪氦路鎻愮ず甯︽寚鍗楀悕涓庡疄闄呯姸鎬佹垨璧锋鏃堕棿
-              - paragraph [ref=f34e497]: 涓撳璇勫鏄庣粏 research_topic_review路涓�浣嶄笓瀹朵竴琛�
-              - paragraph [ref=f34e504]: 鎸夊姞鏉冨垎闄嶅簭閲嶆帓鍚嶆 ranking
-              - paragraph [ref=f34e511]: 鐢虫姤鍗曡瘎瀹℃眹鎬宦峰垹闄ゆ渶鍚庝竴鏉℃槑缁嗘椂娓呯┖姹囨��
-              - paragraph [ref=f34e518]: 涓夎妭鐐归『搴徛峰鎵规柟娉曚笌瀹℃壒浜哄湪娴佺▼瀹氫箟鍐呴厤缃�
-              - paragraph [ref=f34e525]: Activity_secretariat_review 绉樹功澶勫舰寮忓鏌�
-              - paragraph [ref=f34e532]: Activity_expert_review 涓撳璇勫
-              - paragraph [ref=f34e539]: Activity_council_approve 瀛︿細绔嬮」瀹℃壒
-              - paragraph [ref=f34e546]: 宸茬珛椤� 20路鐩戝惉鍣ㄧ敓鎴� 绔嬮」缂栧彿 LX + yyyyMMdd + 6浣嶆祦姘� 涓� approveTime路宸叉湁缂栧彿鍒欏箓绛夎烦杩�
-              - paragraph [ref=f34e553]: 宸查┏鍥� 35
-              - paragraph [ref=f34e560]: system_notice 鍏憡路鏍囬銆庡叧浜庡叕甯冪珛椤硅棰樼殑閫氱煡銆徛锋鏂囧惈璇鹃鍚� 绔嬮」缂栧彿 绫诲埆 鐢虫姤浜� 鍗曚綅 璧勫姪閲戦路鍥炲啓 noticeId路閲嶅鍙戝竷琚嫤鎴�
-              - paragraph [ref=f34e567]: 濉壒娆″悕绉� 鎷ㄤ粯閲戦 鎷ㄤ粯鏃ユ湡 鏀舵鍗曚綅 鎷ㄤ粯鐘舵�� 鍑瘉闄勪欢
-              - paragraph [ref=f34e574]: 寰呮嫧浠� 0
-              - paragraph [ref=f34e581]: 宸叉嫧浠� 20
-              - paragraph [ref=f34e588]: 闄勪欢 recordType = research_guideline
-              - paragraph [ref=f34e595]: 闄勪欢 recordType = research_application
-              - paragraph [ref=f34e602]: 闄勪欢 recordType = research_fund_record
-              - paragraph [ref=f34e609]: "瀹℃壒涓績鐪嬪崟: 娴佺▼瀹氫箟琛ㄥ崟绫诲瀷=鑷畾涔夎〃鍗暵峰垱寤鸿矾寰� /research/application/index 路 鏌ョ湅璺緞 /research/application/detail/approve/index.vue路鎸� businessKey 鍥炴樉鐢虫姤鍗�"
-              - paragraph [ref=f34e616]: "瀹℃壒浜哄彇鍊�: 涓夎妭鐐瑰湪娴佺▼瀹氫箟鍐呴厤濂藉鎵规柟娉�(渚濇瀹℃壒)涓庡鎵逛汉绛栫暐路閮ㄧ讲鍚庣敱绠$悊鍛樺湪 BPM 娴佺▼璁捐鍣ㄦ寜瀛︿細瀹為檯缁勭粐璋冩暣路鎻愪氦鏃跺墠绔彧璁╅�夋祦绋嬩笉閫夊鎵逛汉"
-              - paragraph [ref=f34e623]: "涓�鏈熻竟鐣�: 缁忚垂鎷ㄤ粯鍙湪鏈ā鍧楄惤鎷ㄦ璁板綍路涓庤储鍔℃ā鍧楁棤鏁版嵁鍚屾(涓嶅仛璺ㄦā鍧楀鎺�)"
-              - paragraph [ref=f34e630]: "浜屾湡鑼冨洿(鏈浘鏈鐩�): 缁撻」楠屾敹 缁撻」璇佷功 鎴愭灉褰掓。鐭ヨ瘑搴� 閲囩撼鎯呭喌涓庣ぞ浼氭晥鐩婃爣娉�"
-              - paragraph [ref=f34e637]: 鐙珛妯″潡 yudao-module-research路瀵� ums_member 涓鸿蒋鍏宠仈(鏃犲閿�)路浠呭鐢� BPM 瀹℃壒銆乻ystem 鍏憡涓庨檮浠朵腑蹇�
-      - heading "姝ラ涓庤鑹�" [level=3] [ref=f34e646]
-      - table [ref=f34e647]:
-        - rowgroup [ref=f34e648]:
-          - row [ref=f34e649]:
-            - columnheader "闃舵" [ref=f34e650]
-            - columnheader "鍔ㄤ綔锛堝惈鐪熷疄鐘舵�佹祦杞級" [ref=f34e651]
-            - columnheader "瑙掕壊" [ref=f34e652]
-          - row [ref=f34e653]:
-            - cell "鍙戝竷鎸囧崡" [ref=f34e654]
-            - 'cell "鏂板缓鎸囧崡閫夌被鍒� 1閲嶇偣璇鹃 / 2涓�鑸棰橈紝濉敵鎶ヨ捣姝㈡椂闂翠笌鎸囧崡姝f枃锛涚紪鍙� GL+yyyyMMdd+6浣嶆祦姘达紙Redis 姣忔棩閲嶇疆锛夛紱status: 0鑽夌/20宸插彂甯�/30宸叉埅姝€�備粎鑽夌鍙敼鍙垹锛屽彂甯冨悗鍗冲喕缁擄紱宸蹭骇鐢熺敵鎶ュ崟鐨勬寚鍗楃姝㈠垹闄わ紙鎻愮ず璇ユ寚鍗椾笅宸叉湁 N 寮犵敵鎶ュ崟锛�" [ref=f34e655]'
-            - cell "瀛︿細绉樹功澶勩�佽棰樼鐞嗗憳" [ref=f34e656]
-          - row [ref=f34e657]:
-            - cell "鍦ㄧ嚎鐢虫姤" [ref=f34e658]
-            - 'cell "浠庡凡鍙戝竷涓斿湪鐢虫姤鏈熷唴鐨勬寚鍗楁柊寤虹敵鎶ヤ功锛涢�変細鍛橈紙ums_member锛夎嚜鍔ㄥ甫鍑哄鍚�/宸ヤ綔鍗曚綅/鑱岀О/鑱旂郴鐢佃瘽/閭锛屽彲鏀癸紱濉棰樺悕绉般�佽棰樼粍鎴愬憳銆佺敵璇风粡璐广�佺敵鎶ヤ功姝f枃锛涚紪鍙� SB+yyyyMMdd+6浣嶆祦姘达紱绫诲埆鍐椾綑鑷寚鍗楋紝涓嶄俊浠诲墠绔紶鍊硷紱status: 0鑽夌" [ref=f34e659]'
-            - cell "璇鹃鐢虫姤浜猴紙浼氬憳锛夈�佽棰樼鐞嗗憳" [ref=f34e660]
-          - row [ref=f34e661]:
-            - cell "鎻愪氦瀹℃壒" [ref=f34e662]
-            - cell "submit 浼� id 涓� processDefinitionKey锛坬uery 鍙傛暟锛夆啋 status=15 瀹℃壒涓苟鍥炲啓 processInstanceId銆傛彁浜ゅ墠閫愭潯鏍¢獙锛氣憼 鎸囧崡蹇呴』宸插彂甯� 鈶� 褰撳墠鏃堕棿蹇呴』鍦ㄧ敵鎶ヨ捣姝㈡湡鍐呪�斺�斾换涓�涓嶆弧瓒冲嵆鎷掔粷锛屾彁绀哄甫鎸囧崡鍚嶄笌瀹為檯鐘舵�佹垨璧锋鏃堕棿锛涗粎鑽夌涓庡凡椹冲洖鍙敼鍙噸鎻愶紝浠呰崏绋垮彲鍒狅紙杩炲甫娓呴檮浠讹級" [ref=f34e663]
-            - cell "璇鹃鐢虫姤浜�" [ref=f34e664]
-          - row [ref=f34e665]:
-            - cell "涓撳璇勫" [ref=f34e666]
-            - cell "鐢虫姤鍗曞浜庡鎵逛腑(15)鏃跺綍鍏ヤ笓瀹惰瘎鍒嗘槑缁嗭紙涓�浣嶄笓瀹朵竴琛岋細濮撳悕/鍗曚綅/鑱岀О + 閫夐浠峰��0.3/鐮旂┒鏂规0.3/鍥㈤槦鍩虹0.2/棰勬湡鎴愭灉0.2 鍥涢」鍒嗗�� + 缁撹 + 鎰忚锛夛紱鍚庣姣忔澧炲垹鏀瑰悗閲嶇畻 compositeScore 鍔犳潈鍚堣銆佹寜鍔犳潈鍒嗛檷搴忛噸鎺� ranking锛屽苟鍥炲啓涓诲崟 reviewAvgScore锛堝叏閮ㄤ笓瀹跺潎鍒嗭級涓� reviewPass锛堝潎鍒嗚揪鏍囦笖澶氭暟缁撹涓烘帹鑽愮珛椤癸級锛涙槑缁嗗叏閮ㄥ垹闄ゆ椂姹囨�诲�兼竻绌轰负鏈眹鎬伙紱鍥涢」鍒嗗�煎潎鍦� 0~100 涔嬪鏃舵彁绀烘寚鍑哄叿浣撶淮搴︿笌瀹為檯鍊�" [ref=f34e667]
-            - cell "璇勫涓撳銆佸浼氱涔﹀銆佽棰樼鐞嗗憳" [ref=f34e668]
-          - row [ref=f34e669]:
-            - cell "绔嬮」瀹℃壒" [ref=f34e670]
-            - cell "BPM 娴佺▼ research_topic_approve 椤哄簭璧颁笁鑺傜偣锛氱涔﹀褰㈠紡瀹℃煡 鈫� 涓撳璇勫 鈫� 瀛︿細绔嬮」瀹℃壒銆傚鎵规柟娉曚笌瀹℃壒浜哄湪娴佺▼瀹氫箟鍐呴厤缃紝閮ㄧ讲鍚庣敱绠$悊鍛樺湪 BPM 娴佺▼璁捐鍣ㄦ寜瀛︿細瀹為檯缁勭粐璋冩暣锛涘鎵逛腑蹇冨唴鐨勮嚜瀹氫箟琛ㄥ崟鍙鍥炴樉鐢虫姤鍗� + 璇勫鏄庣粏 + 鎷ㄤ粯璁板綍銆傞�氳繃鈫�20 宸茬珛椤广�侀┏鍥炩啋35 宸查┏鍥炪�佸彇娑堚啋閫�鍥� 0 鑽夌" [ref=f34e671]
-            - cell "瀛︿細绉樹功澶勶紙褰㈠紡瀹℃煡锛夈�佽瘎瀹′笓瀹讹紙璇勫鑺傜偣锛夈�佸浼氱悊浜嬩細锛堢珛椤瑰鎵癸級" [ref=f34e672]
-          - row [ref=f34e673]:
-            - cell "绔嬮」涓庨�氱煡" [ref=f34e674]
-            - cell "鐩戝惉鍣� ResearchApplicationStatusListener 鍏堝洖鍐欏鎵圭姸鎬侊紝瀹℃壒閫氳繃鍚庡啀璋� approveApplication锛氱敓鎴愮珛椤圭紪鍙� LX+yyyyMMdd+6浣嶆祦姘� 涓� approveTime锛堝凡鏈夌紪鍙峰垯骞傜瓑璺宠繃锛夈�傜偣銆屽彂甯冪珛椤归�氱煡銆嶇敓鎴� system_notice 鍏憡锛堟爣棰樸�庡叧浜庡叕甯冪珛椤硅棰樼殑閫氱煡銆忥紝姝f枃鍚棰樺悕/绔嬮」缂栧彿/绫诲埆/鐢虫姤浜�/鍗曚綅/璧勫姪閲戦锛夛紝鍐欏洖 noticeId 闃查噸澶嶁�斺�旈噸澶嶇偣鍑绘寜宸插彂甯冩嫤鎴苟鍛婄煡涓婃鍙戝竷鏃堕棿" [ref=f34e675]
-            - cell "璇鹃绠$悊鍛樸�佸浼氱涔﹀" [ref=f34e676]
-          - row [ref=f34e677]:
-            - cell "缁忚垂鎷ㄤ粯" [ref=f34e678]
-            - 'cell "浠呫�屽凡绔嬮」(20)銆嶇殑鐢虫姤鍗曞彲鐧昏鎷ㄦ璁板綍锛堟壒娆″悕绉�/鎷ㄤ粯閲戦/鎷ㄤ粯鏃ユ湡/鏀舵鍗曚綅/鐘舵��/澶囨敞/鍑瘉闄勪欢锛夛紱閲戦鏍¢獙锛氬凡鐧昏閲戦 + 鏈閲戦 涓嶅緱瓒呭嚭璇ュ崟鐢宠缁忚垂锛岃秴鍑烘椂鎻愮ず鎶ュ嚭鏈銆佸凡鐧昏銆佸悎璁°�侀绠楀洓椤规暟鍊硷紱status: 0寰呮嫧浠�/20宸叉嫧浠橈紝鍑瘉鏄庣粏璧� system 闄勪欢涓績 recordType=research_fund_record锛岀姸鎬佹敼涓哄凡鎷ㄤ粯鏃跺嚟璇佸繀濉紱浠呭緟鎷ㄤ粯鐘舵�佸彲鏀瑰彲鍒狅紝缂栬緫鏃朵細鎶婃湰琛岄噾棰濆姞鍥炰綑棰濇樉绀�" [ref=f34e679]'
-            - cell "瀛︿細璐㈠姟銆佽棰樼鐞嗗憳" [ref=f34e680]
-  - contentinfo [ref=f34e48]: 鍩轰簬 dev_business 鍒嗘敮 87bf4a99 婧愮爜锛�6 缁勫苟琛屾簮鐮佽蛋璇伙紙MES 鐢熶骇璐ㄩ噺 / MES 浠撳偍 / ERP / CRM+SRM / 鍞悗+HRM / AI+BI+MDM+BPM+搴曞骇锛夛紱2026-09-28 澧為噺琛ュ厖 yudao-module-finance 鐙珛璐㈠姟鎶ラ攢妯″潡锛堟姤閿�鍗� + 鏄庣粏 + 鍙戠エ闄勪欢 + BPM 瀹℃壒 + 褰掓。锛変笌 yudao-module-ums 鐙珛浼氬憳绠$悊妯″潡锛堝叆浼氱敵璇� + BPM 涓ょ骇瀹℃壒 + 瀹℃壒閫氳繃鑷姩寤轰細鍛樹笌鐧诲綍璐﹀彿 + 浼氬憳鍒楄〃/閫氳褰曪級锛�2026-09-28 澧為噺锛欱PM 娴佺▼瀹氫箟 ums_membership_approve锛堝叆浼氬鎵癸級宸插湪 mom-jssm 閮ㄧ讲骞跺苟鍏� config_export_all 闃舵涓�锛堝垎绫� ums_membership_approve锛岀涔﹀鍒濆 鈫� 鐞嗕簨浼氬璁紝涓よ妭鐐瑰鎵逛汉鍧囦负銆岃鑹层�嶇瓥鐣ャ�佽鑹�=瓒呯骇绠$悊鍛�(1) 鍏滃簳锛岃〃鍗曠被鍨�=鑷畾涔夎〃鍗曪紝鏌ョ湅璺緞 /ums/application/detail/approve/index.vue锛夛紱鍚屾椂淇 mom-jssm 鐢� CREATE TABLE ... LIKE 鍏嬮殕瀵艰嚧鐨� 47 涓寚鍚� ruoyi-vue-pro 鐨� act_*/flw_* 璺ㄥ簱澶栭敭锛堥噸寤轰负鎸囧悜 mom-jssm 鑷韩锛屽鍎胯棰勬涓� 0锛夛紱2026-09-28 澧為噺锛氭柊澧� yudao-module-research 鐙珛绉戠爺璇鹃妯″潡涓�鏈燂紙璇鹃鎸囧崡鍙戝竷 + 鍦ㄧ嚎鐢虫姤 + 涓撳璇勫鏄庣粏 + BPM 涓夌骇绔嬮」瀹℃壒 + 绔嬮」閫氱煡鍏憡 + 缁忚垂鎷ㄤ粯璁板綍锛�4 寮� research_* 琛� / 5 涓瓧鍏� / bpm_category id=8002 / 娴佺▼ research_topic_approve / 鑿滃崟 1075510~1075553锛夛紝骞跺苟鍏� config_export_all_20260928 闃舵涓�锛涚敵鎶ヤ汉鍙� ums_member锛堣蒋鍏宠仈锛夈�佺珛椤归�氱煡澶嶇敤 system_notice 鍏憡銆佹嫧娆惧嚟璇佽蛋 system 闄勪欢涓績 recordType=research_fund_record 路 鐢熸垚浜� 2026-09-28 路 缁存姢璇疯蛋鎶�鑳� project-business-analysis
\ No newline at end of file
diff --git a/src/api/archive/borrow/index.ts b/src/api/archive/borrow/index.ts
new file mode 100644
index 0000000..efc2036
--- /dev/null
+++ b/src/api/archive/borrow/index.ts
@@ -0,0 +1,106 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace ArchiveBorrowApi {
+  /** 鍊熼槄鍗� */
+  export interface Borrow {
+    id?: number; // 鍊熼槄鍗曠紪鍙�
+    no?: string; // 鍊熼槄鍗曞彿锛堝悗绔敓鎴愶細JY+yyyyMMdd+6浣嶆祦姘达紝鍙锛�
+    documentId: number; // 妗f ID
+    documentNo?: string; // 妗f缂栧彿锛堝揩鐓э級
+    documentTitle?: string; // 妗f鏍囬锛堝揩鐓э級
+    borrowerUserId?: number; // 鍊熼槄浜虹敤鎴� ID
+    borrowerName: string; // 鍊熼槄浜哄鍚�
+    borrowerDept?: string; // 鍊熼槄浜洪儴闂�
+    borrowerPhone?: string; // 鍊熼槄浜鸿仈绯荤數璇�
+    borrowDate: string; // 鍊熼槄鏃ユ湡
+    borrowPurpose?: string; // 鍊熼槄鐩殑
+    returnDeadline: string; // 褰掕繕鏈熼檺
+    status?: number; // 鐘舵�侊紝瀛楀吀 archive_borrow_status
+    processInstanceId?: string; // BPM 娴佺▼瀹炰緥 ID
+    approveTime?: string; // 瀹℃壒閫氳繃锛堝疄闄呭�熷嚭锛夋椂闂�
+    returnTime?: string; // 瀹為檯褰掕繕鏃堕棿
+    returnConfirmUserId?: number; // 褰掕繕纭浜虹敤鎴� ID
+    returnConfirmUserName?: string; // 褰掕繕纭浜哄鍚�
+    returnRemark?: string; // 褰掕繕澶囨敞
+    rejectReason?: string; // 椹冲洖鍘熷洜
+    rejectTime?: string; // 椹冲洖鏃堕棿
+    remark?: string; // 澶囨敞
+    overdue?: boolean; // 鏄惁閫炬湡锛堝悗绔疄鏃惰绠楋級
+    overdueDays?: number; // 閫炬湡澶╂暟锛堝悗绔疄鏃惰绠楋級
+    createTime?: string; // 鍒涘缓鏃堕棿
+  }
+
+  /** 瀹℃壒娴佺▼瀹氫箟 */
+  export interface ProcessDefinition {
+    id: string;
+    key: string;
+    name: string;
+  }
+
+  /** 褰掕繕鐧昏璇锋眰 */
+  export interface ReturnReq {
+    id: number; // 鍊熼槄鍗曠紪鍙�
+    returnRemark?: string; // 褰掕繕澶囨敞
+  }
+}
+
+/** 鏌ヨ鍊熼槄鍗曞垎椤� */
+export function getBorrowPage(params: PageParam) {
+  return requestClient.get<PageResult<ArchiveBorrowApi.Borrow>>(
+    '/archive/borrow/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ閫炬湡鏈繕鍊熼槄鍗曞垎椤� */
+export function getOverdueBorrowPage(params: PageParam) {
+  return requestClient.get<PageResult<ArchiveBorrowApi.Borrow>>(
+    '/archive/borrow/overdue-page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鍊熼槄鍗曡鎯� */
+export function getBorrow(id: number) {
+  return requestClient.get<ArchiveBorrowApi.Borrow>(
+    `/archive/borrow/get?id=${id}`,
+  );
+}
+
+/** 鏂板鍊熼槄鍗� */
+export function createBorrow(data: ArchiveBorrowApi.Borrow) {
+  return requestClient.post('/archive/borrow/create', data);
+}
+
+/** 淇敼鍊熼槄鍗� */
+export function updateBorrow(data: ArchiveBorrowApi.Borrow) {
+  return requestClient.put('/archive/borrow/update', data);
+}
+
+/** 鍒犻櫎鍊熼槄鍗� */
+export function deleteBorrow(id: number) {
+  return requestClient.delete('/archive/borrow/delete', {
+    params: { id },
+  });
+}
+
+/** 鎻愪氦鍊熼槄鍗曞鎵� */
+export function submitBorrow(id: number, processDefinitionKey?: string) {
+  return requestClient.post('/archive/borrow/submit', null, {
+    params: { id, processDefinitionKey },
+  });
+}
+
+/** 鑾峰彇妗f鍊熼槄瀹℃壒娴佺▼鍒楄〃 */
+export function getBorrowApproveProcessList() {
+  return requestClient.get<ArchiveBorrowApi.ProcessDefinition[]>(
+    '/archive/borrow/approve-process-list',
+  );
+}
+
+/** 褰掕繕鐧昏 */
+export function returnBorrow(data: ArchiveBorrowApi.ReturnReq) {
+  return requestClient.post('/archive/borrow/return', data);
+}
diff --git a/src/api/archive/category/index.ts b/src/api/archive/category/index.ts
new file mode 100644
index 0000000..e814ca5
--- /dev/null
+++ b/src/api/archive/category/index.ts
@@ -0,0 +1,57 @@
+import { requestClient } from '#/api/request';
+
+export namespace ArchiveCategoryApi {
+  /** 妗f鍒嗙被锛堜功鏋�/鍒嗙被鏍戯級 */
+  export interface Category {
+    id?: number; // 鍒嗙被缂栧彿
+    parentId?: number; // 鐖跺垎绫� ID锛�0=椤剁骇
+    name?: string; // 鍒嗙被鍚嶇О
+    code?: string; // 鍒嗙被缂栫爜锛屽敮涓�锛坢ember/research/meeting/finance/document/party锛�
+    sort?: number; // 鏄剧ず鎺掑簭
+    description?: string; // 鍒嗙被璇存槑
+    defaultSecretLevel?: number; // 璇ュ垎绫讳笅妗f榛樿瀵嗙骇锛屽瓧鍏� archive_secret_level
+    status?: number; // 鐘舵�侊紝瀛楀吀 archive_category_status
+    remark?: string; // 澶囨敞
+    createTime?: string; // 鍒涘缓鏃堕棿
+    children?: Category[]; // 瀛愬垎绫伙紙鏍戝舰杩斿洖锛�
+  }
+}
+
+/** 鏌ヨ妗f鍒嗙被鍒楄〃锛堝钩閾猴級 */
+export function getCategoryList(params?: ArchiveCategoryApi.Category) {
+  return requestClient.get<ArchiveCategoryApi.Category[]>(
+    '/archive/category/list',
+    { params },
+  );
+}
+
+/** 鏌ヨ妗f鍒嗙被鏍� */
+export function getCategoryTree() {
+  return requestClient.get<ArchiveCategoryApi.Category[]>(
+    '/archive/category/tree',
+  );
+}
+
+/** 鏌ヨ妗f鍒嗙被璇︽儏 */
+export function getCategory(id: number) {
+  return requestClient.get<ArchiveCategoryApi.Category>(
+    `/archive/category/get?id=${id}`,
+  );
+}
+
+/** 鏂板妗f鍒嗙被 */
+export function createCategory(data: ArchiveCategoryApi.Category) {
+  return requestClient.post('/archive/category/create', data);
+}
+
+/** 淇敼妗f鍒嗙被 */
+export function updateCategory(data: ArchiveCategoryApi.Category) {
+  return requestClient.put('/archive/category/update', data);
+}
+
+/** 鍒犻櫎妗f鍒嗙被 */
+export function deleteCategory(id: number) {
+  return requestClient.delete('/archive/category/delete', {
+    params: { id },
+  });
+}
diff --git a/src/api/archive/document/index.ts b/src/api/archive/document/index.ts
new file mode 100644
index 0000000..dd55e7f
--- /dev/null
+++ b/src/api/archive/document/index.ts
@@ -0,0 +1,75 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import type { SystemStorageApi } from '#/api/system/storage';
+
+import { requestClient } from '#/api/request';
+
+export namespace ArchiveDocumentApi {
+  /** 妗f锛堢數瀛愰檮浠� + 瀹炰綋瀛樻斁浣嶇疆锛� */
+  export interface Document {
+    id?: number; // 妗f缂栧彿
+    no?: string; // 妗f缂栧彿锛堝悗绔敓鎴愶細DA+yyyyMMdd+6浣嶆祦姘达紝鍙锛�
+    title: string; // 妗f鏍囬
+    categoryId: number; // 妗f鍒嗙被 ID
+    categoryName?: string; // 鍒嗙被鍚嶇О锛堝悗绔啑浣欙級
+    secretLevel?: number; // 瀵嗙骇锛屽瓧鍏� archive_secret_level
+    carrierType?: number; // 杞戒綋锛屽瓧鍏� archive_carrier_type
+    shelfNo?: string; // 瀹炰綋-涔︽灦鍙�
+    layerNo?: string; // 瀹炰綋-灞傚彿
+    columnNo?: string; // 瀹炰綋-鍒楀彿
+    boxNo?: string; // 瀹炰綋-鍐屽彿/妗f鐩掑彿
+    storePlace?: string; // 瀹炰綋瀛樻斁浣嶇疆鎻忚堪
+    sourceType?: number; // 鏉ユ簮锛屽瓧鍏� archive_source_type锛堝悗绔啓锛屽墠绔彧璇伙級
+    sourceNo?: string; // 鏉ユ簮鍗曞彿锛堢珛椤圭紪鍙�/浼氬憳缂栧彿/鎶ラ攢鍗曞彿锛�
+    sourceId?: number; // 鏉ユ簮鍗曟嵁 ID
+    sourceTitle?: string; // 鏉ユ簮鍗曟嵁鏍囬
+    archiveDate?: string; // 褰掓。鏃ユ湡
+    archivistUserId?: number; // 褰掓。浜虹敤鎴� ID
+    archivistName?: string; // 褰掓。浜哄鍚�
+    status?: number; // 妗f鐘舵�侊紝瀛楀吀 archive_document_status
+    summary?: string; // 鎽樿
+    tags?: string; // 鍏抽敭璇嶏紝鑻辨枃閫楀彿鍒嗛殧
+    remark?: string; // 澶囨敞
+    createTime?: string; // 鍒涘缓鏃堕棿
+    attachmentList?: SystemStorageApi.StorageBlob[]; // 闄勪欢鍒楄〃
+    hasAttachment?: boolean; // 鏄惁鏈夐檮浠�
+    blobIds?: number[]; // 鎻愪氦鏃剁殑闄勪欢 ID 鍒楄〃锛堝叏閲忥級
+  }
+}
+
+/** 鏌ヨ妗f鍒嗛〉 */
+export function getDocumentPage(params: PageParam) {
+  return requestClient.get<PageResult<ArchiveDocumentApi.Document>>(
+    '/archive/document/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ妗f璇︽儏 */
+export function getDocument(id: number) {
+  return requestClient.get<ArchiveDocumentApi.Document>(
+    `/archive/document/get?id=${id}`,
+  );
+}
+
+/** 鏂板妗f */
+export function createDocument(data: ArchiveDocumentApi.Document) {
+  return requestClient.post('/archive/document/create', data);
+}
+
+/** 淇敼妗f */
+export function updateDocument(data: ArchiveDocumentApi.Document) {
+  return requestClient.put('/archive/document/update', data);
+}
+
+/** 鍒犻櫎妗f */
+export function deleteDocument(id: number) {
+  return requestClient.delete('/archive/document/delete', {
+    params: { id },
+  });
+}
+
+/** 瀵煎嚭妗f Excel */
+export function exportDocument(params: ArchiveDocumentApi.Document) {
+  return requestClient.download('/archive/document/export-excel', { params });
+}
diff --git a/src/api/archive/stat/index.ts b/src/api/archive/stat/index.ts
new file mode 100644
index 0000000..98a7302
--- /dev/null
+++ b/src/api/archive/stat/index.ts
@@ -0,0 +1,36 @@
+import { requestClient } from '#/api/request';
+
+export namespace ArchiveStatApi {
+  /** 鍒嗙被缁村害缁熻椤� */
+  export interface StatCategory {
+    categoryId?: number; // 鍒嗙被 ID
+    name?: string; // 鍒嗙被鍚嶇О
+    total?: number; // 妗f鏁伴噺
+  }
+
+  /** 瀛楀吀鍊肩淮搴︾粺璁¢」 */
+  export interface StatValue {
+    value?: number; // 瀛楀吀鍊�
+    name?: string; // 瀛楀吀鏍囩锛堝悗绔浆涔夛級
+    total?: number; // 妗f鏁伴噺
+  }
+
+  /** 妗f缁熻姒傝 */
+  export interface StatOverview {
+    totalCount?: number; // 妗f鎬绘暟
+    inStockCount?: number; // 鍦ㄥ簱妗f鏁�
+    borrowedCount?: number; // 鍊熷嚭涓。妗堟暟
+    borrowingCount?: number; // 鍊熷嚭涓�熼槄鍗曟暟
+    overdueCount?: number; // 閫炬湡鏈繕鍊熼槄鍗曟暟
+    byCategory?: StatCategory[]; // 鎸夊垎绫诲垎甯�
+    bySecretLevel?: StatValue[]; // 鎸夊瘑绾у垎甯�
+    byCarrierType?: StatValue[]; // 鎸夎浇浣撳垎甯�
+  }
+}
+
+/** 鏌ヨ妗f缁熻姒傝 */
+export function getArchiveStatOverview() {
+  return requestClient.get<ArchiveStatApi.StatOverview>(
+    '/archive/stat/overview',
+  );
+}
diff --git a/src/api/finance/reimbursement/index.ts b/src/api/finance/reimbursement/index.ts
index d26a375..65754fd 100644
--- a/src/api/finance/reimbursement/index.ts
+++ b/src/api/finance/reimbursement/index.ts
@@ -29,6 +29,8 @@
     applyDate: string; // 鐢宠鏃ユ湡 yyyy-MM-dd
     processInstanceId?: string; // BPM 娴佺▼瀹炰緥 ID
     archiveTime?: string; // 褰掓。鏃堕棿
+    archiveDocumentId?: number; // 褰掓。鐢熸垚鐨勬。妗� ID锛堥潪绌哄嵆宸茬敓鎴愭。妗堬級
+    archived?: boolean; // 鏄惁宸茬敓鎴愭。妗�
     remark?: string; // 澶囨敞
     creator?: string; // 鍒涘缓浜� ID
     creatorName?: string; // 鍒涘缓浜哄鍚�
@@ -91,9 +93,9 @@
   });
 }
 
-/** 褰掓。鎶ラ攢鍗曪紙浠呭凡閫氳繃鍙綊妗o級 */
+/** 褰掓。鎶ラ攢鍗曪紙浠呭凡閫氳繃鍙綊妗o級锛岃繑鍥炵敓鎴愮殑妗f缂栧彿 */
 export function archiveReimbursement(id: number) {
-  return requestClient.post('/finance/reimbursement/archive', null, {
+  return requestClient.post<string>('/finance/reimbursement/archive', null, {
     params: { id },
   });
 }
diff --git a/src/api/research/application/index.ts b/src/api/research/application/index.ts
index f96ab84..3e91c52 100644
--- a/src/api/research/application/index.ts
+++ b/src/api/research/application/index.ts
@@ -30,6 +30,8 @@
     researchNo?: string; // 绔嬮」缂栧彿锛屽鎵归�氳繃鍚庣敓鎴�
     approveTime?: string; // 绔嬮」鏃堕棿
     noticeId?: number; // 宸插彂甯冪殑绔嬮」閫氱煡鍏憡 ID
+    archiveDocumentId?: number; // 褰掓。鐢熸垚鐨勬。妗� ID锛堥潪绌哄嵆宸插綊妗o級
+    archived?: boolean; // 鏄惁宸插綊妗�
     remark?: string; // 澶囨敞
     creator?: string; // 鍒涘缓浜� ID
     creatorName?: string; // 鍒涘缓浜哄鍚�
@@ -120,6 +122,13 @@
   });
 }
 
+/** 褰掓。鍒版。妗堢鐞嗭紙浠呭凡绔嬮」鍙綊妗o級锛岃繑鍥炵敓鎴愮殑妗f缂栧彿 */
+export function archiveApplication(id: number) {
+  return requestClient.post<string>('/research/application/archive', null, {
+    params: { id },
+  });
+}
+
 /** 鏌ヨ鏌愮敵鎶ヤ功鐨勪笓瀹惰瘎瀹℃槑缁嗗垪琛� */
 export function getTopicReviewList(applicationId: number) {
   return requestClient.get<ResearchApplicationApi.TopicReview[]>(
diff --git a/src/api/ums/member/index.ts b/src/api/ums/member/index.ts
index 685cb4f..eaa2d86 100644
--- a/src/api/ums/member/index.ts
+++ b/src/api/ums/member/index.ts
@@ -29,6 +29,8 @@
     joinDate?: string; // 鍏ヤ細鏃ユ湡
     expireDate?: string; // 鏈夋晥鏈熻嚦
     status?: number; // 鐘舵�侊紝瀛楀吀 ums_member_status
+    archiveDocumentId?: number; // 褰掓。鐢熸垚鐨勬。妗� ID锛堥潪绌哄嵆宸插綊妗o級
+    archived?: boolean; // 鏄惁宸插綊妗�
     remark?: string; // 澶囨敞
     createTime?: string; // 鍒涘缓鏃堕棿
     attachmentList?: SystemStorageApi.StorageBlob[]; // 闄勪欢鍒楄〃
@@ -71,4 +73,11 @@
   return requestClient.delete('/ums/member/delete', {
     params: { id },
   });
+}
+
+/** 褰掓。鍒版。妗堢鐞嗭紙浠呮甯哥姸鎬佷細鍛樺彲褰掓。锛夛紝杩斿洖鐢熸垚鐨勬。妗堢紪鍙� */
+export function archiveMember(id: number) {
+  return requestClient.post<string>('/ums/member/archive', null, {
+    params: { id },
+  });
 }
\ No newline at end of file
diff --git a/src/packages/constants/src/dict-enum.ts b/src/packages/constants/src/dict-enum.ts
index 08aed55..9e5b3c8 100644
--- a/src/packages/constants/src/dict-enum.ts
+++ b/src/packages/constants/src/dict-enum.ts
@@ -169,6 +169,16 @@
   RESEARCH_FUND_STATUS: 'research_fund_status', // 缁忚垂鎷ㄤ粯鐘舵��
 } as const;
 
+/** ========== ARCHIVE - 妗f绠$悊妯″潡 ========== */
+const ARCHIVE_DICT = {
+  ARCHIVE_CATEGORY_STATUS: 'archive_category_status', // 妗f鍒嗙被鍚敤/鍋滅敤
+  ARCHIVE_DOCUMENT_STATUS: 'archive_document_status', // 妗f鐘舵�侊紙鍦ㄥ簱/鍊熷嚭涓級
+  ARCHIVE_SECRET_LEVEL: 'archive_secret_level', // 妗f瀵嗙骇锛堝叕寮�/鍐呴儴/淇濆瘑锛�
+  ARCHIVE_CARRIER_TYPE: 'archive_carrier_type', // 妗f杞戒綋锛堢數瀛�/瀹炰綋/鐢靛瓙+瀹炰綋锛�
+  ARCHIVE_SOURCE_TYPE: 'archive_source_type', // 妗f鏉ユ簮锛堟墜宸�/璇鹃/浼氬憳/璐㈠姟锛�
+  ARCHIVE_BORROW_STATUS: 'archive_borrow_status', // 鍊熼槄鍗曠姸鎬�
+} as const;
+
 /** ========== AI - 浜哄伐鏅鸿兘妯″潡 ========== */
 const AI_DICT = {
   AI_GENERATE_MODE: 'ai_generate_mode', // AI 鐢熸垚妯″紡
@@ -373,6 +383,7 @@
 const DICT_TYPE = {
   ...AFTERSALES_DICT,
   ...AI_DICT,
+  ...ARCHIVE_DICT,
   ...BPM_DICT,
   ...CRM_DICT,
   ...ERP_DICT,
diff --git a/src/views/archive/borrow/data.ts b/src/views/archive/borrow/data.ts
new file mode 100644
index 0000000..c251154
--- /dev/null
+++ b/src/views/archive/borrow/data.ts
@@ -0,0 +1,307 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ArchiveBorrowApi } from '#/api/archive/borrow';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+
+import { getDocumentPage } from '#/api/archive/document';
+import { getSimpleUserList } from '#/api/system/user';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'edit';
+
+/** 鍊熼槄鍗曠姸鎬侊紙瀵瑰簲瀛楀吀 archive_borrow_status锛� */
+export const BorrowStatus = {
+  DRAFT: 0, // 鑽夌
+  APPROVING: 15, // 瀹℃壒涓�
+  BORROWED: 20, // 鍊熷嚭涓�
+  RETURNED: 30, // 宸插綊杩�
+  REJECTED: 35, // 宸查┏鍥�
+} as const;
+
+/** 鍊熼槄鐧昏琛ㄥ崟閰嶇疆 */
+export function useFormSchema(
+  formType: FormType,
+  documentId?: number,
+): VbenFormSchema[] {
+  const readonly = formType === 'detail';
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'documentId',
+      label: '鍊熼槄妗f',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        showSearch: true,
+        placeholder: '璇烽�夋嫨鍦ㄥ簱妗f',
+        api: async () => {
+          const { list } = await getDocumentPage({
+            pageNo: 1,
+            pageSize: 100,
+            status: 10,
+          });
+          return list.map((item) => ({
+            id: item.id,
+            label: item.no ? `${item.no} ${item.title}` : item.title,
+          }));
+        },
+        labelField: 'label',
+        valueField: 'id',
+        disabled: readonly || !!documentId,
+      },
+      rules: 'selectRequired',
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'borrowerUserId',
+      label: '鍊熼槄浜�',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        showSearch: true,
+        placeholder: '璇烽�夋嫨鍊熼槄浜�',
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        valueField: 'id',
+        disabled: readonly,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'borrowerName',
+      label: '鍊熼槄浜哄鍚�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ�熼槄浜哄鍚�',
+        disabled: readonly,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'borrowerDept',
+      label: '鍊熼槄浜洪儴闂�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ�熼槄浜洪儴闂�',
+        disabled: readonly,
+      },
+    },
+    {
+      fieldName: 'borrowerPhone',
+      label: '鑱旂郴鐢佃瘽',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヨ仈绯荤數璇�',
+        disabled: readonly,
+      },
+    },
+    {
+      fieldName: 'borrowDate',
+      label: '鍊熼槄鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        allowClear: true,
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍊熼槄鏃ユ湡',
+        disabled: readonly,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'returnDeadline',
+      label: '褰掕繕鏈熼檺',
+      component: 'DatePicker',
+      componentProps: {
+        allowClear: true,
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨褰掕繕鏈熼檺',
+        disabled: readonly,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'borrowPurpose',
+      label: '鍊熼槄鐩殑',
+      component: 'Textarea',
+      componentProps: {
+        rows: 2,
+        placeholder: '璇疯緭鍏ュ�熼槄鐩殑',
+        disabled: readonly,
+      },
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        rows: 2,
+        placeholder: '璇疯緭鍏ュ娉�',
+        disabled: readonly,
+      },
+      formItemClass: 'col-span-2',
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'no',
+      label: '鍊熼槄鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ�熼槄鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'documentNo',
+      label: '妗f缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ。妗堢紪鍙�',
+      },
+    },
+    {
+      fieldName: 'documentTitle',
+      label: '妗f鏍囬',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ。妗堟爣棰�',
+      },
+    },
+    {
+      fieldName: 'borrowerName',
+      label: '鍊熼槄浜�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ�熼槄浜哄鍚�',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_BORROW_STATUS, 'number'),
+        allowClear: true,
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+    },
+    {
+      fieldName: 'borrowDate',
+      label: '鍊熼槄鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        allowClear: true,
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: ['寮�濮嬫棩鏈�', '缁撴潫鏃ユ湡'],
+      },
+    },
+    {
+      fieldName: 'returnDeadline',
+      label: '褰掕繕鏈熼檺',
+      component: 'RangePicker',
+      componentProps: {
+        allowClear: true,
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: ['寮�濮嬫棩鏈�', '缁撴潫鏃ユ湡'],
+      },
+    },
+  ];
+}
+
+/** 琛ㄦ牸鍒楅厤缃� */
+export function useGridColumns(): VxeTableGridOptions<ArchiveBorrowApi.Borrow>['columns'] {
+  return [
+    {
+      field: 'no',
+      title: '鍊熼槄鍗曞彿',
+      minWidth: 170,
+    },
+    {
+      field: 'documentNo',
+      title: '妗f缂栧彿',
+      minWidth: 170,
+    },
+    {
+      field: 'documentTitle',
+      title: '妗f鏍囬',
+      minWidth: 200,
+    },
+    {
+      field: 'borrowerName',
+      title: '鍊熼槄浜�',
+      minWidth: 100,
+    },
+    {
+      field: 'borrowerDept',
+      title: '閮ㄩ棬',
+      minWidth: 120,
+    },
+    {
+      field: 'borrowDate',
+      title: '鍊熼槄鏃ユ湡',
+      minWidth: 120,
+    },
+    {
+      field: 'returnDeadline',
+      title: '褰掕繕鏈熼檺',
+      minWidth: 120,
+    },
+    {
+      field: 'overdue',
+      title: '閫炬湡',
+      minWidth: 110,
+      slots: { default: 'overdue' },
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.ARCHIVE_BORROW_STATUS },
+      },
+    },
+    {
+      field: 'approveTime',
+      title: '鍊熷嚭鏃堕棿',
+      minWidth: 170,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'returnTime',
+      title: '褰掕繕鏃堕棿',
+      minWidth: 170,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 230,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/archive/borrow/detail/approve/index.vue b/src/views/archive/borrow/detail/approve/index.vue
new file mode 100644
index 0000000..65575a6
--- /dev/null
+++ b/src/views/archive/borrow/detail/approve/index.vue
@@ -0,0 +1,125 @@
+<script lang="ts" setup>
+import type { ArchiveBorrowApi } from '#/api/archive/borrow';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, onMounted, ref } from 'vue';
+
+import { ContentWrap } from '@vben/common-ui';
+import { DICT_TYPE } from '@vben/constants';
+import { formatDateTime } from '@vben/utils';
+
+import { Spin, Tag } from 'ant-design-vue';
+
+import { getBorrow } from '#/api/archive/borrow';
+import { DictTag } from '#/components/dict-tag';
+import { useDescription } from '#/components/description';
+
+defineOptions({ name: 'ArchiveBorrowApproveDetail' });
+
+const props = defineProps<{ id: string }>();
+
+const loading = ref(false);
+const formData = ref<ArchiveBorrowApi.Borrow>();
+
+/** 鏂囨湰瀛楁绌哄�煎崰浣� */
+function textRender(val: unknown) {
+  return val === 0 || val ? String(val) : '-';
+}
+
+/** 鏃ユ湡鏃堕棿瀛楁绌哄�煎崰浣� */
+function timeRender(val: unknown) {
+  return val ? (formatDateTime(val as string) as string) : '-';
+}
+
+/** 閫炬湡灞曠ず */
+function overdueRender(_: unknown, record: ArchiveBorrowApi.Borrow) {
+  return record?.overdue
+    ? h(Tag, { color: 'error' }, () => `閫炬湡 ${record.overdueDays} 澶ー)
+    : h(Tag, { color: 'success' }, () => '鏈�炬湡');
+}
+
+const [Descriptions] = useDescription({
+  bordered: true,
+  column: 2,
+  schema: [
+    { label: '鍊熼槄鍗曞彿', field: 'no', render: textRender },
+    {
+      label: '鐘舵��',
+      field: 'status',
+      render: (val) =>
+        h(DictTag, { type: DICT_TYPE.ARCHIVE_BORROW_STATUS, value: val }),
+    },
+    { label: '妗f缂栧彿', field: 'documentNo', render: textRender },
+    { label: '妗f鏍囬', field: 'documentTitle', render: textRender },
+    { label: '鍊熼槄浜�', field: 'borrowerName', render: textRender },
+    { label: '鍊熼槄浜洪儴闂�', field: 'borrowerDept', render: textRender },
+    { label: '鑱旂郴鐢佃瘽', field: 'borrowerPhone', render: textRender },
+    { label: '鍊熼槄鏃ユ湡', field: 'borrowDate', render: textRender },
+    { label: '褰掕繕鏈熼檺', field: 'returnDeadline', render: textRender },
+    {
+      label: '閫炬湡鎯呭喌',
+      field: 'overdue',
+      render: (val, record) => overdueRender(val, record as ArchiveBorrowApi.Borrow),
+    },
+    {
+      label: '鍊熷嚭鏃堕棿',
+      field: 'approveTime',
+      render: timeRender,
+    },
+    {
+      label: '褰掕繕鏃堕棿',
+      field: 'returnTime',
+      render: timeRender,
+    },
+    { label: '褰掕繕纭浜�', field: 'returnConfirmUserName', render: textRender },
+    {
+      label: '鍊熼槄鐩殑',
+      field: 'borrowPurpose',
+      span: 2,
+      render: textRender,
+    },
+    {
+      label: '褰掕繕澶囨敞',
+      field: 'returnRemark',
+      span: 2,
+      render: textRender,
+    },
+    {
+      label: '椹冲洖鍘熷洜',
+      field: 'rejectReason',
+      span: 2,
+      render: textRender,
+    },
+    {
+      label: '椹冲洖鏃堕棿',
+      field: 'rejectTime',
+      render: timeRender,
+    },
+    { label: '鍒涘缓鏃堕棿', field: 'createTime', render: timeRender },
+    { label: '澶囨敞', field: 'remark', span: 2, render: textRender },
+  ] as DescriptionItemSchema[],
+});
+
+onMounted(async () => {
+  try {
+    loading.value = true;
+    formData.value = await getBorrow(Number(props.id));
+  } finally {
+    loading.value = false;
+  }
+});
+</script>
+
+<template>
+  <ContentWrap class="m-2">
+    <Spin :spinning="loading" tip="鍔犺浇涓�...">
+      <div class="rounded-lg border border-border bg-background p-4">
+        <div class="mb-4 flex items-center gap-2">
+          <span class="inline-block h-4 w-1 rounded bg-primary" />
+          <span class="text-base font-semibold">妗f鍊熼槄鍗曚俊鎭�</span>
+        </div>
+        <Descriptions :data="formData" />
+      </div>
+    </Spin>
+  </ContentWrap>
+</template>
diff --git a/src/views/archive/borrow/index.vue b/src/views/archive/borrow/index.vue
new file mode 100644
index 0000000..7db1c1c
--- /dev/null
+++ b/src/views/archive/borrow/index.vue
@@ -0,0 +1,256 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ArchiveBorrowApi } from '#/api/archive/borrow';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteBorrow,
+  getBorrowApproveProcessList,
+  getBorrowPage,
+  getOverdueBorrowPage,
+} from '#/api/archive/borrow';
+import { $t } from '#/locales';
+
+import { BorrowStatus, useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+import ProcessSelectModal from './modules/process-select-modal.vue';
+import ReturnForm from './modules/return-form.vue';
+
+/** 鍊熼槄鐧昏 */
+defineOptions({ name: 'ArchiveBorrow' });
+
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+  connectedComponent: ProcessSelectModal,
+  destroyOnClose: true,
+});
+
+const [ReturnModal, returnModalApi] = useVbenModal({
+  connectedComponent: ReturnForm,
+  destroyOnClose: true,
+});
+
+/** 鏄惁鍙湅閫炬湡鏈繕 */
+const overdueOnly = ref(false);
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒囨崲銆屽彧鐪嬮�炬湡銆� */
+function handleToggleOverdue() {
+  overdueOnly.value = !overdueOnly.value;
+  gridApi.query();
+}
+
+/** 鐧昏鍊熼槄 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 缂栬緫鍊熼槄鍗� */
+function handleEdit(row: ArchiveBorrowApi.Borrow) {
+  formModalApi.setData({ formType: 'edit', id: row.id }).open();
+}
+
+/** 鏌ョ湅鍊熼槄鍗曡鎯� */
+function handleDetail(row: ArchiveBorrowApi.Borrow) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 鍒犻櫎鍊熼槄鍗� */
+async function handleDelete(row: ArchiveBorrowApi.Borrow) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.no]),
+    duration: 0,
+  });
+  try {
+    await deleteBorrow(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.no]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎻愪氦瀹℃壒 - 鍏堣幏鍙栨祦绋嬪垪琛紝娴佺▼ key 涓嶅啓姝� */
+async function handleSubmit(row: ArchiveBorrowApi.Borrow) {
+  const hideLoading = message.loading({
+    content: '鑾峰彇瀹℃壒娴佺▼...',
+    duration: 0,
+  });
+  try {
+    const processList = await getBorrowApproveProcessList();
+    hideLoading();
+    if (!processList || processList.length === 0) {
+      message.warning(
+        '鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堝埌 BPM 娴佺▼璁捐鍣ㄥ彂甯冩。妗堝�熼槄瀹℃壒娴佺▼',
+      );
+      return;
+    }
+    processModalApi.setData({ id: row.id, processList }).open();
+  } catch {
+    hideLoading();
+  }
+}
+
+/** 褰掕繕鐧昏 */
+function handleReturn(row: ArchiveBorrowApi.Borrow) {
+  returnModalApi.setData(row).open();
+}
+
+/** 鏌ョ湅瀹℃壒杩涘害 */
+function handleProcessDetail(row: ArchiveBorrowApi.Borrow) {
+  if (!row.processInstanceId) {
+    message.warning('璇ュ�熼槄鍗曞皻鏈彁浜ゅ鎵�');
+    return;
+  }
+  router.push({
+    name: 'BpmProcessInstanceDetail',
+    query: { id: row.processInstanceId },
+  });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          const params = {
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          };
+          return overdueOnly.value
+            ? await getOverdueBorrowPage(params)
+            : await getBorrowPage(params);
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<ArchiveBorrowApi.Borrow>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <ProcessModal @success="handleRefresh" />
+    <ReturnModal @success="handleRefresh" />
+    <Grid table-title="鍊熼槄鐧昏">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '鐧昏鍊熼槄',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['archive:borrow:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: overdueOnly ? '鏌ョ湅鍏ㄩ儴' : '鍙湅閫炬湡',
+              type: overdueOnly ? 'primary' : 'default',
+              danger: overdueOnly,
+              icon: ACTION_ICON.SEARCH,
+              auth: ['archive:borrow:query'],
+              onClick: handleToggleOverdue,
+            },
+          ]"
+        />
+      </template>
+      <template #overdue="{ row }">
+        <Tag v-if="row.overdue" color="error">閫炬湡 {{ row.overdueDays }} 澶�</Tag>
+        <span v-else>-</span>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.detail'),
+              type: 'link',
+              icon: ACTION_ICON.VIEW,
+              auth: ['archive:borrow:query'],
+              onClick: handleDetail.bind(null, row),
+            },
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['archive:borrow:update'],
+              ifShow: () =>
+                row.status === BorrowStatus.DRAFT ||
+                row.status === BorrowStatus.REJECTED,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: '鎻愪氦瀹℃壒',
+              type: 'link',
+              icon: ACTION_ICON.AUDIT,
+              auth: ['archive:borrow:submit'],
+              ifShow: () =>
+                row.status === BorrowStatus.DRAFT ||
+                row.status === BorrowStatus.REJECTED,
+              onClick: handleSubmit.bind(null, row),
+            },
+            {
+              label: '褰掕繕鐧昏',
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['archive:borrow:return'],
+              ifShow: () => row.status === BorrowStatus.BORROWED,
+              onClick: handleReturn.bind(null, row),
+            },
+            {
+              label: '鏌ョ湅瀹℃壒',
+              type: 'link',
+              icon: ACTION_ICON.VIEW,
+              auth: ['archive:borrow:query'],
+              ifShow: () => !!row.processInstanceId,
+              onClick: handleProcessDetail.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['archive:borrow:delete'],
+              ifShow: () => row.status === BorrowStatus.DRAFT,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.no]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/archive/borrow/modules/form.vue b/src/views/archive/borrow/modules/form.vue
new file mode 100644
index 0000000..f931706
--- /dev/null
+++ b/src/views/archive/borrow/modules/form.vue
@@ -0,0 +1,133 @@
+<script lang="ts" setup>
+import type { ArchiveBorrowApi } from '#/api/archive/borrow';
+
+import type { FormType } from '../data';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import { message } from 'ant-design-vue';
+import dayjs from 'dayjs';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createBorrow,
+  getBorrow,
+  updateBorrow,
+} from '#/api/archive/borrow';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'ArchiveBorrowForm' });
+
+const emit = defineEmits(['success']);
+
+const formType = ref<FormType>('create');
+/** 浠庢。妗堝垪琛ㄣ�岀櫥璁板�熼槄銆嶅甫鍏ョ殑妗f ID */
+const fixedDocumentId = ref<number>();
+
+const getTitle = computed(() => {
+  if (formType.value === 'create') {
+    return '鐧昏鍊熼槄';
+  }
+  if (formType.value === 'edit') {
+    return $t('ui.actionTitle.edit', ['鍊熼槄鍗�']);
+  }
+  return '鍊熼槄鍗曡鎯�';
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    labelWidth: 110,
+  },
+  wrapperClass: 'grid-cols-2',
+  layout: 'vertical',
+  schema: [],
+  showDefaultActions: false,
+});
+
+/** 鍙彁浜ゅ悗绔� SaveReqVO 鎺ュ彈鐨勫瓧娈� */
+async function buildSubmitData() {
+  const values = (await formApi.getValues()) as ArchiveBorrowApi.Borrow;
+  return {
+    id: values.id,
+    documentId: values.documentId,
+    borrowerUserId: values.borrowerUserId,
+    borrowerName: values.borrowerName,
+    borrowerDept: values.borrowerDept,
+    borrowerPhone: values.borrowerPhone,
+    borrowDate: values.borrowDate,
+    borrowPurpose: values.borrowPurpose,
+    returnDeadline: values.returnDeadline,
+    remark: values.remark,
+  } as ArchiveBorrowApi.Borrow;
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    const data = await buildSubmitData();
+    try {
+      await (formType.value === 'create'
+        ? createBorrow(data)
+        : updateBorrow(data));
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      fixedDocumentId.value = undefined;
+      return;
+    }
+    const data = modalApi.getData<{
+      formType: FormType;
+      id?: number;
+      documentId?: number;
+    }>();
+    formType.value = data.formType;
+    fixedDocumentId.value = data.documentId;
+    formApi.setState({ schema: useFormSchema(formType.value, data.documentId) });
+    formApi.setDisabled(formType.value === 'detail');
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        const detail = await getBorrow(data.id);
+        await formApi.setValues(detail, false);
+      } finally {
+        modalApi.unlock();
+      }
+    } else {
+      await formApi.resetForm();
+      // 鏂板缓鏃跺甫鍑烘。妗堜笌榛樿鍊熼槄鏃ユ湡锛堜粖澶╋級
+      await formApi.setValues({
+        documentId: data.documentId,
+        borrowDate: dayjs().format('YYYY-MM-DD'),
+      });
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal
+    :title="getTitle"
+    class="w-2/3"
+    :close-on-click-modal="false"
+    :show-confirm-button="formType !== 'detail'"
+  >
+    <Form class="mx-3" />
+  </Modal>
+</template>
diff --git a/src/views/archive/borrow/modules/process-select-modal.vue b/src/views/archive/borrow/modules/process-select-modal.vue
new file mode 100644
index 0000000..694d71e
--- /dev/null
+++ b/src/views/archive/borrow/modules/process-select-modal.vue
@@ -0,0 +1,77 @@
+<script lang="ts" setup>
+import type { ArchiveBorrowApi } from '#/api/archive/borrow';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message, Select } from 'ant-design-vue';
+
+import { submitBorrow } from '#/api/archive/borrow';
+
+defineOptions({ name: 'ArchiveBorrowProcessSelect' });
+
+const emit = defineEmits(['success']);
+
+const borrowId = ref<number>();
+const processList = ref<ArchiveBorrowApi.ProcessDefinition[]>([]);
+const selectedProcessKey = ref<string>('');
+
+/** 娴佺▼ key 鍙兘鏉ヨ嚜鍚庣瀹炴椂鍒楄〃锛岀姝㈠墠绔啓姝� */
+const processOptions = computed(() =>
+  processList.value.map((item) => ({ label: item.name, value: item.key })),
+);
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!selectedProcessKey.value) {
+      message.warning('璇烽�夋嫨瀹℃壒娴佺▼');
+      return;
+    }
+    modalApi.lock();
+    try {
+      await submitBorrow(borrowId.value!, selectedProcessKey.value);
+      await modalApi.close();
+      emit('success');
+      message.success('鎻愪氦鎴愬姛');
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      borrowId.value = undefined;
+      selectedProcessKey.value = '';
+      processList.value = [];
+      return;
+    }
+    const data = modalApi.getData<{
+      id: number;
+      processList: ArchiveBorrowApi.ProcessDefinition[];
+    }>();
+    borrowId.value = data.id;
+    processList.value = data.processList || [];
+    // 榛樿閫夋嫨绗竴涓祦绋�
+    if (processList.value.length > 0) {
+      selectedProcessKey.value = processList.value[0]!.key;
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal title="閫夋嫨瀹℃壒娴佺▼" class="w-1/3">
+    <div class="mb-4">
+      <span class="mr-2">瀹℃壒娴佺▼锛�</span>
+      <Select
+        v-model:value="selectedProcessKey"
+        :options="processOptions"
+        style="width: 240px"
+        placeholder="璇烽�夋嫨瀹℃壒娴佺▼"
+      />
+    </div>
+    <p class="text-sm text-gray-500">
+      鎻愪氦鍚庝緷娆℃祦杞細妗f绠$悊鍛樺鏍� 鈫� 鍒嗙棰嗗瀹℃壒
+    </p>
+  </Modal>
+</template>
diff --git a/src/views/archive/borrow/modules/return-form.vue b/src/views/archive/borrow/modules/return-form.vue
new file mode 100644
index 0000000..54d3688
--- /dev/null
+++ b/src/views/archive/borrow/modules/return-form.vue
@@ -0,0 +1,82 @@
+<script lang="ts" setup>
+import type { ArchiveBorrowApi } from '#/api/archive/borrow';
+
+import { ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { returnBorrow } from '#/api/archive/borrow';
+
+defineOptions({ name: 'ArchiveBorrowReturnForm' });
+
+const emit = defineEmits(['success']);
+
+/** 褰撳墠褰掕繕鐨勫�熼槄鍗� */
+const current = ref<ArchiveBorrowApi.Borrow>();
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  wrapperClass: 'grid-cols-1',
+  layout: 'vertical',
+  schema: [
+    {
+      fieldName: 'returnRemark',
+      label: '褰掕繕澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        rows: 3,
+        placeholder: '濡傚綊杩樻椂妗f瀹屽ソ銆佹槸鍚︽湁缂洪〉绛�',
+      },
+    },
+  ],
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const values = (await formApi.getValues()) as { returnRemark?: string };
+    modalApi.lock();
+    try {
+      await returnBorrow({
+        id: current.value!.id!,
+        returnRemark: values.returnRemark,
+      });
+      await modalApi.close();
+      emit('success');
+      message.success('褰掕繕鐧昏鎴愬姛');
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      current.value = undefined;
+      return;
+    }
+    current.value = modalApi.getData<ArchiveBorrowApi.Borrow>();
+    await formApi.resetForm();
+  },
+});
+</script>
+
+<template>
+  <Modal title="褰掕繕鐧昏" class="w-1/2">
+    <div class="mx-3 mb-3 text-sm text-gray-600">
+      <p>
+        鍊熼槄鍗曞彿锛歿{ current?.no }}銆�妗f锛歿{ current?.documentNo }}
+        {{ current?.documentTitle }}
+      </p>
+      <p>
+        鍊熼槄浜猴細{{ current?.borrowerName }}銆�褰掕繕鏈熼檺锛歿{ current?.returnDeadline }}
+      </p>
+    </div>
+    <Form class="mx-3" />
+  </Modal>
+</template>
diff --git a/src/views/archive/category/data.ts b/src/views/archive/category/data.ts
new file mode 100644
index 0000000..da37a6f
--- /dev/null
+++ b/src/views/archive/category/data.ts
@@ -0,0 +1,192 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ArchiveCategoryApi } from '#/api/archive/category';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+import { handleTree } from '@vben/utils';
+
+import { getCategoryList } from '#/api/archive/category';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'id',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'parentId',
+      label: '涓婄骇鍒嗙被',
+      component: 'ApiTreeSelect',
+      componentProps: {
+        allowClear: true,
+        api: async () => {
+          const data = await getCategoryList();
+          data.unshift({
+            id: 0,
+            name: '椤剁骇鍒嗙被',
+          } as ArchiveCategoryApi.Category);
+          return handleTree(data);
+        },
+        fieldNames: { label: 'name', value: 'id', children: 'children' },
+        placeholder: '璇烽�夋嫨涓婄骇鍒嗙被',
+        showSearch: true,
+        treeDefaultExpandAll: true,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'name',
+      label: '鍒嗙被鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ垎绫诲悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'code',
+      label: '鍒嗙被缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '鑻辨枃缂栫爜锛屽 member/research/finance',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'defaultSecretLevel',
+      label: '榛樿瀵嗙骇',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_SECRET_LEVEL, 'number'),
+        placeholder: '璇ュ垎绫讳笅妗f鐨勯粯璁ゅ瘑绾�',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'sort',
+      label: '鏄剧ず鎺掑簭',
+      component: 'InputNumber',
+      componentProps: {
+        min: 0,
+        placeholder: '鏁板瓧瓒婂皬瓒婇潬鍓�',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_CATEGORY_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+    },
+    {
+      fieldName: 'description',
+      label: '鍒嗙被璇存槑',
+      component: 'Textarea',
+      componentProps: {
+        rows: 2,
+        placeholder: '璇疯緭鍏ュ垎绫昏鏄�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        rows: 2,
+        placeholder: '璇疯緭鍏ュ娉�',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'name',
+      label: '鍒嗙被鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ垎绫诲悕绉�',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_CATEGORY_STATUS, 'number'),
+        allowClear: true,
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 琛ㄦ牸鍒楅厤缃� */
+export function useGridColumns(): VxeTableGridOptions<ArchiveCategoryApi.Category>['columns'] {
+  return [
+    {
+      field: 'name',
+      title: '鍒嗙被鍚嶇О',
+      treeNode: true,
+    },
+    {
+      field: 'code',
+      title: '鍒嗙被缂栫爜',
+      minWidth: 130,
+    },
+    {
+      field: 'defaultSecretLevel',
+      title: '榛樿瀵嗙骇',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.ARCHIVE_SECRET_LEVEL },
+      },
+    },
+    {
+      field: 'sort',
+      title: '鎺掑簭',
+      minWidth: 80,
+    },
+    {
+      field: 'description',
+      title: '鍒嗙被璇存槑',
+      minWidth: 180,
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      minWidth: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.ARCHIVE_CATEGORY_STATUS },
+      },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'actions',
+      title: '鎿嶄綔',
+      width: 250,
+      fixed: 'right',
+      slots: {
+        default: 'actions',
+      },
+    },
+  ];
+}
diff --git a/src/views/archive/category/index.vue b/src/views/archive/category/index.vue
new file mode 100644
index 0000000..c3c5c52
--- /dev/null
+++ b/src/views/archive/category/index.vue
@@ -0,0 +1,163 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ArchiveCategoryApi } from '#/api/archive/category';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteCategory,
+  getCategoryList,
+} from '#/api/archive/category';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+/** 妗f鍒嗙被锛堜功鏋讹級 */
+defineOptions({ name: 'ArchiveCategory' });
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒囨崲鏍戝舰灞曞紑/鏀剁缉鐘舵�� */
+const isExpanded = ref(false);
+function handleExpand() {
+  isExpanded.value = !isExpanded.value;
+  gridApi.grid.setAllTreeExpand(isExpanded.value);
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鍒嗙被 */
+function handleCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 娣诲姞涓嬬骇鍒嗙被 */
+function handleAppend(row: ArchiveCategoryApi.Category) {
+  formModalApi.setData({ parentId: row.id }).open();
+}
+
+/** 缂栬緫鍒嗙被 */
+function handleEdit(row: ArchiveCategoryApi.Category) {
+  formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎鍒嗙被 */
+async function handleDelete(row: ArchiveCategoryApi.Category) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.name]),
+    duration: 0,
+  });
+  try {
+    await deleteCategory(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    pagerConfig: {
+      enabled: false,
+    },
+    proxyConfig: {
+      ajax: {
+        query: async (_, formValues) => {
+          return await getCategoryList(formValues);
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+    treeConfig: {
+      parentField: 'parentId',
+      rowField: 'id',
+      transform: true,
+      expandAll: true,
+      reserve: true,
+    },
+  } as VxeTableGridOptions<ArchiveCategoryApi.Category>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <Grid table-title="妗f鍒嗙被锛堜功鏋讹級">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['妗f鍒嗙被']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['archive:category:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: isExpanded ? '鏀剁缉' : '灞曞紑',
+              type: 'primary',
+              onClick: handleExpand,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '鏂板涓嬬骇',
+              type: 'link',
+              icon: ACTION_ICON.ADD,
+              auth: ['archive:category:create'],
+              onClick: handleAppend.bind(null, row),
+            },
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['archive:category:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['archive:category:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/archive/category/modules/form.vue b/src/views/archive/category/modules/form.vue
new file mode 100644
index 0000000..a9142c9
--- /dev/null
+++ b/src/views/archive/category/modules/form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { ArchiveCategoryApi } from '#/api/archive/category';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createCategory,
+  getCategory,
+  updateCategory,
+} from '#/api/archive/category';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'ArchiveCategoryForm' });
+
+const emit = defineEmits(['success']);
+
+const formData = ref<ArchiveCategoryApi.Category>();
+
+const getTitle = computed(() => {
+  if (formData.value?.id) {
+    return $t('ui.actionTitle.edit', ['妗f鍒嗙被']);
+  }
+  return formData.value?.parentId
+    ? '鏂板涓嬬骇鍒嗙被'
+    : $t('ui.actionTitle.create', ['妗f鍒嗙被']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    const data = (await formApi.getValues()) as ArchiveCategoryApi.Category;
+    try {
+      await (formData.value?.id
+        ? updateCategory({ ...data, id: formData.value.id })
+        : createCategory(data));
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    let data = modalApi.getData<ArchiveCategoryApi.Category>();
+    // 鏂板锛堥《绾� / 涓嬬骇锛夋椂鍙甫 parentId 浣滀负榛樿鍊�
+    if (!data || !data.id) {
+      formData.value = data ?? undefined;
+      await formApi.resetForm();
+      if (data?.parentId !== undefined) {
+        await formApi.setValues({ parentId: data.parentId });
+      }
+      return;
+    }
+    modalApi.lock();
+    try {
+      data = await getCategory(data.id);
+      formData.value = data;
+      await formApi.setValues(data);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal class="w-2/5" :title="getTitle">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/archive/document/data.ts b/src/views/archive/document/data.ts
new file mode 100644
index 0000000..fa71c7d
--- /dev/null
+++ b/src/views/archive/document/data.ts
@@ -0,0 +1,442 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ArchiveDocumentApi } from '#/api/archive/document';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+import { handleTree } from '@vben/utils';
+
+import { getCategoryList } from '#/api/archive/category';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'edit';
+
+/** 妗f琛ㄥ崟閰嶇疆 */
+export function useFormSchema(formType: FormType): VbenFormSchema[] {
+  const readonly = formType === 'detail';
+  // 妗f缂栧彿銆佹潵婧愩�佺姸鎬併�佸綊妗d汉鐢卞悗绔淮鎶わ紝浠呯紪杈�/璇︽儏鍙
+  const showBackendFields = formType !== 'create';
+
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'no',
+      label: '妗f缂栧彿',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => showBackendFields,
+      },
+      componentProps: {
+        disabled: true,
+        placeholder: '绯荤粺鑷姩鐢熸垚',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '妗f鐘舵��',
+      component: 'Select',
+      dependencies: {
+        triggerFields: [''],
+        show: () => showBackendFields,
+      },
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_DOCUMENT_STATUS, 'number'),
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'title',
+      label: '妗f鏍囬',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        disabled: readonly,
+        placeholder: '璇疯緭鍏ユ。妗堟爣棰�',
+      },
+      rules: 'required',
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'categoryId',
+      label: '妗f鍒嗙被',
+      component: 'ApiTreeSelect',
+      componentProps: {
+        allowClear: true,
+        api: async () => handleTree(await getCategoryList({ status: 0 })),
+        fieldNames: { label: 'name', value: 'id', children: 'children' },
+        placeholder: '璇烽�夋嫨妗f鍒嗙被',
+        showSearch: true,
+        treeDefaultExpandAll: true,
+        disabled: readonly,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'secretLevel',
+      label: '瀵嗙骇',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_SECRET_LEVEL, 'number'),
+        placeholder: '璇烽�夋嫨瀵嗙骇',
+        disabled: readonly,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'carrierType',
+      label: '杞戒綋',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_CARRIER_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨杞戒綋',
+        disabled: readonly,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'archiveDate',
+      label: '褰掓。鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        allowClear: true,
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨褰掓。鏃ユ湡',
+        disabled: readonly,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'shelfNo',
+      label: '涔︽灦鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '濡� A鏋�',
+        disabled: readonly,
+      },
+    },
+    {
+      fieldName: 'layerNo',
+      label: '灞傚彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '濡� 2灞�',
+        disabled: readonly,
+      },
+    },
+    {
+      fieldName: 'columnNo',
+      label: '鍒楀彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '濡� 3鍒�',
+        disabled: readonly,
+      },
+    },
+    {
+      fieldName: 'boxNo',
+      label: '鍐屽彿/妗f鐩掑彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '濡� 妗f鐩�-001',
+        disabled: readonly,
+      },
+    },
+    {
+      fieldName: 'storePlace',
+      label: '瀛樻斁浣嶇疆',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '濡� 3鍙峰簱鎴緼鏋�2灞�',
+        disabled: readonly,
+      },
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'tags',
+      label: '鍏抽敭璇�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '澶氫釜鍏抽敭璇嶇敤鑻辨枃閫楀彿鍒嗛殧锛屼究浜庢绱�',
+        disabled: readonly,
+      },
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'summary',
+      label: '鎽樿',
+      component: 'Textarea',
+      componentProps: {
+        rows: 2,
+        placeholder: '璇疯緭鍏ユ憳瑕�',
+        disabled: readonly,
+      },
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'sourceType',
+      label: '鏉ユ簮绫诲瀷',
+      component: 'Select',
+      dependencies: {
+        triggerFields: [''],
+        show: () => showBackendFields,
+      },
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_SOURCE_TYPE, 'number'),
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'sourceNo',
+      label: '鏉ユ簮鍗曞彿',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => showBackendFields,
+      },
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'archivistName',
+      label: '褰掓。浜�',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => showBackendFields,
+      },
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        rows: 2,
+        placeholder: '璇疯緭鍏ュ娉�',
+        disabled: readonly,
+      },
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'blobIds',
+      label: '鐢靛瓙鏂囦欢',
+      component: 'FileUpload',
+      componentProps: {
+        valueKey: 'id',
+        maxNumber: 10,
+        multiple: true,
+        showDescription: !readonly,
+        disabled: readonly,
+      },
+      formItemClass: 'col-span-2',
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'no',
+      label: '妗f缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ。妗堢紪鍙�',
+      },
+    },
+    {
+      fieldName: 'title',
+      label: '妗f鏍囬',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ。妗堟爣棰�',
+      },
+    },
+    {
+      fieldName: 'categoryId',
+      label: '妗f鍒嗙被',
+      component: 'ApiTreeSelect',
+      componentProps: {
+        allowClear: true,
+        api: async () => handleTree(await getCategoryList({ status: 0 })),
+        fieldNames: { label: 'name', value: 'id', children: 'children' },
+        placeholder: '璇烽�夋嫨妗f鍒嗙被',
+        showSearch: true,
+        treeDefaultExpandAll: true,
+      },
+    },
+    {
+      fieldName: 'secretLevel',
+      label: '瀵嗙骇',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_SECRET_LEVEL, 'number'),
+        allowClear: true,
+        placeholder: '璇烽�夋嫨瀵嗙骇',
+      },
+    },
+    {
+      fieldName: 'carrierType',
+      label: '杞戒綋',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_CARRIER_TYPE, 'number'),
+        allowClear: true,
+        placeholder: '璇烽�夋嫨杞戒綋',
+      },
+    },
+    {
+      fieldName: 'sourceType',
+      label: '鏉ユ簮绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_SOURCE_TYPE, 'number'),
+        allowClear: true,
+        placeholder: '璇烽�夋嫨鏉ユ簮绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'sourceNo',
+      label: '鏉ユ簮鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鍙�',
+      },
+    },
+    {
+      fieldName: 'tags',
+      label: '鍏抽敭璇�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叧閿瘝',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '妗f鐘舵��',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.ARCHIVE_DOCUMENT_STATUS, 'number'),
+        allowClear: true,
+        placeholder: '璇烽�夋嫨妗f鐘舵��',
+      },
+    },
+    {
+      fieldName: 'archiveDate',
+      label: '褰掓。鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        allowClear: true,
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: ['寮�濮嬫棩鏈�', '缁撴潫鏃ユ湡'],
+      },
+    },
+  ];
+}
+
+/** 琛ㄦ牸鍒楅厤缃� */
+export function useGridColumns(): VxeTableGridOptions<ArchiveDocumentApi.Document>['columns'] {
+  return [
+    {
+      field: 'no',
+      title: '妗f缂栧彿',
+      minWidth: 170,
+    },
+    {
+      field: 'title',
+      title: '妗f鏍囬',
+      minWidth: 220,
+    },
+    {
+      field: 'categoryName',
+      title: '妗f鍒嗙被',
+      minWidth: 120,
+    },
+    {
+      field: 'secretLevel',
+      title: '瀵嗙骇',
+      minWidth: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.ARCHIVE_SECRET_LEVEL },
+      },
+    },
+    {
+      field: 'carrierType',
+      title: '杞戒綋',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.ARCHIVE_CARRIER_TYPE },
+      },
+    },
+    {
+      field: 'sourceType',
+      title: '鏉ユ簮',
+      minWidth: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.ARCHIVE_SOURCE_TYPE },
+      },
+    },
+    {
+      field: 'sourceNo',
+      title: '鏉ユ簮鍗曞彿',
+      minWidth: 160,
+    },
+    {
+      field: 'storePlace',
+      title: '瀛樻斁浣嶇疆',
+      minWidth: 170,
+    },
+    {
+      field: 'archiveDate',
+      title: '褰掓。鏃ユ湡',
+      minWidth: 120,
+    },
+    {
+      field: 'status',
+      title: '妗f鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.ARCHIVE_DOCUMENT_STATUS },
+      },
+    },
+    {
+      field: 'hasAttachment',
+      title: '鐢靛瓙鏂囦欢',
+      minWidth: 100,
+      slots: { default: 'hasAttachment' },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 200,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/archive/document/index.vue b/src/views/archive/document/index.vue
new file mode 100644
index 0000000..4f3fd7c
--- /dev/null
+++ b/src/views/archive/document/index.vue
@@ -0,0 +1,163 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ArchiveDocumentApi } from '#/api/archive/document';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteDocument,
+  exportDocument,
+  getDocumentPage,
+} from '#/api/archive/document';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+/** 妗f绠$悊 */
+defineOptions({ name: 'ArchiveDocument' });
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鏂板妗f */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 缂栬緫妗f */
+function handleEdit(row: ArchiveDocumentApi.Document) {
+  formModalApi.setData({ formType: 'edit', id: row.id }).open();
+}
+
+/** 鏌ョ湅妗f璇︽儏 */
+function handleDetail(row: ArchiveDocumentApi.Document) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 鍒犻櫎妗f */
+async function handleDelete(row: ArchiveDocumentApi.Document) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.title]),
+    duration: 0,
+  });
+  try {
+    await deleteDocument(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.title]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 瀵煎嚭 Excel */
+async function handleExport() {
+  const data = await exportDocument(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '妗f.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getDocumentPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<ArchiveDocumentApi.Document>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <Grid table-title="妗f绠$悊">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['妗f']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['archive:document:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: '瀵煎嚭',
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['archive:document:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #hasAttachment="{ row }">
+        <Tag :color="row.hasAttachment ? 'success' : 'default'">
+          {{ row.hasAttachment ? '鏈�' : '鏃�' }}
+        </Tag>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.detail'),
+              type: 'link',
+              icon: ACTION_ICON.VIEW,
+              auth: ['archive:document:query'],
+              onClick: handleDetail.bind(null, row),
+            },
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['archive:document:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['archive:document:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.no]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/archive/document/modules/form.vue b/src/views/archive/document/modules/form.vue
new file mode 100644
index 0000000..a002b28
--- /dev/null
+++ b/src/views/archive/document/modules/form.vue
@@ -0,0 +1,133 @@
+<script lang="ts" setup>
+import type { ArchiveDocumentApi } from '#/api/archive/document';
+
+import type { FormType } from '../data';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createDocument,
+  getDocument,
+  updateDocument,
+} from '#/api/archive/document';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'ArchiveDocumentForm' });
+
+const emit = defineEmits(['success']);
+
+const formType = ref<FormType>('create');
+
+const getTitle = computed(() => {
+  if (formType.value === 'create') {
+    return $t('ui.actionTitle.create', ['妗f']);
+  }
+  if (formType.value === 'edit') {
+    return $t('ui.actionTitle.edit', ['妗f']);
+  }
+  return '妗f璇︽儏';
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    labelWidth: 120,
+  },
+  wrapperClass: 'grid-cols-2',
+  layout: 'vertical',
+  schema: [],
+  showDefaultActions: false,
+});
+
+/** 鍙彁浜ゅ悗绔� SaveReqVO 鎺ュ彈鐨勫瓧娈碉紝閬垮厤鍥炰紶 no/status/source* 绛夊彧璇诲瓧娈� */
+async function buildSubmitData() {
+  const values = (await formApi.getValues()) as ArchiveDocumentApi.Document;
+  return {
+    id: values.id,
+    title: values.title,
+    categoryId: values.categoryId,
+    secretLevel: values.secretLevel,
+    carrierType: values.carrierType,
+    shelfNo: values.shelfNo,
+    layerNo: values.layerNo,
+    columnNo: values.columnNo,
+    boxNo: values.boxNo,
+    storePlace: values.storePlace,
+    archiveDate: values.archiveDate,
+    summary: values.summary,
+    tags: values.tags,
+    remark: values.remark,
+    // 闄勪欢涓哄叏閲忔浛鎹㈣涔夛細娓呯┖鍏ㄩ儴闄勪欢鏃跺繀椤绘樉寮忎紶绌烘暟缁�
+    blobIds: values.blobIds ?? [],
+  } as ArchiveDocumentApi.Document;
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    const data = await buildSubmitData();
+    try {
+      await (formType.value === 'create'
+        ? createDocument(data)
+        : updateDocument(data));
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      return;
+    }
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value) });
+    formApi.setDisabled(formType.value === 'detail');
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        const detail = await getDocument(data.id);
+        // 宸叉湁闄勪欢杞垚涓婁紶缁勪欢鍙瘑鍒殑鏂囦欢瀵硅薄锛屾彁浜ゆ椂鎸� valueKey=id 鎻愬彇涓� blobId
+        const blobIds = (detail.attachmentList ?? []).map((item) => ({
+          uid: String(item.id),
+          name: item.name || '',
+          url: item.url || '',
+          status: 'done',
+          id: item.id,
+        }));
+        await formApi.setValues({ ...detail, blobIds }, false);
+      } finally {
+        modalApi.unlock();
+      }
+    } else {
+      await formApi.resetForm();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal
+    :title="getTitle"
+    class="w-3/4"
+    :close-on-click-modal="false"
+    :show-confirm-button="formType !== 'detail'"
+  >
+    <Form class="mx-3" />
+  </Modal>
+</template>
diff --git a/src/views/archive/stat/data.ts b/src/views/archive/stat/data.ts
new file mode 100644
index 0000000..2fa65e9
--- /dev/null
+++ b/src/views/archive/stat/data.ts
@@ -0,0 +1,68 @@
+import type { EChartsOption } from '@vben/plugins/echarts';
+
+import type { ArchiveStatApi } from '#/api/archive/stat';
+
+/** 椤堕儴缁熻鍗$墖 */
+export interface StatCard {
+  key: keyof ArchiveStatApi.StatOverview;
+  label: string;
+  color: string;
+}
+
+export const STAT_CARDS: StatCard[] = [
+  { key: 'totalCount', label: '妗f鎬绘暟', color: '#1677ff' },
+  { key: 'inStockCount', label: '鍦ㄥ簱妗f', color: '#52c41a' },
+  { key: 'borrowedCount', label: '鍊熷嚭涓。妗�', color: '#faad14' },
+  { key: 'overdueCount', label: '閫炬湡鏈繕', color: '#ff4d4f' },
+];
+
+/** 閫氱敤楗煎浘閰嶇疆 */
+export function buildPieOption(
+  title: string,
+  rows: { name: string; value: number }[],
+): EChartsOption {
+  return {
+    title: { text: title, left: 'center', textStyle: { fontSize: 14 } },
+    tooltip: { trigger: 'item' },
+    legend: { orient: 'vertical', left: 'left', top: 'middle' },
+    series: [
+      {
+        name: title,
+        type: 'pie',
+        radius: ['40%', '65%'],
+        center: ['60%', '55%'],
+        data: rows,
+        emphasis: {
+          itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.5)' },
+        },
+      },
+    ],
+  };
+}
+
+/** 閫氱敤鏌辩姸鍥鹃厤缃� */
+export function buildBarOption(
+  title: string,
+  rows: { name: string; value: number }[],
+): EChartsOption {
+  return {
+    title: { text: title, left: 'center', textStyle: { fontSize: 14 } },
+    tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
+    grid: { left: 20, right: 20, bottom: 20, containLabel: true },
+    xAxis: {
+      type: 'category',
+      data: rows.map((item) => item.name),
+      axisTick: { alignWithLabel: true },
+    },
+    yAxis: { type: 'value', minInterval: 1 },
+    series: [
+      {
+        name: title,
+        type: 'bar',
+        barMaxWidth: 48,
+        data: rows.map((item) => item.value),
+        label: { show: true, position: 'top' },
+      },
+    ],
+  };
+}
diff --git a/src/views/archive/stat/index.vue b/src/views/archive/stat/index.vue
new file mode 100644
index 0000000..bdc7c17
--- /dev/null
+++ b/src/views/archive/stat/index.vue
@@ -0,0 +1,123 @@
+<script lang="ts" setup>
+import type { EchartsUIType } from '@vben/plugins/echarts';
+
+import type { ArchiveStatApi } from '#/api/archive/stat';
+
+import { computed, onMounted, ref } from 'vue';
+
+import { ContentWrap, Page } from '@vben/common-ui';
+import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
+
+import { Spin } from 'ant-design-vue';
+
+import { getArchiveStatOverview } from '#/api/archive/stat';
+
+import { buildBarOption, buildPieOption, STAT_CARDS } from './data';
+
+/** 妗f缁熻 */
+defineOptions({ name: 'ArchiveStat' });
+
+const loading = ref(false);
+const overview = ref<ArchiveStatApi.StatOverview>({});
+
+const categoryChartRef = ref<EchartsUIType>();
+const secretChartRef = ref<EchartsUIType>();
+const carrierChartRef = ref<EchartsUIType>();
+
+const { renderEcharts: renderCategory } = useEcharts(categoryChartRef);
+const { renderEcharts: renderSecret } = useEcharts(secretChartRef);
+const { renderEcharts: renderCarrier } = useEcharts(carrierChartRef);
+
+/** 鍗$墖鍙栧�� */
+const cards = computed(() =>
+  STAT_CARDS.map((card) => ({
+    ...card,
+    value: (overview.value[card.key] as number | undefined) ?? 0,
+  })),
+);
+
+async function loadData() {
+  loading.value = true;
+  try {
+    const data = await getArchiveStatOverview();
+    overview.value = data;
+    await Promise.all([
+      renderCategory(
+        buildPieOption(
+          '鎸夊垎绫诲垎甯�',
+          (data.byCategory ?? []).map((item) => ({
+            name: item.name ?? '鏈懡鍚�',
+            value: item.total ?? 0,
+          })),
+        ),
+      ),
+      renderSecret(
+        buildBarOption(
+          '鎸夊瘑绾у垎甯�',
+          (data.bySecretLevel ?? []).map((item) => ({
+            name: item.name ?? '鏈缃�',
+            value: item.total ?? 0,
+          })),
+        ),
+      ),
+      renderCarrier(
+        buildPieOption(
+          '鎸夎浇浣撳垎甯�',
+          (data.byCarrierType ?? []).map((item) => ({
+            name: item.name ?? '鏈缃�',
+            value: item.total ?? 0,
+          })),
+        ),
+      ),
+    ]);
+  } finally {
+    loading.value = false;
+  }
+}
+
+onMounted(loadData);
+</script>
+
+<template>
+  <Page auto-content-height>
+    <Spin :spinning="loading" tip="鍔犺浇涓�...">
+      <div class="space-y-4">
+        <!-- 缁熻鍗$墖 -->
+        <div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
+          <ContentWrap
+            v-for="card in cards"
+            :key="card.key"
+            class="flex items-center justify-between"
+          >
+            <div>
+              <p class="text-sm text-gray-500">{{ card.label }}</p>
+              <p
+                class="mt-1 text-2xl font-semibold"
+                :style="{ color: card.color }"
+              >
+                {{ card.value }}
+              </p>
+            </div>
+            <span
+              class="inline-block h-10 w-1 rounded"
+              :style="{ backgroundColor: card.color }"
+            />
+          </ContentWrap>
+        </div>
+
+        <!-- 鍒嗗竷鍥捐〃 -->
+        <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
+          <ContentWrap>
+            <EchartsUI class="h-72 w-full" ref="categoryChartRef" />
+          </ContentWrap>
+          <ContentWrap>
+            <EchartsUI class="h-72 w-full" ref="secretChartRef" />
+          </ContentWrap>
+        </div>
+        <ContentWrap>
+          <EchartsUI class="h-72 w-full" ref="carrierChartRef" />
+        </ContentWrap>
+      </div>
+    </Spin>
+  </Page>
+</template>
diff --git a/src/views/finance/reimbursement/index.vue b/src/views/finance/reimbursement/index.vue
index 49f64c9..3855bac 100644
--- a/src/views/finance/reimbursement/index.vue
+++ b/src/views/finance/reimbursement/index.vue
@@ -110,8 +110,8 @@
     duration: 0,
   });
   try {
-    await archiveReimbursement(row.id!);
-    message.success('褰掓。鎴愬姛');
+    const archiveNo = await archiveReimbursement(row.id!);
+    message.success(`宸插綊妗e苟鐢熸垚妗f ${archiveNo}`);
     handleRefresh();
   } finally {
     hideLoading();
diff --git a/src/views/research/application/index.vue b/src/views/research/application/index.vue
index b66ce43..b7828c1 100644
--- a/src/views/research/application/index.vue
+++ b/src/views/research/application/index.vue
@@ -12,6 +12,7 @@
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import {
+  archiveApplication,
   deleteApplication,
   getApplicationApproveProcessList,
   getApplicationPage,
@@ -142,6 +143,18 @@
     name: 'BpmProcessInstanceDetail',
     query: { id: row.processInstanceId },
   });
+}
+
+/** 褰掓。鍒版。妗堢鐞嗭紙浠呭凡绔嬮」鍙綊妗o級 */
+async function handleArchive(row: ResearchApplicationApi.Application) {
+  const hideLoading = message.loading({ content: '褰掓。涓�...', duration: 0 });
+  try {
+    const archiveNo = await archiveApplication(row.id!);
+    message.success(`褰掓。鎴愬姛锛屽凡鐢熸垚妗f ${archiveNo}`);
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
 }
 
 const checkedIds = ref<number[]>([]);
@@ -284,6 +297,19 @@
               onClick: handlePublishNotice.bind(null, row),
             },
             {
+              label: '褰掓。',
+              type: 'link',
+              icon: ACTION_ICON.UPLOAD,
+              auth: ['research:application:archive'],
+              ifShow: () =>
+                row.status === ApplicationStatus.APPROVED &&
+                !row.archiveDocumentId,
+              popConfirm: {
+                title: `纭褰掓。璇鹃 ${row.researchNo || row.no} 鍚楋紵褰掓。鍚庡皢鍦ㄦ。妗堢鐞嗙敓鎴愬搴旀。妗坄,
+                confirm: handleArchive.bind(null, row),
+              },
+            },
+            {
               label: '鏌ョ湅瀹℃壒',
               type: 'link',
               icon: ACTION_ICON.VIEW,
diff --git a/src/views/ums/member/index.vue b/src/views/ums/member/index.vue
index aebb821..bee2433 100644
--- a/src/views/ums/member/index.vue
+++ b/src/views/ums/member/index.vue
@@ -13,9 +13,18 @@
 import { getDictOptions } from '@vben/hooks';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteMember, getMemberPage } from '#/api/ums/member';
+import {
+  archiveMember,
+  deleteMember,
+  getMemberPage,
+} from '#/api/ums/member';
 
-import { MemberType, useGridColumns, useGridFormSchema } from './data';
+import {
+  MemberStatus,
+  MemberType,
+  useGridColumns,
+  useGridFormSchema,
+} from './data';
 import Form from './modules/form.vue';
 
 /** 浼氬憳鍒楄〃 */
@@ -64,6 +73,18 @@
 const checkedIds = ref<number[]>([]);
 function handleRowCheckboxChange({ records }: { records: UmsMemberApi.Member[] }) {
   checkedIds.value = records.map((item) => item.id!);
+}
+
+/** 褰掓。鍒版。妗堢鐞嗭紙浠呮甯哥姸鎬佷細鍛樺彲褰掓。锛� */
+async function handleArchive(row: UmsMemberApi.Member) {
+  const hideLoading = message.loading({ content: '褰掓。涓�...', duration: 0 });
+  try {
+    const archiveNo = await archiveMember(row.id!);
+    message.success(`褰掓。鎴愬姛锛屽凡鐢熸垚妗f ${archiveNo}`);
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
 }
 
 /** 绛夌骇瀛楀吀鏄犲皠锛氭牴鎹細鍛樼被鍨嬫樉绀哄搴旂殑绛夌骇鍚嶇О */
@@ -165,6 +186,18 @@
               onClick: handleEdit.bind(null, row),
             },
             {
+              label: '褰掓。',
+              type: 'link',
+              icon: ACTION_ICON.UPLOAD,
+              auth: ['ums:member:archive'],
+              ifShow: () =>
+                row.status === MemberStatus.NORMAL && !row.archiveDocumentId,
+              popConfirm: {
+                title: `纭褰掓。浼氬憳 ${row.name} 鍚楋紵褰掓。鍚庡皢鍦ㄦ。妗堢鐞嗙敓鎴愬搴旀。妗坄,
+                confirm: handleArchive.bind(null, row),
+              },
+            },
+            {
               label: '鍒犻櫎',
               type: 'link',
               danger: true,

--
Gitblit v1.9.3