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