From ac565df702d10c6cb5caf5cdec131c07b3e9d7f7 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期五, 12 六月 2026 10:46:52 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' into dev_宁夏_万通新型

---
 src/views/collaborativeApproval/notificationManagement/summary/index.vue                       |    8 
 src/views/financialManagement/receivable/salesOut.vue                                          |   24 
 .gitignore                                                                                     |    2 
 doc/知识库模块前端实现文档.md                                                                             | 1212 ++++++++++
 src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue                     |  236 ++
 src/views/financialManagement/payable/payment.vue                                              |   22 
 src/views/productionManagement/workOrder/index.vue                                             |    2 
 multiple/assets/favicon/KHYYfavicon.ico                                                        |    0 
 src/api/collaborativeApproval/knowledgeBase.js                                                 |  161 +
 doc/知识库模块传参方式和参数命名规范文档.md                                                                      |  941 ++++++++
 src/views/financialManagement/voucher/index.vue                                                |   13 
 src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue               |  741 +++---
 src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue |    6 
 src/views/officeProcessAutomation/AttendManage/overtime-apply/index.vue                        |    2 
 src/views/financialManagement/payable/input-invoice.vue                                        |   22 
 src/views/productionManagement/workOrderManagement/index.vue                                   |    2 
 src/views/qualityManagement/metricBinding/index.vue                                            |    1 
 src/api/procurementManagement/paymentLedger.js                                                 |    9 
 src/views/financialManagement/assets/intangibleAssets.vue                                      |    5 
 src/views/financialManagement/receivable/reconciliation.vue                                    |   25 
 src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue                     |    4 
 vite.config.js                                                                                 |    2 
 src/views/productionManagement/productStructure/DetailNew/index.vue                            |  626 +++++
 src/views/collaborativeApproval/knowledgeBase/index.vue                                        |  741 ++++++
 src/views/inventoryManagement/stockManagement/New.vue                                          |    8 
 src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue                   |    6 
 src/views/personnelManagement/socialSecuritySet/components/formDia.vue                         |  784 +++---
 multiple/assets/favicon/NYfavicon.ico                                                          |    0 
 src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue                   |    4 
 src/components/PIMTable/PIMTable.vue                                                           |    2 
 multiple/assets/logo/KHYYLogo.png                                                              |    0 
 multiple/assets/logo/NYLogo.png                                                                |    0 
 src/views/financialManagement/receivable/invoiceApply.vue                                      |   23 
 src/views/productionManagement/workOrderEdit/index.vue                                         |    2 
 src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue                     |    9 
 src/views/financialManagement/receivable/salesReturn.vue                                       |   24 
 src/views/financialManagement/payable/paymentApply.vue                                         |   20 
 src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js                 |    4 
 src/views/financialManagement/payable/reconciliation.vue                                       |   22 
 src/views/financialManagement/payable/purchaseIn.vue                                           |   22 
 doc/知识库RAG功能实现文档.md                                                                            | 1034 +++++++++
 src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue                   |    6 
 src/views/productionManagement/productionOrder/index.vue                                       |    2 
 src/views/financialManagement/receivable/outputInvoice.vue                                     |   23 
 src/views/financialManagement/payable/purchaseReturn.vue                                       |   20 
 45 files changed, 5,960 insertions(+), 862 deletions(-)

diff --git a/.gitignore b/.gitignore
index 78a752d..f76fc8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,8 @@
 yarn-error.log*
 **/*.log
 
+.claude/
+
 tests/**/coverage/
 tests/e2e/reports
 selenium-debug.log
diff --git "a/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md" "b/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..4a94cc5
--- /dev/null
+++ "b/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md"
@@ -0,0 +1,1034 @@
+# 鐭ヨ瘑搴揜AG鍚戦噺妫�绱㈠姛鑳藉疄鐜版枃妗�
+
+## 涓�銆佸姛鑳芥杩�
+
+鍩轰簬 RAG锛圧etrieval-Augmented Generation锛夋妧鏈疄鐜扮煡璇嗗簱闂瓟鍔熻兘锛屾敮鎸侊細
+- 鐭ヨ瘑搴撶鐞嗭紙CRUD锛�
+- 鏂囦欢涓婁紶涓庡悜閲忓寲澶勭悊
+- 鍩轰簬鍚戦噺妫�绱㈢殑鏅鸿兘闂瓟
+- 澶氱鏂囦欢鏍煎紡鏀寔锛坱xt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df锛�
+
+## 浜屻�佹妧鏈灦鏋�
+
+### 2.1 鎶�鏈爤
+| 缁勪欢 | 鎶�鏈� |
+|------|------|
+| 鍚戦噺鏁版嵁搴� | Pinecone |
+| Embedding妯″瀷 | 闃块噷浜� DashScope text-embedding-v3 |
+| LLM | 闃块噷浜戦�氫箟鍗冮棶 qwen-max |
+| 妗嗘灦 | langchain4j |
+| ORM | MyBatis-Plus |
+
+### 2.2 鏋舵瀯鍥�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                        鍓嶇搴旂敤                              鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                     Controller Layer                         鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹� KnowledgeBaseCtrl   鈹�  鈹� KnowledgeChatController     鈹�   鈹�
+鈹�  鈹� (鐭ヨ瘑搴撶鐞�)         鈹�  鈹� (鐭ヨ瘑搴撻棶绛�)                 鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      Service Layer                           鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹侹nowledgeBaseService 鈹�  鈹� KnowledgeRagService         鈹�   鈹�
+鈹�  鈹� (鐭ヨ瘑搴揅RUD)         鈹�  鈹� (鍚戦噺鍖�/妫�绱�)                鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      AI Layer                                鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹� KnowledgeChatAgent  鈹�  鈹� EmbeddingStore (Pinecone)   鈹�   鈹�
+鈹�  鈹� (闂瓟Agent)          鈹�  鈹� (鍚戦噺瀛樺偍)                   鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 涓夈�佸悗绔疄鐜�
+
+### 3.1 鏁版嵁搴撹璁�
+
+#### 3.1.1 鐭ヨ瘑搴撹〃锛坘nowledge_base锛�
+
+```sql
+CREATE TABLE knowledge_base (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    title VARCHAR(255) COMMENT '鐭ヨ瘑鏍囬',
+    type VARCHAR(50) COMMENT '鐭ヨ瘑绫诲瀷',
+    scenario VARCHAR(255) COMMENT '閫傜敤鍦烘櫙',
+    efficiency VARCHAR(20) COMMENT '瑙e喅鏁堢巼',
+    problem TEXT COMMENT '闂鎻忚堪',
+    solution TEXT COMMENT '瑙e喅鏂规',
+    key_points TEXT COMMENT '鍏抽敭瑕佺偣',
+    creator VARCHAR(100) COMMENT '鍒涘缓浜�',
+    usage_count INT DEFAULT 0 COMMENT '浣跨敤娆℃暟',
+    file_count INT DEFAULT 0 COMMENT '鏂囦欢鏁伴噺',
+    total_chunk_count INT DEFAULT 0 COMMENT '鎬诲垏鐗囨暟閲�',
+    description VARCHAR(500) COMMENT '鐭ヨ瘑搴撴弿杩�',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    create_user INT,
+    update_user INT,
+    tenant_id BIGINT,
+    dept_id BIGINT
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐭ヨ瘑搴撹〃';
+```
+
+#### 3.1.2 鐭ヨ瘑搴撳悜閲忚褰曡〃锛坘nowledge_base_vector锛�
+
+```sql
+CREATE TABLE knowledge_base_vector (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭ID',
+    knowledge_base_id BIGINT NOT NULL COMMENT '鍏宠仈鐭ヨ瘑搴揑D',
+    storage_blob_id BIGINT NOT NULL COMMENT '鍏宠仈鏂囦欢blob ID',
+    file_name VARCHAR(255) NOT NULL COMMENT '鏂囦欢鍚嶇О',
+    file_type VARCHAR(50) NOT NULL COMMENT '鏂囦欢绫诲瀷',
+    vector_status TINYINT DEFAULT 0 COMMENT '鍚戦噺鍖栫姸鎬�: 0-寰呭鐞�, 1-澶勭悊涓�, 2-宸插畬鎴�, 3-澶辫触',
+    vector_error VARCHAR(500) COMMENT '鍚戦噺鍖栧け璐ュ師鍥�',
+    chunk_count INT DEFAULT 0 COMMENT '鍒囩墖鏁伴噺',
+    namespace VARCHAR(100) COMMENT '鍚戦噺鍛藉悕绌洪棿',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    create_user INT,
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    update_user INT,
+    tenant_id BIGINT,
+    dept_id BIGINT,
+    INDEX idx_knowledge_base_id (knowledge_base_id),
+    INDEX idx_storage_blob_id (storage_blob_id),
+    INDEX idx_vector_status (vector_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐭ヨ瘑搴撴枃浠跺悜閲忚褰曡〃';
+```
+
+### 3.2 Maven渚濊禆
+
+```xml
+<!-- langchain4j BOM -->
+<dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>dev.langchain4j</groupId>
+            <artifactId>langchain4j-bom</artifactId>
+            <version>1.0.0-beta3</version>
+            <type>pom</type>
+            <scope>import</scope>
+        </dependency>
+    </dependencies>
+</dependencyManagement>
+
+<dependencies>
+    <!-- langchain4j 鏍稿績 -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-spring-boot-starter</artifactId>
+    </dependency>
+
+    <!-- Pinecone 鍚戦噺鏁版嵁搴� -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-pinecone</artifactId>
+    </dependency>
+
+    <!-- 闃块噷浜� DashScope -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
+    </dependency>
+</dependencies>
+```
+
+### 3.3 閰嶇疆鏂囦欢锛坅pplication.yml锛�
+
+```yaml
+# Pinecone 鍚戦噺鏁版嵁搴撻厤缃�
+pinecone:
+  api-key: your-pinecone-api-key
+  index: your-index-name
+  namespace: knowledge-base
+
+# langchain4j 閰嶇疆
+langchain4j:
+  community:
+    dashscope:
+      streaming-chat-model:
+        api-key: your-dashscope-api-key
+        model-name: "qwen-max"
+      embedding-model:
+        api-key: your-dashscope-api-key
+        model-name: "text-embedding-v3"
+```
+
+### 3.4 鏍稿績浠g爜瀹炵幇
+
+#### 3.4.1 瀹炰綋绫�
+
+**KnowledgeBase.java**
+```java
+@Data
+@TableName("knowledge_base")
+public class KnowledgeBase implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String title;
+    private String type;
+    private String scenario;
+    private String efficiency;
+    private String problem;
+    private String solution;
+    private String keyPoints;
+    private String creator;
+    private Integer usageCount;
+    private Integer fileCount;
+    private Integer totalChunkCount;
+    private String description;
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
+```
+
+**KnowledgeBaseVector.java**
+```java
+@Data
+@TableName("knowledge_base_vector")
+public class KnowledgeBaseVector implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long knowledgeBaseId;
+    private Long storageBlobId;
+    private String fileName;
+    private String fileType;
+    private Integer vectorStatus;
+    private String vectorError;
+    private Integer chunkCount;
+    private String namespace;
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    // 鍚戦噺鍖栫姸鎬佸父閲�
+    public static final int STATUS_PENDING = 0;
+    public static final int STATUS_PROCESSING = 1;
+    public static final int STATUS_COMPLETED = 2;
+    public static final int STATUS_FAILED = 3;
+}
+```
+
+#### 3.4.2 EmbeddingStore閰嶇疆
+
+**EmbeddingStoreConfig.java**
+```java
+@Configuration
+public class EmbeddingStoreConfig {
+
+    @Value("${pinecone.api-key}")
+    private String pineconeApiKey;
+
+    @Value("${pinecone.index}")
+    private String indexName;
+
+    @Value("${pinecone.namespace}")
+    private String namespace;
+
+    @Bean
+    public Pinecone pinecone() {
+        return new Pinecone.Builder(pineconeApiKey).build();
+    }
+
+    @Bean
+    public Index pineconeIndex(Pinecone pinecone) {
+        return pinecone.getIndexConnection(indexName);
+    }
+
+    @Bean
+    public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) {
+        return PineconeEmbeddingStore.builder()
+                .apiKey(pineconeApiKey)
+                .index(indexName)
+                .nameSpace(namespace)
+                .createIndex(PineconeServerlessIndexConfig.builder()
+                        .cloud("AWS")
+                        .region("us-east-1")
+                        .dimension(embeddingModel.dimension())
+                        .build())
+                .build();
+    }
+}
+```
+
+#### 3.4.3 RAG鏈嶅姟瀹炵幇
+
+**KnowledgeRagService.java**
+```java
+public interface KnowledgeRagService {
+    void processVectorAsync(Long vectorId);
+    void processVector(Long vectorId);
+    List<String> searchRelevantContent(String namespace, String query, int maxResults);
+    void deleteEmbeddings(String namespace, Long storageBlobId);
+}
+```
+
+**KnowledgeRagServiceImpl.java**锛堟牳蹇冨疄鐜帮級
+```java
+@Slf4j
+@Service
+public class KnowledgeRagServiceImpl implements KnowledgeRagService {
+
+    private final KnowledgeBaseVectorService knowledgeBaseVectorService;
+    private final StorageBlobService storageBlobService;
+    private final EmbeddingModel embeddingModel;
+    private final EmbeddingStore<TextSegment> embeddingStore;
+    private final FileProperties fileProperties;
+    private final Index pineconeIndex;
+
+    @Value("${pinecone.namespace}")
+    private String namespace;
+
+    private static final int CHUNK_SIZE = 500;
+    private static final int CHUNK_OVERLAP = 100;
+    private static final long CHUNK_THRESHOLD_BYTES = 80L * 1024 * 1024;
+    private static final int EMBEDDING_MAX_LENGTH = 8000;
+
+    @Override
+    @Async("threadPoolTaskExecutor")
+    public void processVectorAsync(Long vectorId) {
+        processVector(vectorId);
+    }
+
+    @Override
+    public void processVector(Long vectorId) {
+        KnowledgeBaseVector vector = knowledgeBaseVectorService.getById(vectorId);
+        if (vector == null) return;
+
+        try {
+            // 鏇存柊鐘舵�佷负澶勭悊涓�
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_PROCESSING, null, null);
+
+            // 鑾峰彇鏂囦欢鍐呭
+            StorageBlob blob = storageBlobService.getById(vector.getStorageBlobId());
+            File file = getFile(blob);
+            String content = extractFileContent(file, vector.getFileName());
+
+            if (content == null || content.trim().isEmpty()) {
+                throw new RuntimeException("鏂囦欢鍐呭涓虹┖");
+            }
+
+            // 鏂囨湰鍒囩墖
+            List<TextSegment> chunks;
+            boolean needChunk = file.length() > CHUNK_THRESHOLD_BYTES || content.length() > EMBEDDING_MAX_LENGTH;
+            if (needChunk) {
+                chunks = splitText(content, vector);
+            } else {
+                Map<String, Object> metadata = buildMetadata(vector);
+                chunks = List.of(TextSegment.from(content, new Metadata(metadata)));
+            }
+
+            // 鐢熸垚宓屽叆鍚戦噺骞跺瓨鍌�
+            int chunkCount = 0;
+            for (TextSegment chunk : chunks) {
+                Embedding embedding = embeddingModel.embed(chunk).content();
+                embeddingStore.add(embedding, chunk);
+                chunkCount++;
+            }
+
+            // 鏇存柊鐘舵�佷负瀹屾垚
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_COMPLETED, chunkCount, null);
+
+        } catch (Exception e) {
+            log.error("鍚戦噺鍖栧鐞嗗け璐�", e);
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_FAILED, null, e.getMessage());
+        }
+    }
+
+    @Override
+    public List<String> searchRelevantContent(String namespace, String query, int maxResults) {
+        Embedding queryEmbedding = embeddingModel.embed(query).content();
+        EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
+                .queryEmbedding(queryEmbedding)
+                .maxResults(maxResults)
+                .minScore(0.7)
+                .build();
+        EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest);
+        return searchResult.matches().stream()
+                .map(match -> match.embedded().text())
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void deleteEmbeddings(String namespace, Long storageBlobId) {
+        Struct filter = Struct.newBuilder()
+                .putFields("storageBlobId", Value.newBuilder()
+                        .setStructValue(Struct.newBuilder()
+                                .putFields("$eq", Value.newBuilder()
+                                        .setNumberValue(storageBlobId.doubleValue())
+                                        .build()))
+                        .build())
+                .build();
+        pineconeIndex.delete(new ArrayList<>(), false, this.namespace, filter);
+    }
+
+    private String extractFileContent(File file, String fileName) throws Exception {
+        String ext = getFileExtension(fileName);
+        if (isPlainText(ext)) {
+            return readFileWithEncoding(file);
+        }
+        if ("docx".equals(ext)) {
+            return extractDocx(file);
+        }
+        if ("xlsx".equals(ext) || "xls".equals(ext)) {
+            return extractExcel(file);
+        }
+        return readFileWithEncoding(file);
+    }
+
+    // ... 鍏朵粬杈呭姪鏂规硶
+}
+```
+
+#### 3.4.4 鐭ヨ瘑搴撻棶绛擜gent
+
+**KnowledgeChatAgent.java**
+```java
+@AiService(
+        wiringMode = EXPLICIT,
+        streamingChatModel = "qwenStreamingChatModel",
+        chatMemoryProvider = "chatMemoryProvider"
+)
+public interface KnowledgeChatAgent {
+
+    @SystemMessage("""
+            浣犳槸浼佷笟鐭ヨ瘑搴撻棶绛斿姪鎵嬨��
+            浣犻渶瑕佸熀浜庢彁渚涚殑鐭ヨ瘑搴撳唴瀹瑰洖绛旂敤鎴烽棶棰樸��
+            閬靛惊浠ヤ笅瑙勫垯锛�
+            1. 涓ユ牸鍩轰簬鐭ヨ瘑搴撳唴瀹瑰洖绛旓紝涓嶈缂栭�犱俊鎭�
+            2. 濡傛灉鐭ヨ瘑搴撲腑娌℃湁鐩稿叧淇℃伅锛屾槑纭憡鐭ョ敤鎴�
+            3. 鍥炵瓟瑕佸噯纭�佺畝娲併�佹湁鏉$悊
+            4. 寮曠敤鏉ユ簮鏃舵敞鏄�"鏍规嵁鐭ヨ瘑搴撳唴瀹�"
+            """)
+    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
+}
+```
+
+#### 3.4.5 Controller灞�
+
+**KnowledgeBaseController.java**
+```java
+@RestController
+@RequestMapping("/knowledgeBase")
+@Tag(name = "鐭ヨ瘑搴撶鐞�")
+public class KnowledgeBaseController {
+
+    @GetMapping("/getList")
+    public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
+                              @RequestParam(defaultValue = "10") long size,
+                              KnowledgeBase knowledgeBase) {
+        Page page = new Page(current, size);
+        return AjaxResult.success(knowledgeBaseService.listpage(page, knowledgeBase));
+    }
+
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase) {
+        return AjaxResult.success(knowledgeBaseService.save(knowledgeBase));
+    }
+
+    @PostMapping("/update")
+    public AjaxResult update(@RequestBody KnowledgeBase knowledgeBase) {
+        return AjaxResult.success(knowledgeBaseService.updateById(knowledgeBase));
+    }
+
+    @DeleteMapping("/delete")
+    public AjaxResult delete(@RequestBody List<Long> ids) {
+        return AjaxResult.success(knowledgeBaseService.removeByIds(ids));
+    }
+
+    @GetMapping("/vector/status/{knowledgeBaseId}")
+    @Operation(summary = "鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��")
+    public AjaxResult getVectorStatus(@PathVariable Long knowledgeBaseId) {
+        return AjaxResult.success(knowledgeBaseVectorService.getVectorStatusByKnowledgeBaseId(knowledgeBaseId));
+    }
+
+    @PostMapping("/vector/reprocess/{vectorId}")
+    @Operation(summary = "閲嶆柊鍚戦噺鍖栨枃浠�")
+    public AjaxResult reprocessVector(@PathVariable Long vectorId) {
+        knowledgeBaseVectorService.reprocessVector(vectorId);
+        return AjaxResult.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+    }
+
+    @PostMapping("/file/save")
+    @Operation(summary = "淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�")
+    public AjaxResult saveKnowledgeBaseFiles(@RequestBody KnowledgeBaseFileDTO dto) {
+        // 淇濆瓨闄勪欢鍏宠仈骞惰Е鍙戝悜閲忓寲
+        // ...
+    }
+
+    @DeleteMapping("/file/delete")
+    @Operation(summary = "鍒犻櫎鐭ヨ瘑搴撴枃浠�")
+    public AjaxResult deleteKnowledgeBaseFiles(@RequestBody List<Long> vectorIds) {
+        knowledgeBaseVectorService.deleteVectors(vectorIds);
+        return AjaxResult.success();
+    }
+}
+```
+
+**KnowledgeChatController.java**
+```java
+@RestController
+@RequestMapping("/ai/knowledge")
+@Tag(name = "鐭ヨ瘑搴撻棶绛�")
+public class KnowledgeChatController {
+
+    private final KnowledgeChatAgent knowledgeChatAgent;
+    private final KnowledgeRagService knowledgeRagService;
+    private final KnowledgeBaseService knowledgeBaseService;
+
+    @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
+    @Operation(summary = "鐭ヨ瘑搴撻棶绛�")
+    public Flux<String> chat(@RequestBody KnowledgeChatRequest request) {
+        // 妫�绱㈢浉鍏冲唴瀹�
+        String namespace = "kb-" + request.getKnowledgeBaseId();
+        List<String> relevantContents = knowledgeRagService.searchRelevantContent(
+                namespace, request.getQuestion(), 5);
+
+        if (relevantContents.isEmpty()) {
+            return Flux.just("鐭ヨ瘑搴撲腑鏈壘鍒扮浉鍏冲唴瀹�");
+        }
+
+        // 鏋勫缓涓婁笅鏂�
+        StringBuilder context = new StringBuilder();
+        context.append("浠ヤ笅鏄粠鐭ヨ瘑搴撲腑妫�绱㈠埌鐨勭浉鍏冲唴瀹癸細\n\n");
+        for (int i = 0; i < relevantContents.size(); i++) {
+            context.append("銆愬唴瀹�").append(i + 1).append("銆慭n");
+            context.append(relevantContents.get(i)).append("\n\n");
+        }
+        context.append("---\n璇峰熀浜庝互涓婄煡璇嗗簱鍐呭鍥炵瓟锛歕n").append(request.getQuestion());
+
+        return knowledgeChatAgent.chat(request.getMemoryId(), context.toString());
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鐭ヨ瘑搴撳垪琛�")
+    public AjaxResult listKnowledgeBases() {
+        return AjaxResult.success(knowledgeBaseService.list());
+    }
+}
+```
+
+---
+
+## 鍥涖�丄PI鎺ュ彛鏂囨。
+
+### 4.1 鐭ヨ瘑搴撶鐞嗘帴鍙�
+
+| 鎺ュ彛 | 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| 鑾峰彇鍒楄〃 | GET | /knowledgeBase/getList | 鍒嗛〉鏌ヨ鐭ヨ瘑搴撳垪琛� |
+| 鏂板鐭ヨ瘑搴� | POST | /knowledgeBase/add | 鍒涘缓鐭ヨ瘑搴� |
+| 鏇存柊鐭ヨ瘑搴� | POST | /knowledgeBase/update | 鏇存柊鐭ヨ瘑搴撲俊鎭� |
+| 鍒犻櫎鐭ヨ瘑搴� | DELETE | /knowledgeBase/delete | 鎵归噺鍒犻櫎鐭ヨ瘑搴� |
+| 鏌ヨ鍚戦噺鍖栫姸鎬� | GET | /knowledgeBase/vector/status/{id} | 鏌ヨ鏂囦欢鍚戦噺鍖栫姸鎬� |
+| 閲嶆柊鍚戦噺鍖� | POST | /knowledgeBase/vector/reprocess/{id} | 閲嶆柊澶勭悊澶辫触鐨勬枃浠� |
+| 淇濆瓨鏂囦欢鍏宠仈 | POST | /knowledgeBase/file/save | 涓婁紶鏂囦欢鍚庡叧鑱斿埌鐭ヨ瘑搴� |
+| 鍒犻櫎鏂囦欢 | DELETE | /knowledgeBase/file/delete | 鍒犻櫎鐭ヨ瘑搴撴枃浠� |
+
+### 4.2 鐭ヨ瘑搴撻棶绛旀帴鍙�
+
+| 鎺ュ彛 | 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| 鐭ヨ瘑搴撻棶绛� | POST | /ai/knowledge/chat | 娴佸紡杩斿洖闂瓟缁撴灉 |
+| 鐭ヨ瘑搴撳垪琛� | GET | /ai/knowledge/list | 鑾峰彇鍙�夌煡璇嗗簱鍒楄〃 |
+
+### 4.3 鎺ュ彛璇︾粏璇存槑
+
+#### 4.3.1 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�
+
+**璇锋眰**
+```json
+POST /knowledgeBase/file/save
+{
+    "knowledgeBaseId": 1,
+    "storageBlobIds": [100, 101, 102]
+}
+```
+
+**鍝嶅簲**
+```json
+{
+    "code": 200,
+    "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 4.3.2 鐭ヨ瘑搴撻棶绛�
+
+**璇锋眰**
+```json
+POST /ai/knowledge/chat
+Content-Type: application/json
+
+{
+    "knowledgeBaseId": 1,
+    "memoryId": "session-uuid",
+    "question": "濡備綍澶勭悊搴撳瓨鐩樼偣宸紓锛�"
+}
+```
+
+**鍝嶅簲**锛圫SE娴佸紡锛�
+```
+鏍规嵁鐭ヨ瘑搴撳唴瀹癸紝搴撳瓨鐩樼偣宸紓鐨勫鐞嗘祦绋嬪涓嬶細
+
+1. 鍙戠幇宸紓鍚庯紝棣栧厛鏍稿鐩樼偣璁板綍...
+2. 妫�鏌ユ槸鍚︽湁婕忕洏鎴栭敊鐩�...
+3. ...
+```
+
+---
+
+## 浜斻�佸墠绔疄鐜�
+
+### 5.1 鐭ヨ瘑搴撶鐞嗛〉闈�
+
+```vue
+<template>
+  <div class="knowledge-base">
+    <!-- 鍒楄〃 -->
+    <el-table :data="tableData" border>
+      <el-table-column prop="title" label="鐭ヨ瘑鏍囬" />
+      <el-table-column prop="type" label="鐭ヨ瘑绫诲瀷" />
+      <el-table-column prop="fileCount" label="鏂囦欢鏁伴噺" />
+      <el-table-column prop="totalChunkCount" label="鍒囩墖鏁伴噺" />
+      <el-table-column label="鎿嶄綔">
+        <template #default="{ row }">
+          <el-button @click="handleEdit(row)">缂栬緫</el-button>
+          <el-button @click="handleFiles(row)">鏂囦欢绠$悊</el-button>
+          <el-button @click="handleChat(row)">闂瓟</el-button>
+          <el-button type="danger" @click="handleDelete(row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getKnowledgeBaseList, deleteKnowledgeBase } from '@/api/knowledge'
+
+const tableData = ref([])
+
+const loadData = async () => {
+  const res = await getKnowledgeBaseList({ current: 1, size: 10 })
+  tableData.value = res.data.records
+}
+
+onMounted(loadData)
+</script>
+```
+
+### 5.2 鏂囦欢涓婁紶涓庡悜閲忓寲鐘舵��
+
+```vue
+<template>
+  <div class="file-manager">
+    <!-- 鏂囦欢涓婁紶 -->
+    <el-upload
+      :action="uploadUrl"
+      :on-success="handleUploadSuccess"
+      multiple
+    >
+      <el-button type="primary">涓婁紶鏂囦欢</el-button>
+    </el-upload>
+
+    <!-- 鏂囦欢鍒楄〃涓庡悜閲忓寲鐘舵�� -->
+    <el-table :data="fileList">
+      <el-table-column prop="fileName" label="鏂囦欢鍚�" />
+      <el-table-column label="鍚戦噺鍖栫姸鎬�">
+        <template #default="{ row }">
+          <el-tag :type="getStatusType(row.vectorStatus)">
+            {{ getStatusText(row.vectorStatus) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="chunkCount" label="鍒囩墖鏁�" />
+      <el-table-column label="鎿嶄綔">
+        <template #default="{ row }">
+          <el-button v-if="row.vectorStatus === 3" @click="reprocess(row)">
+            閲嶆柊澶勭悊
+          </el-button>
+          <el-button type="danger" @click="deleteFile(row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload'
+
+// 涓婁紶鎴愬姛鍚庝繚瀛樺叧鑱�
+const uploadedBlobIds = ref([])
+
+const handleUploadSuccess = (response, file) => {
+  if (response.code === 200) {
+    uploadedBlobIds.value.push(response.data.id)
+  }
+}
+
+// 淇濆瓨鏂囦欢鍏宠仈
+const saveFiles = async () => {
+  await saveKnowledgeBaseFiles({
+    knowledgeBaseId: props.knowledgeBaseId,
+    storageBlobIds: uploadedBlobIds.value
+  })
+  // 鍒锋柊鏂囦欢鍒楄〃
+  loadFileList()
+}
+
+// 鐘舵�佹枃鏈槧灏�
+const getStatusText = (status) => {
+  const map = {
+    0: '寰呭鐞�',
+    1: '澶勭悊涓�',
+    2: '宸插畬鎴�',
+    3: '澶辫触'
+  }
+  return map[status] || '鏈煡'
+}
+
+const getStatusType = (status) => {
+  const map = {
+    0: 'info',
+    1: 'warning',
+    2: 'success',
+    3: 'danger'
+  }
+  return map[status] || 'info'
+}
+</script>
+```
+
+### 5.3 鐭ヨ瘑搴撻棶绛旂晫闈�
+
+```vue
+<template>
+  <div class="knowledge-chat">
+    <!-- 鐭ヨ瘑搴撻�夋嫨 -->
+    <el-select v-model="selectedKbId" placeholder="閫夋嫨鐭ヨ瘑搴�">
+      <el-option
+        v-for="kb in knowledgeBases"
+        :key="kb.id"
+        :label="kb.title"
+        :value="kb.id"
+      />
+    </el-select>
+
+    <!-- 瀵硅瘽鍖哄煙 -->
+    <div class="chat-messages">
+      <div
+        v-for="(msg, index) in messages"
+        :key="index"
+        :class="['message', msg.role]"
+      >
+        <div class="content">{{ msg.content }}</div>
+      </div>
+    </div>
+
+    <!-- 杈撳叆妗� -->
+    <el-input
+      v-model="inputQuestion"
+      placeholder="璇疯緭鍏ラ棶棰�"
+      @keyup.enter="sendMessage"
+    >
+      <template #append>
+        <el-button @click="sendMessage" :loading="loading">鍙戦��</el-button>
+      </template>
+    </el-input>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getKnowledgeBaseList, knowledgeChat } from '@/api/knowledge'
+
+const knowledgeBases = ref([])
+const selectedKbId = ref(null)
+const messages = ref([])
+const inputQuestion = ref('')
+const loading = ref(false)
+const memoryId = ref(crypto.randomUUID())
+
+const sendMessage = async () => {
+  if (!inputQuestion.value.trim()) return
+  if (!selectedKbId.value) {
+    ElMessage.warning('璇烽�夋嫨鐭ヨ瘑搴�')
+    return
+  }
+
+  // 娣诲姞鐢ㄦ埛娑堟伅
+  messages.value.push({
+    role: 'user',
+    content: inputQuestion.value
+  })
+
+  loading.value = true
+
+  try {
+    // 娴佸紡璇锋眰
+    const response = await fetch('/api/ai/knowledge/chat', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        knowledgeBaseId: selectedKbId.value,
+        memoryId: memoryId.value,
+        question: inputQuestion.value
+      })
+    })
+
+    // 澶勭悊SSE娴佸紡鍝嶅簲
+    const reader = response.body.getReader()
+    const decoder = new TextDecoder()
+    let aiContent = ''
+
+    messages.value.push({ role: 'assistant', content: '' })
+
+    while (true) {
+      const { done, value } = await reader.read()
+      if (done) break
+
+      const text = decoder.decode(value)
+      aiContent += text
+      messages.value[messages.value.length - 1].content = aiContent
+    }
+  } finally {
+    loading.value = false
+    inputQuestion.value = ''
+  }
+}
+
+onMounted(async () => {
+  const res = await getKnowledgeBaseList()
+  knowledgeBases.value = res.data
+})
+</script>
+```
+
+### 5.4 API灏佽
+
+```javascript
+// api/knowledge.js
+import request from '@/utils/request'
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛�
+export function getKnowledgeBaseList(params) {
+  return request({
+    url: '/knowledgeBase/getList',
+    method: 'get',
+    params
+  })
+}
+
+// 鏂板鐭ヨ瘑搴�
+export function addKnowledgeBase(data) {
+  return request({
+    url: '/knowledgeBase/add',
+    method: 'post',
+    data
+  })
+}
+
+// 鏇存柊鐭ヨ瘑搴�
+export function updateKnowledgeBase(data) {
+  return request({
+    url: '/knowledgeBase/update',
+    method: 'post',
+    data
+  })
+}
+
+// 鍒犻櫎鐭ヨ瘑搴�
+export function deleteKnowledgeBase(ids) {
+  return request({
+    url: '/knowledgeBase/delete',
+    method: 'delete',
+    data: ids
+  })
+}
+
+// 鑾峰彇鏂囦欢鍚戦噺鍖栫姸鎬�
+export function getVectorStatus(knowledgeBaseId) {
+  return request({
+    url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
+    method: 'get'
+  })
+}
+
+// 閲嶆柊鍚戦噺鍖�
+export function reprocessVector(vectorId) {
+  return request({
+    url: `/knowledgeBase/vector/reprocess/${vectorId}`,
+    method: 'post'
+  })
+}
+
+// 淇濆瓨鏂囦欢鍏宠仈
+export function saveKnowledgeBaseFiles(data) {
+  return request({
+    url: '/knowledgeBase/file/save',
+    method: 'post',
+    data
+  })
+}
+
+// 鍒犻櫎鏂囦欢
+export function deleteKnowledgeBaseFiles(vectorIds) {
+  return request({
+    url: '/knowledgeBase/file/delete',
+    method: 'delete',
+    data: vectorIds
+  })
+}
+
+// 鐭ヨ瘑搴撻棶绛旓紙娴佸紡锛�
+export async function knowledgeChat(data) {
+  const response = await fetch('/api/ai/knowledge/chat', {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(data)
+  })
+  return response.body
+}
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛紙闂瓟鐢級
+export function getKnowledgeBaseListForChat() {
+  return request({
+    url: '/ai/knowledge/list',
+    method: 'get'
+  })
+}
+```
+
+---
+
+## 鍏�佹牳蹇冩祦绋�
+
+### 6.1 鏂囦欢涓婁紶涓庡悜閲忓寲娴佺▼
+
+```
+1. 鍓嶇璋冪敤 /common/upload 涓婁紶鏂囦欢 鈫� 杩斿洖 storageBlobId
+2. 鍓嶇璋冪敤 /knowledgeBase/file/save 鍏宠仈鏂囦欢鍒扮煡璇嗗簱
+3. 鍚庣鍒涘缓 KnowledgeBaseVector 璁板綍锛堢姸鎬侊細寰呭鐞嗭級
+4. 鍚庣寮傛璋冪敤 KnowledgeRagService.processVectorAsync()
+   鈹溾攢鈹� 鏇存柊鐘舵�佷负"澶勭悊涓�"
+   鈹溾攢鈹� 鎻愬彇鏂囦欢鍐呭锛堟敮鎸佸绉嶆牸寮忥級
+   鈹溾攢鈹� 鑷姩妫�娴嬫枃浠剁紪鐮侊紙UTF-8/GBK锛�
+   鈹溾攢鈹� 鏂囨湰鍒囩墖锛堝ぇ鏂囦欢鎴栭暱鍐呭鎵嶅垏鐗囷級
+   鈹溾攢鈹� 鐢熸垚 Embedding 鍚戦噺
+   鈹溾攢鈹� 瀛樺偍鍒� Pinecone
+   鈹斺攢鈹� 鏇存柊鐘舵�佷负"瀹屾垚"鎴�"澶辫触"
+```
+
+### 6.2 鐭ヨ瘑搴撻棶绛旀祦绋�
+
+```
+1. 鐢ㄦ埛閫夋嫨鐭ヨ瘑搴擄紝杈撳叆闂
+2. 鍓嶇璋冪敤 /ai/knowledge/chat锛堟祦寮忔帴鍙o級
+3. 鍚庣澶勭悊锛�
+   鈹溾攢鈹� 鏋勫缓鍛藉悕绌洪棿锛歬b-{knowledgeBaseId}
+   鈹溾攢鈹� 璋冪敤 Embedding 妯″瀷鐢熸垚闂鍚戦噺
+   鈹溾攢鈹� 浠� Pinecone 妫�绱㈢浉鍏冲唴瀹癸紙minScore=0.7, maxResults=5锛�
+   鈹溾攢鈹� 鏋勫缓涓婁笅鏂� Prompt
+   鈹溾攢鈹� 璋冪敤 LLM 鐢熸垚鍥炵瓟
+   鈹斺攢鈹� 娴佸紡杩斿洖缁撴灉
+```
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+1. **Pinecone 鍛藉悕绌洪棿**锛氫笉鑳戒娇鐢� `__default__`锛屽繀椤讳娇鐢ㄨ嚜瀹氫箟鍛藉悕绌洪棿
+2. **鏂囦欢缂栫爜**锛氳嚜鍔ㄦ娴� UTF-8/GBK锛岄伩鍏嶄贡鐮�
+3. **鍒囩墖绛栫暐**锛�
+   - 鏂囦欢 > 80MB 鎴栧唴瀹� > 8000 瀛楃鏃舵墠鍒囩墖
+   - 鍒囩墖澶у皬 500 瀛楃锛岄噸鍙� 100 瀛楃
+   - 浼樺厛鍦ㄥ彞瀛愯竟鐣屽垏鍒�
+4. **Embedding 闄愬埗**锛氶樋閲屼簯 DashScope 闄愬埗鍗曟杈撳叆鏈�澶� 8192 瀛楃
+5. **鍚戦噺鍒犻櫎**锛氫娇鐢� Pinecone 鍘熺敓瀹㈡埛绔紝閫氳繃 metadata filter 鍒犻櫎
+6. **寮傛澶勭悊**锛氬悜閲忓寲浣跨敤 `@Async` 寮傛鎵ц锛岄伩鍏嶉樆濉炴帴鍙�
+
+---
+
+## 鍏�佹枃浠舵竻鍗�
+
+### 鍚庣鏂囦欢
+```
+src/main/java/com/ruoyi/
+鈹溾攢鈹� approve/
+鈹�   鈹溾攢鈹� controller/
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseController.java
+鈹�   鈹溾攢鈹� pojo/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBase.java
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseVector.java
+鈹�   鈹溾攢鈹� service/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseService.java
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseVectorService.java
+鈹�   鈹�   鈹斺攢鈹� impl/
+鈹�   鈹�       鈹溾攢鈹� KnowledgeBaseServiceImpl.java
+鈹�   鈹�       鈹斺攢鈹� KnowledgeBaseVectorServiceImpl.java
+鈹�   鈹溾攢鈹� mapper/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseMapper.java
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseVectorMapper.java
+鈹�   鈹斺攢鈹� dto/
+鈹�       鈹斺攢鈹� KnowledgeBaseVectorVO.java
+鈹斺攢鈹� ai/
+    鈹溾攢鈹� config/
+    鈹�   鈹溾攢鈹� EmbeddingStoreConfig.java
+    鈹�   鈹斺攢鈹� XiaozhiAgentConfig.java
+    鈹溾攢鈹� controller/
+    鈹�   鈹斺攢鈹� KnowledgeChatController.java
+    鈹溾攢鈹� assistant/
+    鈹�   鈹斺攢鈹� KnowledgeChatAgent.java
+    鈹溾攢鈹� service/
+    鈹�   鈹溾攢鈹� KnowledgeRagService.java
+    鈹�   鈹斺攢鈹� impl/
+    鈹�       鈹斺攢鈹� KnowledgeRagServiceImpl.java
+    鈹斺攢鈹� dto/
+        鈹斺攢鈹� KnowledgeChatRequest.java
+```
+
+### 鍓嶇鏂囦欢
+```
+src/views/knowledge/
+鈹溾攢鈹� index.vue              # 鐭ヨ瘑搴撳垪琛�
+鈹溾攢鈹� form.vue               # 鏂板/缂栬緫琛ㄥ崟
+鈹溾攢鈹� files.vue              # 鏂囦欢绠$悊
+鈹斺攢鈹� chat.vue               # 鐭ヨ瘑搴撻棶绛�
+
+src/api/knowledge.js       # API灏佽
+```
\ No newline at end of file
diff --git "a/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\344\274\240\345\217\202\346\226\271\345\274\217\345\222\214\345\217\202\346\225\260\345\221\275\345\220\215\350\247\204\350\214\203\346\226\207\346\241\243.md" "b/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\344\274\240\345\217\202\346\226\271\345\274\217\345\222\214\345\217\202\346\225\260\345\221\275\345\220\215\350\247\204\350\214\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..e962ac2
--- /dev/null
+++ "b/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\344\274\240\345\217\202\346\226\271\345\274\217\345\222\214\345\217\202\346\225\260\345\221\275\345\220\215\350\247\204\350\214\203\346\226\207\346\241\243.md"
@@ -0,0 +1,941 @@
+# 鐭ヨ瘑搴撴ā鍧椾紶鍙傛柟寮忓拰鍙傛暟鍛藉悕瑙勮寖鏂囨。
+
+## 涓�銆佹杩�
+
+鏈枃妗h缁嗚鏄庣煡璇嗗簱妯″潡涓墍鏈夋帴鍙c�佺粍浠躲�佹柟娉曠殑浼犲弬鏂瑰紡鍜屽弬鏁板懡鍚嶈鑼�,鏃ㄥ湪:
+- 缁熶竴鍓嶅悗绔弬鏁板懡鍚嶈鑼�
+- 鏄庣‘鍙傛暟绫诲瀷鍜屽繀濉��
+- 瑙勮寖浼犲弬鏂瑰紡(GET params銆丳OST body銆丏ELETE data)
+- 鎻愪緵娓呮櫚鐨勫弬鏁版槧灏勫叧绯�
+
+---
+
+## 浜屻�佸弬鏁板懡鍚嶈鑼�
+
+### 2.1 鍩烘湰瑙勮寖
+
+#### 鍛藉悕椋庢牸
+- **鍚庣鍙傛暟**: 閬靛惊 Java 椹煎嘲鍛藉悕娉� (camelCase)
+- **鍓嶇鍙傛暟**: 閬靛惊 JavaScript 椹煎嘲鍛藉悕娉� (camelCase)
+- **鏁版嵁搴撳瓧娈�**: 閬靛惊 MySQL 涓嬪垝绾垮懡鍚嶆硶 (snake_case)
+- **鎺ュ彛URL**: 閬靛惊 RESTful 椋庢牸,浣跨敤灏忓啓鍜岃繛瀛楃
+
+#### 鍛藉悕绾﹀畾
+1. **ID鐩稿叧**: 缁熶竴浣跨敤 `id`銆乣Id` 鍚庣紑
+   - `knowledgeBaseId` - 鐭ヨ瘑搴揑D
+   - `storageBlobId` - 鏂囦欢blob ID
+   - `vectorId` - 鍚戦噺璁板綍ID
+   - `memoryId` - 浼氳瘽ID
+
+2. **鍒楄〃鐩稿叧**: 缁熶竴浣跨敤 `Ids` 鍚庣紑鎴栨暟缁勭被鍨�
+   - `storageBlobIds` - 鏂囦欢blob ID鍒楄〃
+   - `ids` - 閫氱敤ID鍒楄〃
+
+3. **鐘舵�佺浉鍏�**: 缁熶竴浣跨敤 `Status` 鍚庣紑
+   - `vectorStatus` - 鍚戦噺鍖栫姸鎬�
+
+4. **鏁伴噺鐩稿叧**: 缁熶竴浣跨敤 `Count` 鍚庣紑
+   - `fileCount` - 鏂囦欢鏁伴噺
+   - `chunkCount` - 鍒囩墖鏁伴噺
+   - `totalChunkCount` - 鎬诲垏鐗囨暟閲�
+   - `usageCount` - 浣跨敤娆℃暟
+
+5. **鏃堕棿鐩稿叧**: 缁熶竴浣跨敤 `Time` 鍚庣紑
+   - `createTime` - 鍒涘缓鏃堕棿
+   - `updateTime` - 鏇存柊鏃堕棿
+
+---
+
+## 涓夈�佹帴鍙d紶鍙傛柟寮忚鑼�
+
+### 3.1 GET 璇锋眰 - 浣跨敤 params
+
+**閫傜敤鍦烘櫙**: 鏌ヨ銆佸垪琛ㄣ�佸垎椤电瓑鑾峰彇鏁版嵁鐨勬帴鍙�
+
+**浼犲弬鏂瑰紡**: 閫氳繃 URL 鍙傛暟浼犻��,浣跨敤 `params`
+
+**绀轰緥**:
+```javascript
+// 鏌ヨ鐭ヨ瘑搴撳垪琛�
+export function listKnowledgeBase(query) {
+  return request({
+    url: "/knowledgeBase/getList",
+    method: "get",
+    params: query,  // 鉁� GET璇锋眰浣跨敤 params
+  });
+}
+
+// 瀹為檯璋冪敤
+listKnowledgeBase({
+  current: 1,      // 褰撳墠椤电爜
+  size: 20,        // 姣忛〉鏉℃暟
+  title: "",       // 鐭ヨ瘑鏍囬(鍙��)
+  type: ""         // 鐭ヨ瘑绫诲瀷(鍙��)
+});
+```
+
+**URL鏍煎紡**: `/knowledgeBase/getList?current=1&size=20&title=&type=`
+
+**瑙勮寖瑕佺偣**:
+- 鉁� 鏌ヨ鍙傛暟缁熶竴鏀惧湪 `params` 涓�
+- 鉁� 鍒嗛〉鍙傛暟鍛藉悕: `current` (褰撳墠椤�)銆乣size` (姣忛〉鏉℃暟)
+- 鉁� 鎼滅储鍙傛暟鍛藉悕: 涓庡疄浣撳瓧娈典繚鎸佷竴鑷�
+- 鉁� 璺緞鍙傛暟浣跨敤 URL 鍗犱綅绗�: `/path/{id}`
+
+---
+
+### 3.2 POST 璇锋眰 - 浣跨敤 data
+
+**閫傜敤鍦烘櫙**: 鏂板銆佹洿鏂般�佷繚瀛樼瓑鎻愪氦鏁版嵁鐨勬帴鍙�
+
+**浼犲弬鏂瑰紡**: 閫氳繃璇锋眰浣撲紶閫�,浣跨敤 `data`
+
+**绀轰緥**:
+```javascript
+// 鏂板鐭ヨ瘑搴�
+export function addKnowledgeBase(data) {
+  return request({
+    url: "/knowledgeBase/add",
+    method: "post",
+    data: data,  // 鉁� POST璇锋眰浣跨敤 data
+  });
+}
+
+// 瀹為檯璋冪敤
+addKnowledgeBase({
+  title: "鎿嶄綔鎵嬪唽",
+  type: "guide",
+  scenario: "绯荤粺鎿嶄綔鎸囧",
+  efficiency: "high",
+  problem: "鐢ㄦ埛涓嶄細鎿嶄綔绯荤粺",
+  solution: "鎸夌収鎿嶄綔鎵嬪唽鎵ц...",
+  keyPoints: "姝ラ1,姝ラ2,姝ラ3",
+  creator: "寮犱笁",
+  usageCount: 0
+});
+```
+
+**璇锋眰浣撴牸寮�**: JSON 鏍煎紡,`Content-Type: application/json`
+
+**瑙勮寖瑕佺偣**:
+- 鉁� 鎻愪氦鏁版嵁缁熶竴鏀惧湪 `data` 涓�
+- 鉁� 鍙傛暟鍚嶄笌鍚庣瀹炰綋瀛楁淇濇寔涓�鑷�
+- 鉁� 蹇呭~鍙傛暟闇�瑕佸湪琛ㄥ崟楠岃瘉瑙勫垯涓0鏄�
+- 鉁� 鏁板�肩被鍨嬪弬鏁伴渶鎸囧畾榛樿鍊�
+
+---
+
+### 3.3 DELETE 璇锋眰 - 浣跨敤 data
+
+**閫傜敤鍦烘櫙**: 鍒犻櫎銆佹壒閲忓垹闄ょ瓑鎿嶄綔
+
+**浼犲弬鏂瑰紡**: 閫氳繃璇锋眰浣撲紶閫掓暟缁勬垨瀵硅薄,浣跨敤 `data`
+
+**绀轰緥**:
+```javascript
+// 鍒犻櫎鐭ヨ瘑搴�
+export function delKnowledgeBase(query) {
+  return request({
+    url: "/knowledgeBase/delete",
+    method: "delete",
+    data: query,  // 鉁� DELETE璇锋眰浣跨敤 data 浼犻�掓暟缁�
+  });
+}
+
+// 瀹為檯璋冪敤(鎵归噺鍒犻櫎)
+delKnowledgeBase([1, 2, 3]);  // 鉁� 鐩存帴浼犻�扞D鏁扮粍
+```
+
+**璇锋眰浣撴牸寮�**: JSON 鏁扮粍 `[1, 2, 3]`
+
+**瑙勮寖瑕佺偣**:
+- 鉁� DELETE璇锋眰鐨勫弬鏁版斁鍦� `data` 涓�
+- 鉁� 鎵归噺鍒犻櫎浼犻�扞D鏁扮粍
+- 鉁� 鍗曚釜鍒犻櫎涔熷彲浠ヤ紶閫掓暟缁� `[id]`
+- 鈿狅笍 涓嶈浣跨敤 `params` 浼犻�掑垹闄ゅ弬鏁�
+
+---
+
+### 3.4 娴佸紡璇锋眰 - 浣跨敤 Fetch API
+
+**閫傜敤鍦烘櫙**: AI闂瓟銆佹祦寮忚緭鍑虹瓑闇�瑕佸疄鏃跺搷搴旂殑鎺ュ彛
+
+**浼犲弬鏂瑰紡**: 浣跨敤鍘熺敓 Fetch API,涓嶆敮鎸� axios
+
+**绀轰緥**:
+```javascript
+// 鐭ヨ瘑搴撻棶绛�(娴佸紡)
+export function knowledgeChat(data) {
+  const token = getToken();
+  return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      'Authorization': 'Bearer ' + token
+    },
+    body: JSON.stringify(data)  // 鉁� 浣跨敤 body 浼犻�掑弬鏁�
+  });
+}
+
+// 瀹為檯璋冪敤
+knowledgeChat({
+  knowledgeBaseId: 10,
+  memoryId: "session-xxx",
+  question: "濡備綍鎿嶄綔瀹℃壒娴佺▼?"
+});
+```
+
+**瑙勮寖瑕佺偣**:
+- 鉁� 娴佸紡鎺ュ彛蹇呴』浣跨敤 Fetch API
+- 鉁� 鍙傛暟浣跨敤 `JSON.stringify()` 搴忓垪鍖�
+- 鉁� 蹇呴』鎼哄甫 Authorization header
+- 鈿狅笍 axios 涓嶆敮鎸佹祦寮忓搷搴�,涓嶈浣跨敤
+
+---
+
+## 鍥涖�佸畬鏁村弬鏁板鐓ц〃
+
+### 4.1 鐭ヨ瘑搴撶鐞嗘帴鍙e弬鏁�
+
+#### 鏌ヨ鍒楄〃 (`GET /knowledgeBase/getList`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| current | Integer | 鏄� | params | 褰撳墠椤电爜 | 1 |
+| size | Integer | 鏄� | params | 姣忛〉鏉℃暟 | 20 |
+| title | String | 鍚� | params | 鐭ヨ瘑鏍囬(妯$硦鎼滅储) | "鎿嶄綔" |
+| type | String | 鍚� | params | 鐭ヨ瘑绫诲瀷(绮剧‘鍖归厤) | "guide" |
+
+**鍓嶇璋冪敤**:
+```javascript
+listKnowledgeBase({
+  current: 1,
+  size: 20,
+  title: "",
+  type: ""
+});
+```
+
+---
+
+#### 鏂板鐭ヨ瘑搴� (`POST /knowledgeBase/add`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| title | String | 鏄� | data | 鐭ヨ瘑鏍囬 | "鎿嶄綔鎵嬪唽" |
+| type | String | 鏄� | data | 鐭ヨ瘑绫诲瀷 | "guide" |
+| scenario | String | 鍚� | data | 閫傜敤鍦烘櫙 | "绯荤粺鎿嶄綔" |
+| efficiency | String | 鍚� | data | 瑙e喅鏁堢巼 | "high" |
+| problem | String | 鏄� | data | 闂鎻忚堪 | "鐢ㄦ埛涓嶄細鎿嶄綔" |
+| solution | String | 鏄� | data | 瑙e喅鏂规 | "鍙傝�冩墜鍐�" |
+| keyPoints | String | 鍚� | data | 鍏抽敭瑕佺偣 | "姝ラ1,姝ラ2" |
+| creator | String | 鍚� | data | 鍒涘缓浜� | "寮犱笁" |
+| usageCount | Integer | 鍚� | data | 浣跨敤娆℃暟 | 0 |
+
+**鍓嶇璋冪敤**:
+```javascript
+addKnowledgeBase({
+  title: "鎿嶄綔鎵嬪唽",
+  type: "guide",
+  scenario: "绯荤粺鎿嶄綔鎸囧",
+  efficiency: "high",
+  problem: "鐢ㄦ埛涓嶄細鎿嶄綔绯荤粺",
+  solution: "鎸夌収鎿嶄綔鎵嬪唽鎵ц...",
+  keyPoints: "姝ラ1,姝ラ2,姝ラ3",
+  creator: "寮犱笁",
+  usageCount: 0
+});
+```
+
+---
+
+#### 鏇存柊鐭ヨ瘑搴� (`POST /knowledgeBase/update`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| id | Long | 鏄� | data | 鐭ヨ瘑搴揑D | 10 |
+| *(鍏朵粬鍙傛暟鍚屾柊澧�)* | - | - | data | - | - |
+
+**鍓嶇璋冪敤**:
+```javascript
+updateKnowledgeBase({
+  id: 10,
+  title: "鎿嶄綔鎵嬪唽(鏇存柊)",
+  type: "guide",
+  // ...鍏朵粬鍙傛暟
+});
+```
+
+---
+
+#### 鍒犻櫎鐭ヨ瘑搴� (`DELETE /knowledgeBase/delete`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| ids | Long[] | 鏄� | data | 鐭ヨ瘑搴揑D鏁扮粍 | [1, 2, 3] |
+
+**鍓嶇璋冪敤**:
+```javascript
+delKnowledgeBase([1, 2, 3]);
+```
+
+---
+
+### 4.2 鏂囦欢绠$悊鎺ュ彛鍙傛暟
+
+#### 鏌ヨ鍚戦噺鍖栫姸鎬� (`GET /knowledgeBase/vector/status/{knowledgeBaseId}`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| knowledgeBaseId | Long | 鏄� | URL璺緞 | 鐭ヨ瘑搴揑D | 10 |
+
+**鍓嶇璋冪敤**:
+```javascript
+getVectorStatus(10);
+```
+
+**URL**: `/knowledgeBase/vector/status/10`
+
+---
+
+#### 淇濆瓨鏂囦欢鍏宠仈 (`POST /knowledgeBase/file/save`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| knowledgeBaseId | Long | 鏄� | data | 鐭ヨ瘑搴揑D | 10 |
+| storageBlobIds | Long[] | 鏄� | data | 鏂囦欢blob ID鏁扮粍 | [123, 124] |
+
+**鍓嶇璋冪敤**:
+```javascript
+saveKnowledgeBaseFiles({
+  knowledgeBaseId: 10,
+  storageBlobIds: [123, 124]
+});
+```
+
+**閲嶈璇存槑**:
+- 鈿狅笍 **蹇呴』鍏堣皟鐢� `/common/upload` 涓婁紶鏂囦欢**
+- 鈿狅笍 **鑾峰彇杩斿洖鐨� `data.id` 浣滀负 `storageBlobId`**
+- 鈿狅笍 **姝ゆ帴鍙hЕ鍙戝紓姝ュ悜閲忓寲澶勭悊**
+
+---
+
+#### 鍒犻櫎鐭ヨ瘑搴撴枃浠� (`DELETE /knowledgeBase/file/delete`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| ids | Long[] | 鏄� | data | 鍚戦噺璁板綍ID鏁扮粍 | [1, 2, 3] |
+
+**鍓嶇璋冪敤**:
+```javascript
+deleteKnowledgeBaseFile([row.id]);
+// 娉ㄦ剰: row.id 鏄悜閲忚褰曠殑ID,涓嶆槸 storageBlobId
+```
+
+---
+
+#### 閲嶆柊鍚戦噺鍖� (`POST /knowledgeBase/vector/reprocess/{vectorId}`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| vectorId | Long | 鏄� | URL璺緞 | 鍚戦噺璁板綍ID | 1 |
+
+**鍓嶇璋冪敤**:
+```javascript
+reprocessVector(1);
+```
+
+**URL**: `/knowledgeBase/vector/reprocess/1`
+
+---
+
+### 4.3 鐭ヨ瘑闂瓟鎺ュ彛鍙傛暟
+
+#### 鐭ヨ瘑搴撻棶绛� (`POST /ai/knowledge/chat` - 娴佸紡)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| knowledgeBaseId | Long | 鏄� | body | 鐭ヨ瘑搴揑D | 10 |
+| memoryId | String | 鏄� | body | 浼氳瘽ID | "kb-chat-xxx" |
+| question | String | 鏄� | body | 鐢ㄦ埛闂 | "濡備綍鎿嶄綔?" |
+
+**鍓嶇璋冪敤**:
+```javascript
+knowledgeChat({
+  knowledgeBaseId: chatKnowledgeBaseId.value,
+  memoryId: memoryId.value,
+  question: currentQuestion
+});
+```
+
+**浼氳瘽ID鐢熸垚瑙勮寖**:
+```javascript
+// 鏂瑰紡1: 浣跨敤 crypto.randomUUID() (鎺ㄨ崘)
+memoryId.value = crypto.randomUUID();
+
+// 鏂瑰紡2: 浣跨敤鏃堕棿鎴�
+memoryId.value = 'kb-chat-' + Date.now();
+```
+
+---
+
+#### 鏌ヨ闂瓟鍘嗗彶 (`GET /ai/knowledge/history/{memoryId}`)
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 浼犲弬浣嶇疆 | 璇存槑 | 绀轰緥鍊� |
+|--------|------|------|----------|------|--------|
+| memoryId | String | 鏄� | URL璺緞 | 浼氳瘽ID | "kb-chat-xxx" |
+
+**鍓嶇璋冪敤**:
+```javascript
+getKnowledgeHistory('kb-chat-xxx');
+```
+
+---
+
+## 浜斻�佸搷搴旀暟鎹瓧娈靛鐓ц〃
+
+### 5.1 鐭ヨ瘑搴撳垪琛ㄥ搷搴�
+
+```javascript
+{
+  code: 200,
+  data: {
+    total: 100,
+    records: [
+      {
+        id: 1,                     // 鐭ヨ瘑搴揑D
+        title: "鎿嶄綔鎵嬪唽",          // 鐭ヨ瘑鏍囬
+        type: "guide",             // 鐭ヨ瘑绫诲瀷
+        scenario: "绯荤粺鎿嶄綔",       // 閫傜敤鍦烘櫙
+        efficiency: "high",        // 瑙e喅鏁堢巼
+        problem: "...",            // 闂鎻忚堪
+        solution: "...",           // 瑙e喅鏂规
+        keyPoints: "...",          // 鍏抽敭瑕佺偣
+        creator: "寮犱笁",           // 鍒涘缓浜�
+        usageCount: 10,            // 浣跨敤娆℃暟
+        fileCount: 3,              // 鏂囦欢鏁伴噺
+        totalChunkCount: 45,       // 鎬诲垏鐗囨暟閲�
+        createTime: "2026-06-08",  // 鍒涘缓鏃堕棿
+        updateTime: "2026-06-08"   // 鏇存柊鏃堕棿
+      }
+    ]
+  }
+}
+```
+
+---
+
+### 5.2 鏂囦欢鍚戦噺鍖栫姸鎬佸搷搴�
+
+```javascript
+{
+  code: 200,
+  data: [
+    {
+      id: 1,                      // 鍚戦噺璁板綍ID
+      storageBlobId: 123,         // 鏂囦欢blob ID
+      fileName: "鎿嶄綔鎵嬪唽.docx",  // 鏂囦欢鍚�
+      fileType: "docx",           // 鏂囦欢绫诲瀷
+      vectorStatus: 2,            // 鍚戦噺鍖栫姸鎬�: 0-寰呭鐞�, 1-澶勭悊涓�, 2-宸插畬鎴�, 3-澶辫触
+      chunkCount: 15,             // 鍒囩墖鏁伴噺
+      namespace: "kb-10",         // 鍚戦噺鍛藉悕绌洪棿
+      vectorError: null,          // 鍚戦噺鍖栭敊璇俊鎭�
+      createTime: "2026-06-08"    // 鍒涘缓鏃堕棿
+    }
+  ]
+}
+```
+
+---
+
+### 5.3 鏂囦欢涓婁紶鍝嶅簲
+
+```javascript
+{
+  code: 200,
+  data: {
+    id: 123,                      // 鈿狅笍 杩欐槸 storageBlobId,鐢ㄤ簬淇濆瓨鏂囦欢鍏宠仈
+    name: "鎿嶄綔鎵嬪唽.docx",        // 鏂囦欢鍚�
+    url: "/profile/upload/...",   // 鏂囦欢URL
+    previewURL: "...",            // 棰勮URL
+    downloadURL: "..."            // 涓嬭浇URL
+  }
+}
+```
+
+**閲嶈**: 涓婁紶鎴愬姛鍚�,闇�瑕佹彁鍙� `response.data.id` 浣滀负 `storageBlobId`
+
+---
+
+## 鍏�佸墠绔粍浠朵紶鍙傝鑼�
+
+### 6.1 琛ㄦ牸缁勪欢浼犲弬
+
+```vue
+<PIMTable
+  rowKey="id"              <!-- 琛屽敮涓�鏍囪瘑瀛楁 -->
+  :column="tableColumn"    <!-- 鍒楅厤缃� -->
+  :tableData="tableData"   <!-- 琛ㄦ牸鏁版嵁 -->
+  :page="page"             <!-- 鍒嗛〉閰嶇疆 -->
+  :isSelection="true"      <!-- 鏄惁鏀寔閫夋嫨 -->
+  @selection-change="handleSelectionChange"
+  :tableLoading="tableLoading"
+  @pagination="pagination"
+  :total="page.total"
+/>
+```
+
+**鍒嗛〉閰嶇疆**:
+```javascript
+page: {
+  current: 1,    // 褰撳墠椤电爜
+  size: 20,      // 姣忛〉鏉℃暟
+  total: 0       // 鎬昏褰曟暟
+}
+```
+
+---
+
+### 6.2 寮圭獥缁勪欢浼犲弬
+
+```vue
+<FormDialog
+  v-model="dialogVisible"       <!-- 鎺у埗鏄剧ず -->
+  :title="dialogTitle"          <!-- 寮圭獥鏍囬 -->
+  :width="'800px'"              <!-- 寮圭獥瀹藉害 -->
+  @close="closeDialog"          <!-- 鍏抽棴鍥炶皟 -->
+  @confirm="submitForm"         <!-- 纭鍥炶皟 -->
+  @cancel="closeDialog"         <!-- 鍙栨秷鍥炶皟 -->
+>
+  <!-- 寮圭獥鍐呭 -->
+</FormDialog>
+```
+
+---
+
+### 6.3 涓婁紶缁勪欢浼犲弬
+
+```vue
+<el-upload
+  :action="uploadUrl"                    <!-- 涓婁紶鍦板潃 -->
+  :headers="uploadHeaders"              <!-- 璇锋眰澶� -->
+  :on-success="handleUploadSuccess"     <!-- 鎴愬姛鍥炶皟 -->
+  :on-error="handleUploadError"         <!-- 澶辫触鍥炶皟 -->
+  :before-upload="beforeUpload"         <!-- 涓婁紶鍓嶆牎楠� -->
+  multiple                               <!-- 鏀寔澶氶�� -->
+  :show-file-list="false"               <!-- 涓嶆樉绀烘枃浠跺垪琛� -->
+  accept=".txt,.md,.docx,.xlsx,.xls,.pdf"  <!-- 鏂囦欢绫诲瀷闄愬埗 -->
+/>
+```
+
+**涓婁紶閰嶇疆**:
+```javascript
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
+const uploadHeaders = {
+  Authorization: "Bearer " + getToken()
+};
+```
+
+---
+
+## 涓冦�佸弬鏁扮被鍨嬭浆鎹㈣鑼�
+
+### 7.1 瀛楃涓茶浆鏁板��
+
+```javascript
+// 鍚庣杩斿洖鐨勬暟鍊煎彲鑳芥槸瀛楃涓�,闇�瑕佽浆鎹�
+const id = Number(row.id);
+const count = parseInt(row.chunkCount, 10);
+```
+
+---
+
+### 7.2 鏁板�艰浆瀛楃涓�
+
+```javascript
+// URL璺緞鍙傛暟闇�瑕佸瓧绗︿覆
+const url = `/knowledgeBase/vector/status/${String(knowledgeBaseId)}`;
+```
+
+---
+
+### 7.3 鏁扮粍澶勭悊
+
+```javascript
+// ID鏁扮粍澶勭悊
+const ids = selection.map(item => item.id);  // 鉁� 鎻愬彇ID
+await delKnowledgeBase(ids);                 // 鉁� 浼犻�掓暟缁�
+
+// 鏂囦欢blob ID鏁扮粍
+const blobIds = uploadedFiles.map(file => file.id);
+await saveKnowledgeBaseFiles({
+  knowledgeBaseId: currentKnowledgeBase.id,
+  storageBlobIds: blobIds
+});
+```
+
+---
+
+## 鍏�佸弬鏁伴獙璇佽鑼�
+
+### 8.1 琛ㄥ崟楠岃瘉瑙勫垯
+
+```javascript
+const rules = {
+  title: [
+    { required: true, message: "璇疯緭鍏ョ煡璇嗘爣棰�", trigger: "blur" }
+  ],
+  type: [
+    { required: true, message: "璇烽�夋嫨鐭ヨ瘑绫诲瀷", trigger: "change" }
+  ],
+  problem: [
+    { required: true, message: "璇锋弿杩伴亣鍒扮殑闂", trigger: "blur" }
+  ],
+  solution: [
+    { required: true, message: "璇疯缁嗘弿杩拌В鍐虫柟妗�", trigger: "blur" }
+  ]
+};
+```
+
+---
+
+### 8.2 涓婁紶鏂囦欢鏍¢獙
+
+```javascript
+const beforeUpload = (file) => {
+  // 鏂囦欢绫诲瀷鏍¢獙
+  const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf'];
+  const fileName = file.name.toLowerCase();
+  const isAllowed = allowedTypes.some(type => fileName.endsWith(type));
+
+  if (!isAllowed) {
+    ElMessage.error('鍙敮鎸� txt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df 鏍煎紡鐨勬枃浠�');
+    return false;
+  }
+
+  // 鏂囦欢澶у皬鏍¢獙
+  const isLt50M = file.size / 1024 / 1024 < 50;
+  if (!isLt50M) {
+    ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 50MB');
+    return false;
+  }
+
+  return true;
+};
+```
+
+---
+
+### 8.3 闂瓟鍙傛暟鏍¢獙
+
+```javascript
+const sendQuestion = async () => {
+  // 绌哄唴瀹规牎楠�
+  if (!questionInput.value.trim()) {
+    ElMessage.warning("璇疯緭鍏ラ棶棰�");
+    return;
+  }
+
+  // 鐭ヨ瘑搴撻�夋嫨鏍¢獙
+  if (!chatKnowledgeBaseId.value) {
+    ElMessage.warning("璇峰厛閫夋嫨鐭ヨ瘑搴�");
+    return;
+  }
+
+  // 鍙戦�佺姸鎬佹牎楠�
+  if (sending.value) {
+    return;  // 闃叉閲嶅鍙戦��
+  }
+
+  // ...鍙戦�佽姹�
+};
+```
+
+---
+
+## 涔濄�佸父瑙侀敊璇拰瑙e喅鏂规
+
+### 9.1 鍙傛暟鍚嶄笉鍖归厤
+
+**閿欒绀轰緥**:
+```javascript
+// 鉂� 閿欒: 浣跨敤浜嗕笅鍒掔嚎鍛藉悕
+saveKnowledgeBaseFiles({
+  knowledge_base_id: 10,
+  storage_blob_ids: [123]
+});
+
+// 鉁� 姝g‘: 浣跨敤椹煎嘲鍛藉悕
+saveKnowledgeBaseFiles({
+  knowledgeBaseId: 10,
+  storageBlobIds: [123]
+});
+```
+
+---
+
+### 9.2 浼犲弬浣嶇疆閿欒
+
+**閿欒绀轰緥**:
+```javascript
+// 鉂� 閿欒: POST璇锋眰浣跨敤 params
+export function addKnowledgeBase(data) {
+  return request({
+    url: "/knowledgeBase/add",
+    method: "post",
+    params: data  // 鉂� 閿欒
+  });
+}
+
+// 鉁� 姝g‘: POST璇锋眰浣跨敤 data
+export function addKnowledgeBase(data) {
+  return request({
+    url: "/knowledgeBase/add",
+    method: "post",
+    data: data  // 鉁� 姝g‘
+  });
+}
+```
+
+---
+
+### 9.3 DELETE璇锋眰鍙傛暟閿欒
+
+**閿欒绀轰緥**:
+```javascript
+// 鉂� 閿欒: DELETE浣跨敤 params 浼犻�掓暟缁�
+export function delKnowledgeBase(ids) {
+  return request({
+    url: "/knowledgeBase/delete",
+    method: "delete",
+    params: ids  // 鉂� 閿欒
+  });
+}
+
+// 鉁� 姝g‘: DELETE浣跨敤 data 浼犻�掓暟缁�
+export function delKnowledgeBase(ids) {
+  return request({
+    url: "/knowledgeBase/delete",
+    method: "delete",
+    data: ids  // 鉁� 姝g‘
+  });
+}
+```
+
+---
+
+### 9.4 娴佸紡鎺ュ彛閿欒
+
+**閿欒绀轰緥**:
+```javascript
+// 鉂� 閿欒: 娴佸紡鎺ュ彛浣跨敤 axios
+export function knowledgeChat(data) {
+  return request({
+    url: "/ai/knowledge/chat",
+    method: "post",
+    data: data  // 鉂� axios涓嶆敮鎸佹祦寮�
+  });
+}
+
+// 鉁� 姝g‘: 娴佸紡鎺ュ彛浣跨敤 Fetch API
+export function knowledgeChat(data) {
+  const token = getToken();
+  return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      'Authorization': 'Bearer ' + token
+    },
+    body: JSON.stringify(data)  // 鉁� 姝g‘
+  });
+}
+```
+
+---
+
+### 9.5 鏂囦欢涓婁紶ID鑾峰彇閿欒
+
+**閿欒绀轰緥**:
+```javascript
+// 鉂� 閿欒: 涓婁紶鎴愬姛鍚庢病鏈変繚瀛� storageBlobId
+const handleUploadSuccess = (response) => {
+  if (response.code === 200) {
+    ElMessage.success("涓婁紶鎴愬姛");
+    // 鉂� 娌℃湁淇濆瓨 response.data.id
+  }
+};
+
+// 鉁� 姝g‘: 淇濆瓨 storageBlobId 鐢ㄤ簬鍚庣画鍏宠仈
+const handleUploadSuccess = (response, file) => {
+  if (response.code === 200) {
+    uploadedBlobIds.value.push(response.data.id);  // 鉁� 淇濆瓨ID
+    ElMessage.success(`鏂囦欢 ${file.name} 涓婁紶鎴愬姛`);
+  }
+};
+```
+
+---
+
+## 鍗併�佹渶浣冲疄璺靛缓璁�
+
+### 10.1 鍙傛暟鍛藉悕涓�鑷存��
+
+鉁� **鍓嶇鍙傛暟鍚嶄笌鍚庣瀹炰綋瀛楁淇濇寔涓�鑷�**
+```javascript
+// 鍓嶇
+{
+  knowledgeBaseId: 10,
+  storageBlobIds: [123]
+}
+
+// 鍚庣瀹炰綋瀛楁
+private Long knowledgeBaseId;
+private List<Long> storageBlobIds;
+```
+
+---
+
+### 10.2 鍙傛暟绫诲瀷涓�鑷存��
+
+鉁� **鏄庣‘鍙傛暟绫诲瀷,閬垮厤鑷姩绫诲瀷杞崲**
+```javascript
+// 鏁板�肩被鍨�
+const id = 10;  // number
+const count = 0;  // number
+
+// 瀛楃涓茬被鍨�
+const title = "";  // string
+const type = "";  // string
+
+// 鏁扮粍绫诲瀷
+const ids = [];  // array
+```
+
+---
+
+### 10.3 蹇呭~鍙傛暟鏍¢獙
+
+鉁� **鍦ㄨ皟鐢ㄦ帴鍙e墠鏍¢獙蹇呭~鍙傛暟**
+```javascript
+const saveFiles = async () => {
+  if (!currentKnowledgeBase.value?.id) {
+    ElMessage.error("鐭ヨ瘑搴撲俊鎭紓甯�");
+    return;
+  }
+
+  if (uploadedBlobIds.value.length === 0) {
+    ElMessage.warning("璇峰厛涓婁紶鏂囦欢");
+    return;
+  }
+
+  // ...璋冪敤鎺ュ彛
+};
+```
+
+---
+
+### 10.4 鍙傛暟榛樿鍊�
+
+鉁� **涓哄彲閫夊弬鏁拌缃悎鐞嗙殑榛樿鍊�**
+```javascript
+const form = {
+  title: "",
+  type: "",
+  usageCount: 0,  // 鉁� 鏁板�肩被鍨嬮粯璁ゅ�间负0
+  creator: userStore.nickName || ""  // 鉁� 浣跨敤褰撳墠鐢ㄦ埛鍚嶄綔涓洪粯璁ゅ��
+};
+```
+
+---
+
+### 10.5 URL璺緞鍙傛暟
+
+鉁� **璺緞鍙傛暟浣跨敤妯℃澘瀛楃涓�**
+```javascript
+const url = `/knowledgeBase/vector/status/${knowledgeBaseId}`;
+const url = `/knowledgeBase/vector/reprocess/${vectorId}`;
+```
+
+---
+
+## 鍗佷竴銆佸弬鏁版槧灏勫叧绯绘�荤粨
+
+### 鐭ヨ瘑搴揑D鐩稿叧
+| 鍦烘櫙 | 鍙傛暟鍚� | 绫诲瀷 | 鏉ユ簮 |
+|------|--------|------|------|
+| 鐭ヨ瘑搴撳垪琛ㄦ煡璇� | - | - | URL璺緞鏃犲弬鏁� |
+| 鐭ヨ瘑搴撹鎯� | id | Long | URL璺緞鍙傛暟 |
+| 鏂囦欢鍏宠仈淇濆瓨 | knowledgeBaseId | Long | POST body鍙傛暟 |
+| 鍚戦噺鍖栫姸鎬佹煡璇� | knowledgeBaseId | Long | URL璺緞鍙傛暟 |
+| 鐭ヨ瘑闂瓟 | knowledgeBaseId | Long | POST body鍙傛暟 |
+
+---
+
+### 鏂囦欢ID鐩稿叧
+| 鍦烘櫙 | 鍙傛暟鍚� | 绫诲瀷 | 鏉ユ簮 |
+|------|--------|------|------|
+| 鏂囦欢涓婁紶鍝嶅簲 | data.id | Long | 鍝嶅簲鏁版嵁(浣滀负storageBlobId) |
+| 鏂囦欢鍏宠仈淇濆瓨 | storageBlobIds | Long[] | POST body鍙傛暟 |
+| 鏂囦欢鍒犻櫎 | ids | Long[] | DELETE body鍙傛暟(鍚戦噺璁板綍ID) |
+| 閲嶆柊鍚戦噺鍖� | vectorId | Long | URL璺緞鍙傛暟 |
+
+---
+
+### 浼氳瘽ID鐩稿叧
+| 鍦烘櫙 | 鍙傛暟鍚� | 绫诲瀷 | 鏉ユ簮 |
+|------|--------|------|------|
+| 鐭ヨ瘑闂瓟 | memoryId | String | 鍓嶇鐢熸垚UUID |
+| 闂瓟鍘嗗彶鏌ヨ | memoryId | String | URL璺緞鍙傛暟 |
+
+---
+
+## 鍗佷簩銆侀檮褰�
+
+### 闄勫綍A: 鍙傛暟绫诲瀷瀵圭収琛�
+
+| 鍙傛暟绫诲瀷 | JavaScript | Java | MySQL |
+|----------|------------|------|-------|
+| ID | number/Long | Long | BIGINT |
+| 鏍囬 | String | String | VARCHAR |
+| 绫诲瀷 | String | String | VARCHAR |
+| 鐘舵�� | Integer | Integer | TINYINT |
+| 鏁伴噺 | Integer | Integer | INT |
+| 鏃堕棿 | String/Date | LocalDateTime | DATETIME |
+| 鏁扮粍 | Array | List | - |
+
+---
+
+### 闄勫綍B: HTTP鏂规硶涓庝紶鍙備綅缃鐓ц〃
+
+| HTTP鏂规硶 | 浼犲弬浣嶇疆 | request閰嶇疆 | 閫傜敤鍦烘櫙 |
+|----------|----------|--------------|----------|
+| GET | URL鍙傛暟 | `params: query` | 鏌ヨ銆佸垪琛� |
+| POST | 璇锋眰浣� | `data: data` | 鏂板銆佹洿鏂般�佷繚瀛� |
+| DELETE | 璇锋眰浣� | `data: ids` | 鍒犻櫎銆佹壒閲忓垹闄� |
+| PUT | 璇锋眰浣� | `data: data` | 鏇存柊(閮ㄥ垎浣跨敤POST) |
+| 娴佸紡POST | 璇锋眰浣� | `body: JSON.stringify()` | AI闂瓟 |
+
+---
+
+### 闄勫綍C: 鍚戦噺鍖栫姸鎬佸�煎鐓ц〃
+
+| 鐘舵�佸�� | 鐘舵�佸悕绉� | 鍓嶇鏄剧ず | 鏍囩棰滆壊 |
+|--------|----------|----------|----------|
+| 0 | 寰呭鐞� | "寰呭鐞�" | info (鐏拌壊) |
+| 1 | 澶勭悊涓� | "澶勭悊涓�" | warning (姗欒壊) |
+| 2 | 宸插畬鎴� | "宸插畬鎴�" | success (缁胯壊) |
+| 3 | 澶辫触 | "澶辫触" | danger (绾㈣壊) |
+
+---
+
+## 鍗佷笁銆佹�荤粨
+
+鏈枃妗h缁嗚鑼冧簡鐭ヨ瘑搴撴ā鍧楃殑鍙傛暟鍛藉悕鍜屼紶鍙傛柟寮�,閬靛惊浠ヤ笅鍘熷垯:
+
+1. 鉁� **鍛藉悕涓�鑷存��**: 鍓嶅悗绔弬鏁板悕淇濇寔涓�鑷�(椹煎嘲鍛藉悕)
+2. 鉁� **浼犲弬瑙勮寖鎬�**: GET鐢╬arams銆丳OST鐢╠ata銆丏ELETE鐢╠ata
+3. 鉁� **绫诲瀷鏄庣‘鎬�**: 鏄庣‘鍙傛暟绫诲瀷,鍚堢悊璁剧疆榛樿鍊�
+4. 鉁� **鏍¢獙瀹屾暣鎬�**: 蹇呭~鍙傛暟闇�鏍¢獙,鍙�夊弬鏁版湁榛樿鍊�
+5. 鉁� **閿欒閬垮厤**: 閬靛惊瑙勮寖閬垮厤甯歌閿欒
+
+寤鸿鍥㈤槦鎴愬憳涓ユ牸閬靛惊鏈鑼�,纭繚鍓嶅悗绔弬鏁颁紶閫掔殑涓�鑷存�у拰鍙潬鎬с��
\ No newline at end of file
diff --git "a/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\345\211\215\347\253\257\345\256\236\347\216\260\346\226\207\346\241\243.md" "b/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\345\211\215\347\253\257\345\256\236\347\216\260\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..c2ea6e2
--- /dev/null
+++ "b/doc/\347\237\245\350\257\206\345\272\223\346\250\241\345\235\227\345\211\215\347\253\257\345\256\236\347\216\260\346\226\207\346\241\243.md"
@@ -0,0 +1,1212 @@
+# 鐭ヨ瘑搴撴ā鍧楀畬鏁村墠绔疄鐜版枃妗�
+
+## 涓�銆佹ā鍧楁杩�
+
+鐭ヨ瘑搴撴ā鍧楁槸涓�涓泦鎴愪簡RAG(妫�绱㈠寮虹敓鎴�)鎶�鏈殑鏅鸿兘鐭ヨ瘑绠$悊绯荤粺,鏀寔:
+- **鐭ヨ瘑搴揅RUD绠$悊** - 鍒涘缓銆佺紪杈戙�佸垹闄ゃ�佹煡璇㈢煡璇嗗簱
+- **鏂囦欢涓婁紶涓庡悜閲忓寲** - 鏀寔澶氱鏂囦欢鏍煎紡,鑷姩杩涜鍚戦噺鍒囩墖澶勭悊
+- **鏅鸿兘闂瓟** - 鍩轰簬涓婁紶鏂囦欢鍐呭杩涜AI闂瓟
+- **鏂囦欢绠$悊** - 鏌ョ湅鏂囦欢鍚戦噺鍖栫姸鎬�,鏀寔閲嶆柊澶勭悊鍜屽垹闄�
+
+### 鎶�鏈灦鏋�
+- **鍓嶇妗嗘灦**: Vue 3 + Composition API
+- **UI缁勪欢搴�**: Element Plus
+- **鍚戦噺鏁版嵁搴�**: Pinecone
+- **AI妯″瀷**: 闃块噷浜戦�氫箟鍗冮棶
+- **鏂囦欢澶勭悊**: 鏀寔docx銆亁lsx銆乸df銆乼xt銆乵d绛夋牸寮�
+
+---
+
+## 浜屻�佹枃浠剁粨鏋�
+
+```
+src/
+鈹溾攢鈹� api/
+鈹�   鈹斺攢鈹� collaborativeApproval/
+鈹�       鈹斺攢鈹� knowledgeBase.js          # API鎺ュ彛灏佽
+鈹溾攢鈹� views/
+鈹�   鈹斺攢鈹� collaborativeApproval/
+鈹�       鈹斺攢鈹� knowledgeBase/
+鈹�           鈹斺攢鈹� index.vue              # 涓婚〉闈㈢粍浠�
+鈹斺攢鈹� components/
+    鈹溾攢鈹� PIMTable/
+    鈹�   鈹斺攢鈹� PIMTable.vue              # 琛ㄦ牸缁勪欢
+    鈹斺攢鈹� Dialog/
+        鈹斺攢鈹� FormDialog.vue             # 寮圭獥缁勪欢
+```
+
+---
+
+## 涓夈�丄PI鎺ュ彛瀹氫箟
+
+### 3.1 鏂囦欢浣嶇疆
+`src/api/collaborativeApproval/knowledgeBase.js`
+
+### 3.2 瀹屾暣鎺ュ彛鍒楄〃
+
+```javascript
+import request from "@/utils/request";
+import { getToken } from '@/utils/auth';
+
+// 1. 鏌ヨ鐭ヨ瘑搴撳垪琛�(鍒嗛〉)
+export function listKnowledgeBase(query) {
+  return request({
+    url: "/knowledgeBase/getList",
+    method: "get",
+    params: query,
+  });
+}
+
+// 2. 鏂板鐭ヨ瘑搴�
+export function addKnowledgeBase(data) {
+  return request({
+    url: "/knowledgeBase/add",
+    method: "post",
+    data: data,
+  });
+}
+
+// 3. 淇敼鐭ヨ瘑搴�
+export function updateKnowledgeBase(data) {
+  return request({
+    url: "/knowledgeBase/update",
+    method: "post",
+    data: data,
+  });
+}
+
+// 4. 鍒犻櫎鐭ヨ瘑搴�
+export function delKnowledgeBase(query) {
+  return request({
+    url: "/knowledgeBase/delete",
+    method: "delete",
+    data: query,
+  });
+}
+
+// 5. 鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��(鍖呭惈鏂囦欢鍒楄〃)
+export function getVectorStatus(knowledgeBaseId) {
+  return request({
+    url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
+    method: "get",
+  });
+}
+
+// 6. 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�(瑙﹀彂鍚戦噺鍖�)
+export function saveKnowledgeBaseFiles(data) {
+  return request({
+    url: "/knowledgeBase/file/save",
+    method: "post",
+    data,
+  });
+}
+
+// 7. 鍒犻櫎鐭ヨ瘑搴撴枃浠�
+export function deleteKnowledgeBaseFile(ids) {
+  return request({
+    url: "/knowledgeBase/file/delete",
+    method: "delete",
+    data: ids,
+  });
+}
+
+// 8. 閲嶆柊鍚戦噺鍖栨枃浠�
+export function reprocessVector(vectorId) {
+  return request({
+    url: `/knowledgeBase/vector/reprocess/${vectorId}`,
+    method: "post",
+  });
+}
+
+// 9. 鐭ヨ瘑搴撻棶绛�(娴佸紡)
+export function knowledgeChat(data, onMessage) {
+  const token = getToken();
+  return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      'Authorization': 'Bearer ' + token
+    },
+    body: JSON.stringify(data)
+  });
+}
+
+// 10. 鏌ヨ鐭ヨ瘑搴撻棶绛斿巻鍙�
+export function getKnowledgeHistory(memoryId) {
+  return request({
+    url: `/ai/knowledge/history/${memoryId}`,
+    method: "get",
+  });
+}
+```
+
+### 3.3 鎺ュ彛鍙傛暟璇存槑
+
+#### 鐭ヨ瘑搴撳垪琛ㄦ煡璇�
+```javascript
+// 璇锋眰鍙傛暟
+{
+  current: 1,        // 褰撳墠椤电爜
+  size: 20,          // 姣忛〉鏉℃暟
+  title: "",         // 鐭ヨ瘑鏍囬(鍙��)
+  type: ""           // 鐭ヨ瘑绫诲瀷(鍙��)
+}
+
+// 鍝嶅簲鏁版嵁
+{
+  code: 200,
+  data: {
+    total: 100,
+    records: [
+      {
+        id: 1,
+        title: "鎿嶄綔鎵嬪唽",
+        type: "guide",
+        scenario: "绯荤粺鎿嶄綔鎸囧",
+        efficiency: "high",
+        problem: "鐢ㄦ埛涓嶄細鎿嶄綔绯荤粺",
+        solution: "鎸夌収鎿嶄綔鎵嬪唽鎵ц...",
+        keyPoints: "姝ラ1,姝ラ2,姝ラ3",
+        creator: "寮犱笁",
+        usageCount: 10,
+        fileCount: 3,           // 鏂囦欢鏁伴噺
+        totalChunkCount: 45,    // 鎬诲垏鐗囨暟閲�
+        createTime: "2026-06-08 10:00:00"
+      }
+    ]
+  }
+}
+```
+
+#### 淇濆瓨鏂囦欢鍏宠仈
+```javascript
+// 璇锋眰鍙傛暟
+{
+  knowledgeBaseId: 10,         // 鐭ヨ瘑搴揑D
+  storageBlobIds: [123, 124]   // 涓婁紶鏂囦欢杩斿洖鐨刡lob ID鍒楄〃
+}
+
+// 鍝嶅簲鏁版嵁
+{
+  code: 200,
+  msg: "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 鍚戦噺鍖栫姸鎬佹煡璇�
+```javascript
+// 鍝嶅簲鏁版嵁
+{
+  code: 200,
+  data: [
+    {
+      id: 1,
+      storageBlobId: 123,
+      fileName: "鎿嶄綔鎵嬪唽.docx",
+      fileType: "docx",
+      vectorStatus: 2,         // 0-寰呭鐞�,1-澶勭悊涓�,2-宸插畬鎴�,3-澶辫触
+      chunkCount: 15,          // 鍒囩墖鏁伴噺
+      namespace: "kb-10",
+      vectorError: null,
+      createTime: "2026-06-08 10:00:00"
+    }
+  ]
+}
+```
+
+#### 鐭ヨ瘑搴撻棶绛�
+```javascript
+// 璇锋眰鍙傛暟
+{
+  knowledgeBaseId: 10,
+  memoryId: "session-xxx",     // 浼氳瘽ID,鐢ㄤ簬淇濇寔涓婁笅鏂�
+  question: "濡備綍鎿嶄綔瀹℃壒娴佺▼?"
+}
+
+// 鍝嶅簲(娴佸紡杩斿洖 text/stream;charset=utf-8)
+// 鏍规嵁鐭ヨ瘑搴撳唴瀹�,瀹℃壒娴佺▼鐨勬搷浣滄楠ゅ涓�:
+// 1. 鐧诲綍绯荤粺鍚庤繘鍏ュ鎵圭鐞嗘ā鍧�...
+```
+
+---
+
+## 鍥涖�佹牳蹇冪粍浠跺疄鐜�
+
+### 4.1 涓婚〉闈㈢粨鏋�
+
+椤甸潰閲囩敤Tab椤电甯冨眬,鍖呭惈涓や釜涓昏鍔熻兘妯″潡:
+- **鐭ヨ瘑搴撶鐞�** - 鐭ヨ瘑搴揅RUD鎿嶄綔
+- **鐭ヨ瘑搴撻棶绛�** - 鍩轰簬RAG鐨勬櫤鑳介棶绛�
+
+### 4.2 鏁版嵁妯″瀷瀹氫箟
+
+```javascript
+// 鍝嶅簲寮忔暟鎹�
+const data = reactive({
+  // 鎼滅储琛ㄥ崟
+  searchForm: {
+    title: "",
+    type: "",
+  },
+
+  // 鍒嗛〉閰嶇疆
+  page: {
+    current: 1,
+    size: 20,
+    total: 0,
+  },
+
+  // 琛ㄦ牸鏁版嵁
+  tableData: [],
+  tableLoading: false,
+  selectedIds: [],
+
+  // 鐭ヨ瘑搴撹〃鍗�
+  form: {
+    title: "",
+    type: "",
+    scenario: "",
+    efficiency: "",
+    problem: "",
+    solution: "",
+    keyPoints: "",
+    creator: "",
+    usageCount: 0
+  },
+
+  // 寮圭獥鎺у埗
+  dialogVisible: false,
+  dialogTitle: "",
+  dialogType: "add",  // add or edit
+  viewDialogVisible: false,
+  currentKnowledge: {},
+
+  // 鏂囦欢绠$悊
+  filesDialogVisible: false,
+  currentKnowledgeBase: null,
+  fileList: [],
+  uploadedBlobIds: [],
+  savingFiles: false,
+
+  // 鐭ヨ瘑搴撻棶绛�
+  chatDialogVisible: false,
+  messages: [],
+  inputQuestion: "",
+  chatLoading: false,
+  memoryId: ""
+});
+```
+
+### 4.3 琛ㄦ牸鍒楅厤缃�
+
+```javascript
+const tableColumn = ref([
+  {
+    label: "鐭ヨ瘑鏍囬",
+    prop: "title",
+    showOverflowTooltip: true,
+  },
+  {
+    label: "鐭ヨ瘑绫诲瀷",
+    prop: "type",
+    dataType: "tag",
+    formatData: (params) => getKnowledgeTypeLabel(params),
+    formatType: (params) => getKnowledgeTypeTagType(params)
+  },
+  {
+    label: "閫傜敤鍦烘櫙",
+    prop: "scenario",
+    width: 150,
+    showOverflowTooltip: true,
+  },
+  {
+    label: "瑙e喅鏁堢巼",
+    prop: "efficiency",
+    dataType: "tag",
+    formatData: (params) => {
+      const efficiencyMap = {
+        high: "鏄捐憲鎻愬崌",
+        medium: "涓�鑸彁鍗�",
+        low: "杞诲井鎻愬崌"
+      };
+      return efficiencyMap[params] || params;
+    },
+    formatType: (params) => {
+      const typeMap = {
+        high: "success",
+        medium: "warning",
+        low: "info"
+      };
+      return typeMap[params] || "info";
+    }
+  },
+  {
+    label: "鏂囦欢鏁伴噺",
+    prop: "fileCount",
+    width: 100,
+    align: "center"
+  },
+  {
+    label: "鍒囩墖鏁伴噺",
+    prop: "totalChunkCount",
+    width: 100,
+    align: "center"
+  },
+  {
+    label: "浣跨敤娆℃暟",
+    prop: "usageCount",
+    width: 100,
+    align: "center"
+  },
+  {
+    label: "鍒涘缓浜�",
+    prop: "creator",
+    width: 120,
+  },
+  {
+    label: "鍒涘缓鏃堕棿",
+    prop: "createTime",
+    width: 180,
+  },
+  {
+    dataType: "action",
+    label: "鎿嶄綔",
+    align: "center",
+    fixed: "right",
+    width: 280,
+    operation: [
+      {
+        name: "缂栬緫",
+        type: "text",
+        clickFun: (row) => openForm("edit", row)
+      },
+      {
+        name: "鏂囦欢",
+        type: "text",
+        clickFun: (row) => openFilesDialog(row)
+      },
+      {
+        name: "闂瓟",
+        type: "text",
+        clickFun: (row) => openChatDialog(row)
+      },
+      {
+        name: "璇︽儏",
+        type: "text",
+        clickFun: (row) => viewKnowledge(row)
+      }
+    ]
+  }
+]);
+```
+
+---
+
+## 浜斻�佹牳蹇冧笟鍔¢�昏緫
+
+### 5.1 鏂囦欢涓婁紶涓庡悜閲忓寲娴佺▼
+
+#### 娴佺▼鍥�
+```
+鐢ㄦ埛鐐瑰嚮"涓婁紶鏂囦欢"
+    鈫�
+閫夋嫨鏂囦欢(鏀寔澶氶��)
+    鈫�
+鍓嶇鏍¢獙鏂囦欢绫诲瀷鍜屽ぇ灏�
+    鈫�
+璋冪敤 /common/upload 涓婁紶鏂囦欢
+    鈫�
+鑾峰彇 storageBlobId 鍒楄〃
+    鈫�
+鐢ㄦ埛鐐瑰嚮"淇濆瓨鏂囦欢鍏宠仈"
+    鈫�
+璋冪敤 /knowledgeBase/file/save
+    鈫�
+鍚庣鍒涘缓鍚戦噺璁板綍 + 寮傛瑙﹀彂鍚戦噺鍖�
+    鈫�
+鍓嶇寤惰繜1绉掑埛鏂版枃浠跺垪琛�
+    鈫�
+鏄剧ず鍚戦噺鍖栫姸鎬�(寰呭鐞嗏啋澶勭悊涓啋宸插畬鎴�)
+```
+
+#### 浠g爜瀹炵幇
+
+```vue
+<template>
+  <div class="file-manager">
+    <!-- 鏂囦欢涓婁紶 -->
+    <div class="upload-section">
+      <el-upload
+        :action="uploadUrl"
+        :headers="uploadHeaders"
+        :on-success="handleUploadSuccess"
+        :on-error="handleUploadError"
+        :before-upload="beforeUpload"
+        multiple
+        :show-file-list="false"
+        accept=".txt,.md,.docx,.xlsx,.xls,.pdf"
+      >
+        <el-button type="primary">涓婁紶鏂囦欢</el-button>
+      </el-upload>
+      <el-button
+        type="success"
+        @click="saveFiles"
+        :disabled="uploadedBlobIds.length === 0"
+        :loading="savingFiles"
+      >
+        淇濆瓨鏂囦欢鍏宠仈
+      </el-button>
+    </div>
+
+    <!-- 鏂囦欢鍒楄〃 -->
+    <el-table :data="fileList" style="margin-top: 20px" border>
+      <el-table-column prop="fileName" label="鏂囦欢鍚�" />
+      <el-table-column prop="fileType" label="鏂囦欢绫诲瀷" width="100" />
+      <el-table-column label="鍚戦噺鍖栫姸鎬�" width="120">
+        <template #default="{ row }">
+          <el-tag :type="getStatusType(row.vectorStatus)">
+            {{ getStatusText(row.vectorStatus) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="chunkCount" label="鍒囩墖鏁�" width="100" />
+      <el-table-column label="鎿嶄綔" width="150">
+        <template #default="{ row }">
+          <el-button
+            v-if="row.vectorStatus === 3"
+            type="text"
+            @click="reprocessFile(row)"
+          >
+            閲嶆柊澶勭悊
+          </el-button>
+          <el-button type="text" @click="deleteFile(row)" style="color: #f56c6c">
+            鍒犻櫎
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+import { getToken } from "@/utils/auth";
+
+// 鏂囦欢涓婁紶閰嶇疆
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
+const uploadHeaders = { Authorization: "Bearer " + getToken() };
+const uploadedBlobIds = ref([]);
+const fileList = ref([]);
+
+// 涓婁紶鍓嶆牎楠�
+const beforeUpload = (file) => {
+  const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf'];
+  const fileName = file.name.toLowerCase();
+  const isAllowed = allowedTypes.some(type => fileName.endsWith(type));
+
+  if (!isAllowed) {
+    ElMessage.error('鍙敮鎸� txt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df 鏍煎紡鐨勬枃浠�');
+    return false;
+  }
+
+  const isLt50M = file.size / 1024 / 1024 < 50;
+  if (!isLt50M) {
+    ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 50MB');
+    return false;
+  }
+
+  return true;
+};
+
+// 涓婁紶鎴愬姛
+const handleUploadSuccess = (response, file) => {
+  if (response.code === 200) {
+    uploadedBlobIds.value.push(response.data.id);
+    ElMessage.success(`鏂囦欢 ${file.name} 涓婁紶鎴愬姛`);
+  } else {
+    ElMessage.error(response.msg || "涓婁紶澶辫触");
+  }
+};
+
+// 淇濆瓨鏂囦欢鍏宠仈
+const saveFiles = async () => {
+  if (uploadedBlobIds.value.length === 0) {
+    ElMessage.warning("璇峰厛涓婁紶鏂囦欢");
+    return;
+  }
+
+  savingFiles.value = true;
+  try {
+    await saveKnowledgeBaseFiles({
+      knowledgeBaseId: currentKnowledgeBase.value.id,
+      storageBlobIds: uploadedBlobIds.value
+    });
+
+    ElMessage.success("鏂囦欢鍏宠仈淇濆瓨鎴愬姛,姝e湪鍚庡彴澶勭悊鍚戦噺鍖�");
+    uploadedBlobIds.value = [];
+
+    // 寤惰繜鍒锋柊鏂囦欢鍒楄〃
+    setTimeout(() => {
+      loadFileList();
+    }, 1000);
+  } catch (error) {
+    console.error("淇濆瓨鏂囦欢鍏宠仈澶辫触:", error);
+    ElMessage.error("淇濆瓨鏂囦欢鍏宠仈澶辫触");
+  } finally {
+    savingFiles.value = false;
+  }
+};
+
+// 鍔犺浇鏂囦欢鍒楄〃
+const loadFileList = async () => {
+  if (!currentKnowledgeBase.value?.id) return;
+
+  try {
+    const res = await getVectorStatus(currentKnowledgeBase.value.id);
+    fileList.value = res.data || [];
+  } catch (error) {
+    console.error("鍔犺浇鏂囦欢鍒楄〃澶辫触:", error);
+    ElMessage.error("鍔犺浇鏂囦欢鍒楄〃澶辫触");
+  }
+};
+
+// 鐘舵�佹槧灏�
+const getStatusText = (status) => {
+  const map = {
+    0: '寰呭鐞�',
+    1: '澶勭悊涓�',
+    2: '宸插畬鎴�',
+    3: '澶辫触'
+  };
+  return map[status] || '鏈煡';
+};
+
+const getStatusType = (status) => {
+  const map = {
+    0: 'info',
+    1: 'warning',
+    2: 'success',
+    3: 'danger'
+  };
+  return map[status] || 'info';
+};
+
+// 閲嶆柊澶勭悊鍚戦噺鍖栫殑鏂囦欢
+const reprocessFile = async (row) => {
+  try {
+    await reprocessVector(row.id);
+    ElMessage.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+    setTimeout(() => {
+      loadFileList();
+    }, 1000);
+  } catch (error) {
+    console.error("閲嶆柊澶勭悊澶辫触:", error);
+    ElMessage.error("閲嶆柊澶勭悊澶辫触");
+  }
+};
+
+// 鍒犻櫎鏂囦欢
+const deleteFile = async (row) => {
+  try {
+    await ElMessageBox.confirm(
+      "纭畾瑕佸垹闄よ鏂囦欢鍚�?鍒犻櫎鍚庡皢鏃犳硶鎭㈠鍚戦噺鏁版嵁",
+      "鍒犻櫎纭",
+      {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+      }
+    );
+
+    await deleteKnowledgeBaseFiles([row.id]);
+    ElMessage.success("鍒犻櫎鎴愬姛");
+    loadFileList();
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error("鍒犻櫎鏂囦欢澶辫触:", error);
+      ElMessage.error("鍒犻櫎鏂囦欢澶辫触");
+    }
+  }
+};
+</script>
+```
+
+### 5.2 鐭ヨ瘑搴撻棶绛旀祦绋�
+
+#### 娴佺▼鍥�
+```
+鐢ㄦ埛閫夋嫨鐭ヨ瘑搴�
+    鈫�
+杈撳叆闂骞舵彁浜�
+    鈫�
+鍓嶇鐢熸垚memoryId(鐢ㄤ簬浼氳瘽涓婁笅鏂�)
+    鈫�
+璋冪敤 /ai/knowledge/chat (娴佸紡鎺ュ彛)
+    鈫�
+鍚庣澶勭悊:
+  - 瀵归棶棰樿繘琛屽悜閲忓寲
+  - 鍦≒inecone涓绱㈢浉鍏冲垏鐗�
+  - 鏋勫缓涓婁笅鏂嘝rompt
+  - 璋冪敤LLM鐢熸垚鍥炵瓟
+    鈫�
+娴佸紡杩斿洖AI鍥炵瓟
+    鈫�
+鍓嶇瀹炴椂鏄剧ず鍥炵瓟鍐呭
+    鈫�
+鑷姩婊氬姩鍒板簳閮�
+```
+
+#### 浠g爜瀹炵幇
+
+```vue
+<template>
+  <div class="knowledge-chat">
+    <div class="chat-header">
+      <el-tag type="success">褰撳墠鐭ヨ瘑搴�: {{ currentKnowledgeBase?.title }}</el-tag>
+    </div>
+
+    <!-- 瀵硅瘽鍖哄煙 -->
+    <div class="chat-messages" ref="chatMessagesRef">
+      <div
+        v-for="(msg, index) in messages"
+        :key="index"
+        :class="['message', msg.role]"
+      >
+        <div class="message-role">{{ msg.role === 'user' ? '鎴�' : 'AI鍔╂墜' }}</div>
+        <div class="message-content">{{ msg.content }}</div>
+      </div>
+      <div v-if="chatLoading" class="message assistant">
+        <div class="message-role">AI鍔╂墜</div>
+        <div class="message-content typing">姝e湪鎬濊�冧腑...</div>
+      </div>
+    </div>
+
+    <!-- 杈撳叆妗� -->
+    <div class="chat-input">
+      <el-input
+        v-model="inputQuestion"
+        placeholder="璇疯緭鍏ラ棶棰�,鎸夊洖杞﹀彂閫�"
+        @keyup.enter="sendMessage"
+        :disabled="chatLoading"
+      >
+        <template #append>
+          <el-button @click="sendMessage" :loading="chatLoading">鍙戦��</el-button>
+        </template>
+      </el-input>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { nextTick } from 'vue';
+import { getToken } from "@/utils/auth";
+
+const messages = ref([]);
+const inputQuestion = ref("");
+const chatLoading = ref(false);
+const memoryId = ref("");
+const chatMessagesRef = ref();
+
+// 鎵撳紑闂瓟寮圭獥
+const openChatDialog = (row) => {
+  currentKnowledgeBase.value = row;
+  chatDialogVisible.value = true;
+  memoryId.value = crypto.randomUUID(); // 鐢熸垚鍞竴浼氳瘽ID
+  messages.value = [];
+  inputQuestion.value = "";
+};
+
+// 鍙戦�佹秷鎭�
+const sendMessage = async () => {
+  if (!inputQuestion.value.trim()) {
+    ElMessage.warning("璇疯緭鍏ラ棶棰�");
+    return;
+  }
+
+  const question = inputQuestion.value.trim();
+
+  // 娣诲姞鐢ㄦ埛娑堟伅
+  messages.value.push({
+    role: 'user',
+    content: question
+  });
+
+  inputQuestion.value = "";
+  chatLoading.value = true;
+
+  // 婊氬姩鍒板簳閮�
+  await nextTick();
+  scrollToBottom();
+
+  try {
+    // 娴佸紡璇锋眰
+    const response = await fetch('/api/ai/knowledge/chat', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Authorization': 'Bearer ' + getToken()
+      },
+      body: JSON.stringify({
+        knowledgeBaseId: currentKnowledgeBase.value.id,
+        memoryId: memoryId.value,
+        question: question
+      })
+    });
+
+    if (!response.ok) {
+      throw new Error('璇锋眰澶辫触');
+    }
+
+    // 澶勭悊SSE娴佸紡鍝嶅簲
+    const reader = response.body.getReader();
+    const decoder = new TextDecoder();
+    let aiContent = '';
+
+    messages.value.push({ role: 'assistant', content: '' });
+
+    while (true) {
+      const { done, value } = await reader.read();
+      if (done) break;
+
+      const text = decoder.decode(value);
+      aiContent += text;
+      messages.value[messages.value.length - 1].content = aiContent;
+
+      // 婊氬姩鍒板簳閮�
+      await nextTick();
+      scrollToBottom();
+    }
+  } catch (error) {
+    console.error("闂瓟璇锋眰澶辫触:", error);
+    ElMessage.error("闂瓟璇锋眰澶辫触,璇风◢鍚庨噸璇�");
+    messages.value.push({
+      role: 'assistant',
+      content: '鎶辨瓑,鍙戠敓浜嗛敊璇�,璇风◢鍚庨噸璇�'
+    });
+  } finally {
+    chatLoading.value = false;
+  }
+};
+
+// 婊氬姩鍒板簳閮�
+const scrollToBottom = () => {
+  if (chatMessagesRef.value) {
+    chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
+  }
+};
+</script>
+
+<style scoped>
+.knowledge-chat {
+  display: flex;
+  flex-direction: column;
+  height: 500px;
+}
+
+.chat-messages {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 16px;
+}
+
+.message {
+  margin-bottom: 16px;
+  max-width: 80%;
+}
+
+.message.user {
+  margin-left: auto;
+  text-align: right;
+}
+
+.message.assistant {
+  margin-right: auto;
+}
+
+.message-role {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+
+.message-content {
+  display: inline-block;
+  padding: 10px 14px;
+  border-radius: 8px;
+  line-height: 1.6;
+  word-wrap: break-word;
+  white-space: pre-wrap;
+}
+
+.message.user .message-content {
+  background: #409eff;
+  color: white;
+}
+
+.message.assistant .message-content {
+  background: white;
+  color: #303133;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.typing {
+  animation: typing 1.5s infinite;
+}
+
+@keyframes typing {
+  0%, 50%, 100% { opacity: 1; }
+  25%, 75% { opacity: 0.5; }
+}
+</style>
+```
+
+---
+
+## 鍏�佸叧閿疄鐜扮粏鑺�
+
+### 6.1 鏂囦欢涓婁紶閰嶇疆
+
+```javascript
+// 涓婁紶鍦板潃
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
+
+// 璇锋眰澶�(蹇呴』鎼哄甫Token)
+const uploadHeaders = {
+  Authorization: "Bearer " + getToken()
+};
+
+// 鏀寔鐨勬枃浠剁被鍨�
+const acceptTypes = '.txt,.md,.docx,.xlsx,.xls,.pdf';
+
+// 鏂囦欢澶у皬闄愬埗
+const maxSize = 50 * 1024 * 1024; // 50MB
+```
+
+### 6.2 鍚戦噺鍖栫姸鎬佽疆璇�
+
+```javascript
+// 寮�濮嬭疆璇㈠悜閲忓寲鐘舵��
+const startVectorStatusPolling = () => {
+  const timer = setInterval(async () => {
+    const res = await getVectorStatus(currentKnowledgeBase.value.id);
+    const hasProcessing = res.data.some(item => item.vectorStatus === 1);
+
+    if (!hasProcessing) {
+      clearInterval(timer);
+    }
+
+    fileList.value = res.data;
+  }, 3000); // 姣�3绉掕疆璇竴娆�
+};
+```
+
+### 6.3 娴佸紡鍝嶅簲澶勭悊
+
+```javascript
+// 浣跨敤 Fetch API 澶勭悊娴佸紡鍝嶅簲
+const response = await fetch('/api/ai/knowledge/chat', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json',
+    'Authorization': 'Bearer ' + getToken()
+  },
+  body: JSON.stringify({
+    knowledgeBaseId: 10,
+    memoryId: "session-xxx",
+    question: "闂鍐呭"
+  })
+});
+
+// 鑾峰彇鍙娴�
+const reader = response.body.getReader();
+const decoder = new TextDecoder();
+
+// 閫愬潡璇诲彇鏁版嵁
+while (true) {
+  const { done, value } = await reader.read();
+  if (done) break;
+
+  const text = decoder.decode(value);
+  // 澶勭悊鏂囨湰鍧�
+  processText(text);
+}
+```
+
+### 6.4 浼氳瘽绠$悊
+
+```javascript
+// 鐢熸垚鍞竴浼氳瘽ID
+const memoryId = crypto.randomUUID();
+
+// 鎴栦娇鐢ㄦ椂闂存埑
+const memoryId = 'kb-chat-' + Date.now();
+
+// 浼氳瘽ID鐢ㄤ簬:
+// 1. 淇濇寔瀵硅瘽涓婁笅鏂�
+// 2. 鏀寔澶氳疆瀵硅瘽
+// 3. 鏌ヨ鍘嗗彶璁板綍
+```
+
+---
+
+## 涓冦�佺姸鎬佺鐞�
+
+### 7.1 鍚戦噺鍖栫姸鎬佸畾涔�
+
+| 鐘舵�佸�� | 鐘舵�佸悕绉� | 璇存槑 | 鏍囩棰滆壊 |
+|--------|----------|------|----------|
+| 0 | 寰呭鐞� | 鏂囦欢宸蹭笂浼�,绛夊緟鍚戦噺鍖栧鐞� | info(鐏拌壊) |
+| 1 | 澶勭悊涓� | 姝e湪杩涜鍚戦噺鍒囩墖澶勭悊 | warning(姗欒壊) |
+| 2 | 宸插畬鎴� | 鍚戦噺鍖栧畬鎴�,鍙繘琛屾绱㈤棶绛� | success(缁胯壊) |
+| 3 | 澶辫触 | 鍚戦噺鍖栧け璐�,闇�閲嶆柊澶勭悊 | danger(绾㈣壊) |
+
+### 7.2 鐭ヨ瘑绫诲瀷閰嶇疆
+
+```javascript
+// 浣跨敤瀛楀吀閰嶇疆鐭ヨ瘑绫诲瀷
+const { knowledge_type } = proxy.useDict("knowledge_type");
+
+// 绀轰緥鏁版嵁
+const knowledgeTypeOptions = [
+  { value: 'contract', label: '鍚堝悓鐭ヨ瘑', elTagType: 'success' },
+  { value: 'approval', label: '瀹℃壒娴佺▼', elTagType: 'warning' },
+  { value: 'solution', label: '瑙e喅鏂规', elTagType: 'primary' },
+  { value: 'experience', label: '缁忛獙鍒嗕韩', elTagType: 'info' },
+  { value: 'guide', label: '鎿嶄綔鎸囧崡', elTagType: 'danger' }
+];
+```
+
+### 7.3 瑙e喅鏁堢巼鏄犲皠
+
+```javascript
+const efficiencyMap = {
+  high: { label: '鏄捐憲鎻愬崌', color: 'success', score: 40, time: '2-3澶�' },
+  medium: { label: '涓�鑸彁鍗�', color: 'warning', score: 25, time: '1-2澶�' },
+  low: { label: '杞诲井鎻愬崌', color: 'info', score: 15, time: '0.5-1澶�' }
+};
+```
+
+---
+
+## 鍏�佹牱寮忚璁�
+
+### 8.1 鏂囦欢绠$悊鏍峰紡
+
+```css
+.file-manager {
+  padding: 20px 0;
+}
+
+.upload-section {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+```
+
+### 8.2 闂瓟鐣岄潰鏍峰紡
+
+```css
+.knowledge-chat {
+  display: flex;
+  flex-direction: column;
+  height: 500px;
+}
+
+.chat-messages {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 16px;
+}
+
+.message {
+  margin-bottom: 16px;
+  max-width: 80%;
+}
+
+.message.user {
+  margin-left: auto;
+  text-align: right;
+}
+
+.message.assistant {
+  margin-right: auto;
+}
+
+.message-content {
+  display: inline-block;
+  padding: 10px 14px;
+  border-radius: 8px;
+  line-height: 1.6;
+  word-wrap: break-word;
+  white-space: pre-wrap;
+}
+
+.message.user .message-content {
+  background: #409eff;
+  color: white;
+}
+
+.message.assistant .message-content {
+  background: white;
+  color: #303133;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+```
+
+---
+
+## 涔濄�佹敞鎰忎簨椤�
+
+### 9.1 鏂囦欢涓婁紶
+
+1. **蹇呴』璋冪敤淇濆瓨鎺ュ彛**: 涓婁紶鎴愬姛鍚庡繀椤昏皟鐢� `/knowledgeBase/file/save` 鎵嶈兘瑙﹀彂鍚戦噺鍖�
+2. **鏂囦欢绫诲瀷闄愬埗**: 鍙敮鎸� txt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df 鏍煎紡
+3. **鏂囦欢澶у皬闄愬埗**: 鍗曟枃浠舵渶澶� 50MB
+4. **寮傛澶勭悊**: 鍚戦噺鍖栨槸寮傛澶勭悊,涓嶄細闃诲鐢ㄦ埛鎿嶄綔
+
+### 9.2 鍚戦噺鍖栫姸鎬�
+
+1. **鐘舵�佽疆璇�**: 寤鸿姣�3-5绉掕疆璇竴娆$姸鎬�
+2. **鍋滄杞**: 褰撴墍鏈夋枃浠剁姸鎬侀兘涓嶆槸"澶勭悊涓�"鏃跺仠姝㈣疆璇�
+3. **澶辫触澶勭悊**: 鐘舵�佷负"澶辫触"鏃跺彲鐐瑰嚮"閲嶆柊澶勭悊"鎸夐挳
+
+### 9.3 鐭ヨ瘑搴撻棶绛�
+
+1. **浼氳瘽ID**: 姣忔鎵撳紑闂瓟寮圭獥闇�瑕佺敓鎴愭柊鐨� memoryId
+2. **娴佸紡澶勭悊**: 浣跨敤 Fetch API 澶勭悊娴佸紡鍝嶅簲,涓嶆敮鎸� axios
+3. **閿欒澶勭悊**: 闇�瑕佸鐞嗙綉缁滈敊璇拰AI鍝嶅簲閿欒
+4. **鑷姩婊氬姩**: 姣忔鏀跺埌鏂版秷鎭嚜鍔ㄦ粴鍔ㄥ埌搴曢儴
+
+### 9.4 鏁版嵁涓�鑷存��
+
+1. **鍒犻櫎鐭ヨ瘑搴�**: 闇�瑕佸悓鏃跺垹闄ゅ叧鑱旂殑鏂囦欢鍜屽悜閲忔暟鎹�
+2. **鍒犻櫎鏂囦欢**: 鍒犻櫎鏂囦欢鏃跺悓姝ュ垹闄ゅ悜閲忓簱涓殑鐩稿叧鍒囩墖
+3. **鍒锋柊鍒楄〃**: 鏂囦欢鎿嶄綔鍚庨渶瑕佸埛鏂扮煡璇嗗簱鍒楄〃,鏇存柊鏂囦欢鏁伴噺鍜屽垏鐗囨暟閲�
+
+---
+
+## 鍗併�佹祴璇曟鏌ユ竻鍗�
+
+### 10.1 鐭ヨ瘑搴撶鐞�
+
+- [ ] 鏂板鐭ヨ瘑搴撴垚鍔�
+- [ ] 缂栬緫鐭ヨ瘑搴撴垚鍔�
+- [ ] 鍒犻櫎鐭ヨ瘑搴撴垚鍔�(鍗曚釜/鎵归噺)
+- [ ] 鎼滅储鍔熻兘姝e父(鎸夋爣棰樸�佺被鍨�)
+- [ ] 鍒嗛〉鍔熻兘姝e父
+- [ ] 瀵煎嚭鍔熻兘姝e父
+
+### 10.2 鏂囦欢涓婁紶
+
+- [ ] 鍗曟枃浠朵笂浼犳垚鍔�
+- [ ] 澶氭枃浠朵笂浼犳垚鍔�
+- [ ] 鏂囦欢绫诲瀷鏍¢獙姝e父
+- [ ] 鏂囦欢澶у皬鏍¢獙姝e父
+- [ ] 淇濆瓨鏂囦欢鍏宠仈鎴愬姛
+- [ ] 鍚戦噺鍖栫姸鎬佹纭樉绀�
+
+### 10.3 鏂囦欢绠$悊
+
+- [ ] 鏌ョ湅鏂囦欢鍒楄〃姝e父
+- [ ] 鍚戦噺鍖栫姸鎬佽疆璇㈡甯�
+- [ ] 閲嶆柊澶勭悊澶辫触鏂囦欢鎴愬姛
+- [ ] 鍒犻櫎鏂囦欢鎴愬姛
+- [ ] 鏂囦欢棰勮/涓嬭浇姝e父
+
+### 10.4 鐭ヨ瘑搴撻棶绛�
+
+- [ ] 闂瓟寮圭獥鎵撳紑姝e父
+- [ ] 鍙戦�侀棶棰樻垚鍔�
+- [ ] 娴佸紡鍝嶅簲鏄剧ず姝e父
+- [ ] 澶氳疆瀵硅瘽姝e父
+- [ ] 鑷姩婊氬姩鍒板簳閮�
+- [ ] 閿欒澶勭悊姝e父
+
+---
+
+## 鍗佷竴銆佸父瑙侀棶棰�
+
+### Q1: 鏂囦欢涓婁紶鍚庢病鏈夎Е鍙戝悜閲忓寲?
+
+**A**: 妫�鏌ユ槸鍚﹁皟鐢ㄤ簡 `/knowledgeBase/file/save` 鎺ュ彛銆備笂浼犳垚鍔熷悗蹇呴』璋冪敤姝ゆ帴鍙f墠鑳借Е鍙戝悜閲忓寲銆�
+
+### Q2: 鍚戦噺鍖栫姸鎬佷竴鐩存槸"澶勭悊涓�"?
+
+**A**: 鍙兘鍘熷洜:
+1. 鍚庡彴鏈嶅姟鏈惎鍔�
+2. Embedding妯″瀷璋冪敤澶辫触
+3. Pinecone杩炴帴澶辫触
+
+寤鸿鏌ョ湅鍚庡彴鏃ュ織鎺掓煡闂銆�
+
+### Q3: 闂瓟杩斿洖绌哄唴瀹�?
+
+**A**: 鍙兘鍘熷洜:
+1. 鐭ヨ瘑搴撲腑娌℃湁鏂囦欢
+2. 鏂囦欢鏈畬鎴愬悜閲忓寲
+3. 妫�绱㈢浉浼煎害浣庝簬闃堝��
+
+寤鸿妫�鏌ユ枃浠舵暟閲忓拰鍚戦噺鍖栫姸鎬併��
+
+### Q4: 娴佸紡鍝嶅簲鏄剧ず涔辩爜?
+
+**A**: 纭繚璇锋眰澶村寘鍚纭殑缂栫爜璁剧疆:
+```javascript
+headers: {
+  'Content-Type': 'application/json'
+}
+```
+
+### Q5: 濡備綍璋冭瘯娴佸紡鎺ュ彛?
+
+**A**: 浣跨敤娴忚鍣ㄥ紑鍙戣�呭伐鍏�:
+1. 鎵撳紑 Network 鏍囩
+2. 鎵惧埌 `/ai/knowledge/chat` 璇锋眰
+3. 鏌ョ湅 Response 鏍囩,鍙互鐪嬪埌娴佸紡杩斿洖鐨勫唴瀹�
+
+---
+
+## 鍗佷簩銆佷紭鍖栧缓璁�
+
+### 12.1 鎬ц兘浼樺寲
+
+1. **铏氭嫙婊氬姩**: 娑堟伅鍒楄〃瓒呰繃100鏉℃椂浣跨敤铏氭嫙婊氬姩
+2. **闃叉姈鑺傛祦**: 鎼滅储杈撳叆浣跨敤闃叉姈,鐘舵�佽疆璇娇鐢ㄨ妭娴�
+3. **鎳掑姞杞�**: 鏂囦欢鍒楄〃浣跨敤鎳掑姞杞�
+
+### 12.2 鐢ㄦ埛浣撻獙浼樺寲
+
+1. **杩涘害鎻愮ず**: 鍚戦噺鍖栨椂鏄剧ず杩涘害鏉�
+2. **蹇嵎閿�**: 鏀寔蹇嵎閿搷浣�(濡� Ctrl+Enter 鍙戦��)
+3. **鍘嗗彶璁板綍**: 鏀寔鏌ョ湅鍘嗗彶闂瓟璁板綍
+4. **瀵煎嚭瀵硅瘽**: 鏀寔瀵煎嚭瀵硅瘽鍐呭
+
+### 12.3 鍔熻兘鎵╁睍
+
+1. **鏂囦欢棰勮**: 鏀寔鍦ㄧ嚎棰勮鏂囦欢鍐呭
+2. **鎵归噺鎿嶄綔**: 鏀寔鎵归噺鍒犻櫎銆佹壒閲忛噸鏂板鐞�
+3. **鍚戦噺鍖栭厤缃�**: 鍏佽鐢ㄦ埛閰嶇疆鍒囩墖澶у皬銆侀噸鍙犲ぇ灏�
+4. **鐩镐技搴﹂槇鍊�**: 鍏佽鐢ㄦ埛璋冩暣妫�绱㈢浉浼煎害闃堝��
+
+---
+
+## 鍗佷笁銆佹洿鏂版棩蹇�
+
+### v1.0.0 (2026-06-08)
+- 鉁� 瀹屾垚鐭ヨ瘑搴揅RUD鍔熻兘
+- 鉁� 瀹屾垚鏂囦欢涓婁紶涓庡悜閲忓寲鍔熻兘
+- 鉁� 瀹屾垚鐭ヨ瘑搴撻棶绛斿姛鑳�
+- 鉁� 瀹屾垚鏂囦欢绠$悊鍔熻兘
+- 鉁� 瀹屾垚鍚戦噺鍖栫姸鎬佹樉绀�
+
+### v1.1.0 (璁″垝涓�)
+- 馃敳 娣诲姞鍘嗗彶璁板綍鏌ヨ
+- 馃敳 娣诲姞鎵归噺鎿嶄綔鍔熻兘
+- 馃敳 娣诲姞鏂囦欢棰勮鍔熻兘
+- 馃敳 浼樺寲鍚戦噺鍖栬繘搴︽樉绀�
\ No newline at end of file
diff --git a/multiple/assets/favicon/KHYYfavicon.ico b/multiple/assets/favicon/KHYYfavicon.ico
new file mode 100644
index 0000000..9982880
--- /dev/null
+++ b/multiple/assets/favicon/KHYYfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/NYfavicon.ico b/multiple/assets/favicon/NYfavicon.ico
new file mode 100644
index 0000000..a0c9f8b
--- /dev/null
+++ b/multiple/assets/favicon/NYfavicon.ico
Binary files differ
diff --git a/multiple/assets/logo/KHYYLogo.png b/multiple/assets/logo/KHYYLogo.png
new file mode 100644
index 0000000..53b479d
--- /dev/null
+++ b/multiple/assets/logo/KHYYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/NYLogo.png b/multiple/assets/logo/NYLogo.png
new file mode 100644
index 0000000..d6eea98
--- /dev/null
+++ b/multiple/assets/logo/NYLogo.png
Binary files differ
diff --git a/src/api/collaborativeApproval/knowledgeBase.js b/src/api/collaborativeApproval/knowledgeBase.js
index b195525..eb44ac2 100644
--- a/src/api/collaborativeApproval/knowledgeBase.js
+++ b/src/api/collaborativeApproval/knowledgeBase.js
@@ -1,55 +1,172 @@
 import request from "@/utils/request";
+import { getToken } from '@/utils/auth';
 
-// 鏌ヨ鐭ヨ瘑搴撳垪琛�
+/**
+ * 鐭ヨ瘑搴撶鐞嗘帴鍙�
+ * 浼犲弬瑙勮寖:
+ * - GET璇锋眰: 浣跨敤 params
+ * - POST璇锋眰: 浣跨敤 data
+ * - DELETE璇锋眰: 浣跨敤 data
+ * - 娴佸紡璇锋眰: 浣跨敤 Fetch API
+ */
+
+// ==================== 鐭ヨ瘑搴揅RUD鎺ュ彛 ====================
+
+/**
+ * 鏌ヨ鐭ヨ瘑搴撳垪琛�
+ * @param {Object} query - 鏌ヨ鍙傛暟
+ * @param {number} query.current - 褰撳墠椤电爜(蹇呭~)
+ * @param {number} query.size - 姣忛〉鏉℃暟(蹇呭~)
+ * @param {string} [query.title] - 鐭ヨ瘑鏍囬(鍙��,妯$硦鎼滅储)
+ * @param {string} [query.type] - 鐭ヨ瘑绫诲瀷(鍙��,绮剧‘鍖归厤)
+ * @returns {Promise}
+ */
 export function listKnowledgeBase(query) {
   return request({
     url: "/knowledgeBase/getList",
     method: "get",
-    params: query,
+    params: query,  // GET璇锋眰浣跨敤params
   });
 }
 
-// 鏌ヨ鐭ヨ瘑搴撹缁�
-// export function getKnowledgeBase(knowledgeBaseId) {
-//   return request({
-//     url: "/collaborativeApproval/knowledgeBase/" + knowledgeBaseId,
-//     method: "get",
-//   });
-// }
-
-// 鏂板鐭ヨ瘑搴�
+/**
+ * 鏂板鐭ヨ瘑搴�
+ * @param {Object} data - 鐭ヨ瘑搴撴暟鎹�
+ * @param {string} data.title - 鐭ヨ瘑鏍囬(蹇呭~)
+ * @param {string} data.type - 鐭ヨ瘑绫诲瀷(蹇呭~)
+ * @param {string} [data.scenario] - 閫傜敤鍦烘櫙(鍙��)
+ * @param {string} [data.efficiency] - 瑙e喅鏁堢巼(鍙��)
+ * @param {string} data.problem - 闂鎻忚堪(蹇呭~)
+ * @param {string} data.solution - 瑙e喅鏂规(蹇呭~)
+ * @param {string} [data.keyPoints] - 鍏抽敭瑕佺偣(鍙��)
+ * @param {string} [data.creator] - 鍒涘缓浜�(鍙��)
+ * @param {number} [data.usageCount=0] - 浣跨敤娆℃暟(鍙��)
+ * @returns {Promise}
+ */
 export function addKnowledgeBase(data) {
   return request({
     url: "/knowledgeBase/add",
     method: "post",
-    data: data,
+    data: data,  // POST璇锋眰浣跨敤data
   });
 }
 
-// 淇敼鐭ヨ瘑搴�
+/**
+ * 淇敼鐭ヨ瘑搴�
+ * @param {Object} data - 鐭ヨ瘑搴撴暟鎹�
+ * @param {number} data.id - 鐭ヨ瘑搴揑D(蹇呭~)
+ * @param {string} data.title - 鐭ヨ瘑鏍囬(蹇呭~)
+ * @param {string} data.type - 鐭ヨ瘑绫诲瀷(蹇呭~)
+ * @returns {Promise}
+ */
 export function updateKnowledgeBase(data) {
   return request({
     url: "/knowledgeBase/update",
     method: "post",
-    data: data,
+    data: data,  // POST璇锋眰浣跨敤data
   });
 }
 
-// 鍒犻櫎鐭ヨ瘑搴�
-export function delKnowledgeBase(query) {
+/**
+ * 鍒犻櫎鐭ヨ瘑搴�(鏀寔鎵归噺鍒犻櫎)
+ * @param {number[]} ids - 鐭ヨ瘑搴揑D鏁扮粍
+ * @returns {Promise}
+ */
+export function delKnowledgeBase(ids) {
   return request({
     url: "/knowledgeBase/delete",
     method: "delete",
-    data: query,
+    data: ids,  // DELETE璇锋眰浣跨敤data浼犻�掓暟缁�
   });
 }
 
-// 鎵归噺鍒犻櫎鐭ヨ瘑搴�
-export function delKnowledgeBaseBatch(knowledgeBaseIds) {
+// ==================== 鏂囦欢绠$悊鎺ュ彛 ====================
+
+/**
+ * 鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��
+ * @param {number} knowledgeBaseId - 鐭ヨ瘑搴揑D
+ * @returns {Promise} 杩斿洖鏂囦欢鍒楄〃鍙婂悜閲忓寲鐘舵��
+ */
+export function getVectorStatus(knowledgeBaseId) {
   return request({
-    url: "/knowledgeBase/batch",
-    method: "delete",
-    data: knowledgeBaseIds,
+    url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
+    method: "get",
   });
 }
 
+/**
+ * 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�(瑙﹀彂鍚戦噺鍖�)
+ * @param {Object} data - 鏂囦欢鍏宠仈鏁版嵁
+ * @param {number} data.knowledgeBaseId - 鐭ヨ瘑搴揑D(蹇呭~)
+ * @param {number[]} data.storageBlobIds - 鏂囦欢blob ID鏁扮粍(蹇呭~)
+ * @returns {Promise}
+ */
+export function saveKnowledgeBaseFiles(data) {
+  return request({
+    url: "/knowledgeBase/file/save",
+    method: "post",
+    data: data,  // POST璇锋眰浣跨敤data
+  });
+}
+
+/**
+ * 鍒犻櫎鐭ヨ瘑搴撴枃浠�
+ * @param {number[]} ids - 鍚戦噺璁板綍ID鏁扮粍
+ * @returns {Promise}
+ */
+export function deleteKnowledgeBaseFile(ids) {
+  return request({
+    url: "/knowledgeBase/file/delete",
+    method: "delete",
+    data: ids,  // DELETE璇锋眰浣跨敤data浼犻�掓暟缁�
+  });
+}
+
+/**
+ * 閲嶆柊鍚戦噺鍖栨枃浠�
+ * @param {number} vectorId - 鍚戦噺璁板綍ID
+ * @returns {Promise}
+ */
+export function reprocessVector(vectorId) {
+  return request({
+    url: `/knowledgeBase/vector/reprocess/${vectorId}`,
+    method: "post",
+  });
+}
+
+// ==================== 鐭ヨ瘑闂瓟鎺ュ彛 ====================
+
+/**
+ * 鐭ヨ瘑搴撻棶绛�(娴佸紡)
+ * 鍚庣鎺ュ彛: POST /ai/knowledge/chat
+ * 鍝嶅簲绫诲瀷: text/stream;charset=utf-8 (Spring Flux<String>)
+ *
+ * @param {Object} data - 闂瓟鍙傛暟
+ * @param {number} data.knowledgeBaseId - 鐭ヨ瘑搴揑D(蹇呭~)
+ * @param {string} data.memoryId - 浼氳瘽ID(蹇呭~,鐢ㄤ簬淇濇寔涓婁笅鏂�)
+ * @param {string} data.question - 鐢ㄦ埛闂(蹇呭~)
+ * @returns {Promise<Response>} 杩斿洖Fetch Response瀵硅薄
+ */
+export function knowledgeChat(data) {
+  const token = getToken();
+  return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      'Authorization': 'Bearer ' + token
+    },
+    body: JSON.stringify(data)
+  });
+}
+
+/**
+ * 鏌ヨ鐭ヨ瘑搴撻棶绛斿巻鍙�
+ * @param {string} memoryId - 浼氳瘽ID
+ * @returns {Promise}
+ */
+export function getKnowledgeHistory(memoryId) {
+  return request({
+    url: `/ai/knowledge/history/${memoryId}`,
+    method: "get",
+  });
+}
diff --git a/src/api/procurementManagement/paymentLedger.js b/src/api/procurementManagement/paymentLedger.js
index 6c5d9de..537ba9f 100644
--- a/src/api/procurementManagement/paymentLedger.js
+++ b/src/api/procurementManagement/paymentLedger.js
@@ -18,3 +18,12 @@
     params,
   });
 }
+
+/** 浠樻鍙拌处 - 浠樻鐧昏鍒楄〃 */
+export function registrationList(params) {
+  return request({
+    url: "/purchase/report/registration",
+    method: "get",
+    params,
+  });
+}
diff --git a/src/components/PIMTable/PIMTable.vue b/src/components/PIMTable/PIMTable.vue
index d63c197..74615af 100644
--- a/src/components/PIMTable/PIMTable.vue
+++ b/src/components/PIMTable/PIMTable.vue
@@ -11,7 +11,7 @@
             :row-key="rowKey"
             :style="tableStyle"
             tooltip-effect="dark"
-            :tooltip-options="{ appendTo: 'body' }"
+            :tooltip-options="{ popperOptions: { strategy: 'absolute' } }"
             :expand-row-keys="expandRowKeys"
             :show-summary="isShowSummary"
             :summary-method="summaryMethod"
diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue
index 43fee33..f50d06b 100644
--- a/src/views/collaborativeApproval/knowledgeBase/index.vue
+++ b/src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -225,18 +225,167 @@
         </div>
       </div>
     </FormDialog>
+
+    <!-- 鏂囦欢绠$悊寮圭獥 -->
+    <FormDialog
+      v-model="filesDialogVisible"
+      title="鏂囦欢绠$悊"
+      :width="'900px'"
+      @close="closeFilesDialog"
+      @confirm="closeFilesDialog"
+      @cancel="closeFilesDialog"
+    >
+      <div class="file-manager">
+        <!-- 鏂囦欢涓婁紶 -->
+        <div class="upload-section">
+          <el-upload
+            :action="uploadUrl"
+            :headers="uploadHeaders"
+            :on-success="handleUploadSuccess"
+            :on-error="handleUploadError"
+            :before-upload="beforeUpload"
+            name="files"
+            multiple
+            :show-file-list="false"
+            accept=".txt,.md,.docx,.xlsx,.xls,.pdf"
+          >
+            <el-button type="primary">涓婁紶鏂囦欢</el-button>
+          </el-upload>
+          <el-button
+            type="success"
+            @click="saveFiles"
+            :disabled="uploadedBlobIds.length === 0"
+            :loading="savingFiles"
+            style="margin-left: 10px"
+          >
+            淇濆瓨鏂囦欢鍏宠仈
+          </el-button>
+          <el-button
+            v-if="uploadedBlobIds.length > 0"
+            type="text"
+            @click="clearUploadedFiles"
+            style="margin-left: 10px"
+          >
+            娓呯┖寰呬繚瀛樺垪琛�
+          </el-button>
+        </div>
+
+        <!-- 寰呬繚瀛樼殑鏂囦欢鍒楄〃 -->
+        <div v-if="uploadedBlobIds.length > 0" class="uploaded-list">
+          <div class="uploaded-tip">
+            <el-icon style="color: #409eff"><InfoFilled /></el-icon>
+            <span>宸蹭笂浼� {{ uploadedBlobIds.length }} 涓枃浠�,璇风偣鍑�"淇濆瓨鏂囦欢鍏宠仈"鎸夐挳瑙﹀彂鍚戦噺鍖栧鐞�</span>
+          </div>
+        </div>
+
+        <!-- 鏂囦欢鍒楄〃涓庡悜閲忓寲鐘舵�� -->
+        <el-table :data="fileList" style="margin-top: 20px" border>
+          <el-table-column prop="fileName" label="鏂囦欢鍚�" show-overflow-tooltip />
+          <el-table-column prop="fileType" label="鏂囦欢绫诲瀷" width="100" />
+          <el-table-column label="鍚戦噺鍖栫姸鎬�" width="120">
+            <template #default="{ row }">
+              <el-tag :type="getStatusType(row.vectorStatus)">
+                {{ getStatusText(row.vectorStatus) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="chunkCount" label="鍒囩墖鏁�" width="100" align="center" />
+          <el-table-column label="閿欒淇℃伅" width="200" show-overflow-tooltip>
+            <template #default="{ row }">
+              <span v-if="row.vectorError" style="color: #f56c6c">{{ row.vectorError }}</span>
+              <span v-else style="color: #909399">-</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="createTime" label="涓婁紶鏃堕棿" width="180" />
+          <el-table-column label="鎿嶄綔" width="150" align="center">
+            <template #default="{ row }">
+              <el-button
+                v-if="row.vectorStatus === 3"
+                type="text"
+                @click="reprocessFile(row)"
+              >
+                閲嶆柊澶勭悊
+              </el-button>
+              <el-button type="text" @click="deleteFile(row)" style="color: #f56c6c">
+                鍒犻櫎
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </FormDialog>
+
+    <!-- 鐭ヨ瘑搴撻棶绛斿脊绐� -->
+    <FormDialog
+      v-model="chatDialogVisible"
+      title="鐭ヨ瘑搴撻棶绛�"
+      :width="'800px'"
+      @close="closeChatDialog"
+      @confirm="closeChatDialog"
+      @cancel="closeChatDialog"
+    >
+      <div class="knowledge-chat">
+        <div class="chat-header">
+          <el-tag type="success">褰撳墠鐭ヨ瘑搴�: {{ currentKnowledgeBase?.title }}</el-tag>
+        </div>
+
+        <!-- 瀵硅瘽鍖哄煙 -->
+        <div class="chat-messages" ref="chatMessagesRef">
+          <div
+            v-for="(msg, index) in messages"
+            :key="index"
+            :class="['message', msg.role]"
+          >
+            <div class="message-role">{{ msg.role === 'user' ? '鎴�' : 'AI鍔╂墜' }}</div>
+            <div class="message-content">{{ msg.content }}</div>
+          </div>
+          <div v-if="chatLoading" class="message assistant">
+            <div class="message-role">AI鍔╂墜</div>
+            <div class="message-content typing">姝e湪鎬濊�冧腑...</div>
+          </div>
+        </div>
+
+        <!-- 杈撳叆妗� -->
+        <div class="chat-input">
+          <el-input
+            v-model="inputQuestion"
+            placeholder="璇疯緭鍏ラ棶棰�,鎸夊洖杞﹀彂閫�(Ctrl+Enter蹇嵎鍙戦��)"
+            @keyup.enter="sendMessage"
+            :disabled="chatLoading"
+          >
+            <template #append>
+              <el-button @click="sendMessage" :loading="chatLoading">鍙戦��</el-button>
+            </template>
+          </el-input>
+          <div class="chat-actions">
+            <el-button type="text" size="small" @click="clearMessages">娓呯┖瀵硅瘽</el-button>
+          </div>
+        </div>
+      </div>
+    </FormDialog>
   </div>
 </template>
 
 <script setup>
-import { Search } from "@element-plus/icons-vue";
-import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch } from "vue";
+import { Search, InfoFilled } from "@element-plus/icons-vue";
+import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch, nextTick } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import PIMTable from "@/components/PIMTable/PIMTable.vue";
 import FormDialog from '@/components/Dialog/FormDialog.vue';
-import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js";
+import {
+  listKnowledgeBase,
+  delKnowledgeBase,
+  addKnowledgeBase,
+  updateKnowledgeBase,
+  getVectorStatus,
+  reprocessVector,
+  saveKnowledgeBaseFiles,
+  deleteKnowledgeBaseFile,
+  knowledgeChat
+} from "@/api/collaborativeApproval/knowledgeBase.js";
 import useUserStore from '@/store/modules/user';
 import { userListNoPageByTenantId } from '@/api/system/user.js';
+import { getToken } from "@/utils/auth";
 
 // 琛ㄥ崟楠岃瘉瑙勫垯
 const rules = {
@@ -283,7 +432,18 @@
   dialogTitle: "",
   dialogType: "add",
   viewDialogVisible: false,
-  currentKnowledge: {}
+  currentKnowledge: {},
+  filesDialogVisible: false,
+  currentKnowledgeBase: null,
+  fileList: [],
+  uploadedBlobIds: [],
+  savingFiles: false,
+  vectorStatusTimer: null, // 鍚戦噺鍖栫姸鎬佽疆璇㈠畾鏃跺櫒
+  chatDialogVisible: false,
+  messages: [],
+  inputQuestion: "",
+  chatLoading: false,
+  memoryId: ""
 });
 
 const {
@@ -297,7 +457,18 @@
   dialogTitle,
   dialogType,
   viewDialogVisible,
-  currentKnowledge
+  currentKnowledge,
+  filesDialogVisible,
+  currentKnowledgeBase,
+  fileList,
+  uploadedBlobIds,
+  savingFiles,
+  vectorStatusTimer,
+  chatDialogVisible,
+  messages,
+  inputQuestion,
+  chatLoading,
+  memoryId
 } = toRefs(data);
 
 // 琛ㄥ崟寮曠敤
@@ -305,6 +476,12 @@
 // 鐢ㄦ埛鐩稿叧
 const userStore = useUserStore();
 const userList = ref([]);
+// 鑱婂ぉ娑堟伅瀹瑰櫒寮曠敤
+const chatMessagesRef = ref();
+
+// 鏂囦欢涓婁紶鐩稿叧
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
+const uploadHeaders = { Authorization: "Bearer " + getToken() };
 
 // 琛ㄦ牸鍒楅厤缃�
 const tableColumn = ref([
@@ -352,6 +529,18 @@
     }
   },
   {
+    label: "鏂囦欢鏁伴噺",
+    prop: "fileCount",
+    width: 100,
+    align: "center"
+  },
+  {
+    label: "鍒囩墖鏁伴噺",
+    prop: "totalChunkCount",
+    width: 100,
+    align: "center"
+  },
+  {
     label: "浣跨敤娆℃暟",
     prop: "usageCount",
     width: 100,
@@ -379,6 +568,20 @@
         type: "text",
         clickFun: (row) => {
           openForm("edit", row);
+        }
+      },
+      {
+        name: "鏂囦欢",
+        type: "text",
+        clickFun: (row) => {
+          openFilesDialog(row);
+        }
+      },
+      {
+        name: "闂瓟",
+        type: "text",
+        clickFun: (row) => {
+          openChatDialog(row);
         }
       },
       {
@@ -422,21 +625,31 @@
 
 const getList = () => {
   tableLoading.value = true;
-  listKnowledgeBase({...page.value, ...searchForm.value})
+
+  // 鉁� GET璇锋眰浣跨敤params浼犲弬
+  listKnowledgeBase({
+    current: page.value.current,
+    size: page.value.size,
+    title: searchForm.value.title,
+    type: searchForm.value.type
+  })
   .then(res => {
     tableLoading.value = false;
     page.value.total = res.data.total;
-    // 濡傛灉褰撳墠椤垫暟瓒呰繃鎬婚〉鏁帮紝閲嶇疆鍒扮1椤靛苟閲嶆柊鏌ヨ
+
+    // 濡傛灉褰撳墠椤垫暟瓒呰繃鎬婚〉鏁�,閲嶇疆鍒扮1椤靛苟閲嶆柊鏌ヨ
     const maxPage = Math.ceil(res.data.total / page.value.size) || 1;
     if (page.value.current > maxPage && maxPage > 0) {
       page.value.current = 1;
-      // 閲嶆柊鏌ヨ绗�1椤垫暟鎹�
       return getList();
     }
+
     tableData.value = res.data.records;
-  }).catch(err => {
-    tableLoading.value = false;
   })
+  .catch(err => {
+    tableLoading.value = false;
+    console.error("鏌ヨ鐭ヨ瘑搴撳垪琛ㄥけ璐�:", err);
+  });
 };
 
 // 鍒嗛〉澶勭悊
@@ -611,27 +824,47 @@
 const submitForm = async () => {
   try {
     await formRef.value.validate();
+
+    // 鉁� POST璇锋眰浣跨敤data浼犲弬,鏄庣‘鍙傛暟缁撴瀯
+    const formData = {
+      title: form.value.title,
+      type: form.value.type,
+      scenario: form.value.scenario || "",
+      efficiency: form.value.efficiency || "",
+      problem: form.value.problem,
+      solution: form.value.solution,
+      keyPoints: form.value.keyPoints || "",
+      creator: form.value.creator || "",
+      usageCount: form.value.usageCount || 0
+    };
+
     if (dialogType.value === "add") {
       // 鏂板鐭ヨ瘑
-      addKnowledgeBase({...form.value}).then(res => {
+      addKnowledgeBase(formData).then(res => {
         if(res.code == 200){
           ElMessage.success("娣诲姞鎴愬姛");
           closeKnowledgeDialog();
           getList();
         }
       }).catch(err => {
-        ElMessage.error(err.msg);
-      })
+        console.error("娣诲姞鐭ヨ瘑搴撳け璐�:", err);
+        ElMessage.error(err.msg || "娣诲姞澶辫触");
+      });
     } else {
-      updateKnowledgeBase({...form.value}).then(res => {
+      // 鏇存柊鐭ヨ瘑 - 娣诲姞id鍙傛暟
+      updateKnowledgeBase({
+        id: form.value.id,
+        ...formData
+      }).then(res => {
         if(res.code == 200){
           ElMessage.success("鏇存柊鎴愬姛");
           closeKnowledgeDialog();
           getList();
         }
       }).catch(err => {
-        ElMessage.error(err.msg);
-      })
+        console.error("鏇存柊鐭ヨ瘑搴撳け璐�:", err);
+        ElMessage.error(err.msg || "鏇存柊澶辫触");
+      });
     }
   } catch (error) {
     console.error("琛ㄥ崟楠岃瘉澶辫触:", error);
@@ -645,19 +878,22 @@
     return;
   }
 
-  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鍒犻櫎", {
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄�,鏄惁纭鍒犻櫎?", "鍒犻櫎", {
     confirmButtonText: "纭",
     cancelButtonText: "鍙栨秷",
     type: "warning",
   }).then(() => {
-    // console.log(selectedIds.value);
+    // 鉁� DELETE璇锋眰浣跨敤data浼犻�扞D鏁扮粍
     delKnowledgeBase(selectedIds.value).then(res => {
       if(res.code == 200){
         ElMessage.success("鍒犻櫎鎴愬姛");
         selectedIds.value = [];
         getList();
       }
-    })
+    }).catch(err => {
+      console.error("鍒犻櫎鐭ヨ瘑搴撳け璐�:", err);
+      ElMessage.error(err.msg || "鍒犻櫎澶辫触");
+    });
   }).catch(() => {
     // 鐢ㄦ埛鍙栨秷
   });
@@ -680,6 +916,364 @@
 const handleExport = () => {
   proxy.download('/knowledgeBase/export', { ...searchForm.value }, '鐭ヨ瘑搴�.xlsx')
 }
+
+// ============ 鏂囦欢绠$悊鐩稿叧 ============
+
+// 鎵撳紑鏂囦欢绠$悊寮圭獥
+const openFilesDialog = (row) => {
+  currentKnowledgeBase.value = row;
+  filesDialogVisible.value = true;
+  loadFileList();
+};
+
+// 鍔犺浇鏂囦欢鍒楄〃
+const loadFileList = async () => {
+  if (!currentKnowledgeBase.value?.id) return;
+
+  try {
+    const res = await getVectorStatus(currentKnowledgeBase.value.id);
+    fileList.value = res.data || [];
+
+    // 妫�鏌ユ槸鍚︽湁澶勭悊涓殑鏂囦欢,濡傛灉鏈夊垯鍚姩杞
+    const hasProcessing = res.data.some(item => item.vectorStatus === 1);
+    if (hasProcessing && !vectorStatusTimer.value) {
+      startVectorStatusPolling();
+    } else if (!hasProcessing && vectorStatusTimer.value) {
+      stopVectorStatusPolling();
+    }
+  } catch (error) {
+    console.error("鍔犺浇鏂囦欢鍒楄〃澶辫触:", error);
+    ElMessage.error("鍔犺浇鏂囦欢鍒楄〃澶辫触");
+  }
+};
+
+// 寮�濮嬭疆璇㈠悜閲忓寲鐘舵��
+const startVectorStatusPolling = () => {
+  vectorStatusTimer.value = setInterval(async () => {
+    try {
+      const res = await getVectorStatus(currentKnowledgeBase.value.id);
+      fileList.value = res.data || [];
+
+      // 妫�鏌ユ槸鍚﹁繕鏈夊鐞嗕腑鐨勬枃浠�
+      const hasProcessing = res.data.some(item => item.vectorStatus === 1);
+      if (!hasProcessing) {
+        stopVectorStatusPolling();
+        ElMessage.success("鎵�鏈夋枃浠跺悜閲忓寲澶勭悊瀹屾垚");
+      }
+    } catch (error) {
+      console.error("杞鍚戦噺鍖栫姸鎬佸け璐�:", error);
+      stopVectorStatusPolling();
+    }
+  }, 3000); // 姣�3绉掕疆璇竴娆�
+};
+
+// 鍋滄杞鍚戦噺鍖栫姸鎬�
+const stopVectorStatusPolling = () => {
+  if (vectorStatusTimer.value) {
+    clearInterval(vectorStatusTimer.value);
+    vectorStatusTimer.value = null;
+  }
+};
+
+// 涓婁紶鍓嶆牎楠�
+const beforeUpload = (file) => {
+  const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf'];
+  const fileName = file.name.toLowerCase();
+  const isAllowed = allowedTypes.some(type => fileName.endsWith(type));
+
+  if (!isAllowed) {
+    ElMessage.error('鍙敮鎸� txt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df 鏍煎紡鐨勬枃浠�');
+    return false;
+  }
+
+  const isLt50M = file.size / 1024 / 1024 < 50;
+  if (!isLt50M) {
+    ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 50MB');
+    return false;
+  }
+
+  return true;
+};
+
+// 涓婁紶鎴愬姛
+const handleUploadSuccess = (response, file) => {
+  console.log("涓婁紶鍝嶅簲:", response);  // 璋冭瘯鏃ュ織
+
+  if (response.code === 200) {
+    // 鉁� 鍚庣杩斿洖鐨勬槸 List<StorageBlobVO>,鎵�浠ata鏄暟缁�
+    if (Array.isArray(response.data) && response.data.length > 0) {
+      // 鍙栨暟缁勭涓�涓厓绱犵殑id
+      const blobId = response.data[0].id;
+      if (blobId) {
+        uploadedBlobIds.value.push(blobId);
+        ElMessage.success(`鏂囦欢 ${file.name} 涓婁紶鎴愬姛`);
+      } else {
+        console.error("涓婁紶鍝嶅簲涓湭鎵惧埌id:", response.data[0]);
+        ElMessage.error("涓婁紶澶辫触: 鏈幏鍙栧埌鏂囦欢ID");
+      }
+    } else {
+      console.error("涓婁紶鍝嶅簲鏍煎紡閿欒:", response);
+      ElMessage.error("涓婁紶澶辫触: 鍝嶅簲鏍煎紡閿欒");
+    }
+  } else {
+    ElMessage.error(response.msg || "涓婁紶澶辫触");
+  }
+};
+
+// 涓婁紶澶辫触
+const handleUploadError = (error, file) => {
+  ElMessage.error(`鏂囦欢 ${file.name} 涓婁紶澶辫触`);
+};
+
+// 淇濆瓨鏂囦欢鍏宠仈
+const saveFiles = async () => {
+  // 鍙傛暟鏍¢獙
+  if (!currentKnowledgeBase.value?.id) {
+    ElMessage.error("鐭ヨ瘑搴撲俊鎭紓甯�");
+    return;
+  }
+
+  if (uploadedBlobIds.value.length === 0) {
+    ElMessage.warning("璇峰厛涓婁紶鏂囦欢");
+    return;
+  }
+
+  savingFiles.value = true;
+
+  try {
+    // 鉁� POST璇锋眰浣跨敤data浼犲弬,鏄庣‘鍙傛暟缁撴瀯
+    await saveKnowledgeBaseFiles({
+      knowledgeBaseId: currentKnowledgeBase.value.id,  // 鐭ヨ瘑搴揑D
+      storageBlobIds: uploadedBlobIds.value             // 鏂囦欢blob ID鏁扮粍
+    });
+
+    ElMessage.success("鏂囦欢鍏宠仈淇濆瓨鎴愬姛,姝e湪鍚庡彴澶勭悊鍚戦噺鍖�");
+    uploadedBlobIds.value = [];
+
+    // 寤惰繜鍒锋柊鏂囦欢鍒楄〃,缁欏悗鍙板鐞嗘椂闂�
+    setTimeout(() => {
+      loadFileList();
+    }, 1000);
+  } catch (error) {
+    console.error("淇濆瓨鏂囦欢鍏宠仈澶辫触:", error);
+    ElMessage.error("淇濆瓨鏂囦欢鍏宠仈澶辫触");
+  } finally {
+    savingFiles.value = false;
+  }
+};
+
+// 閲嶆柊澶勭悊鍚戦噺鍖栫殑鏂囦欢
+const reprocessFile = async (row) => {
+  try {
+    await reprocessVector(row.id);
+    ElMessage.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+    // 寤惰繜鍒锋柊
+    setTimeout(() => {
+      loadFileList();
+    }, 1000);
+  } catch (error) {
+    console.error("閲嶆柊澶勭悊澶辫触:", error);
+    ElMessage.error("閲嶆柊澶勭悊澶辫触");
+  }
+};
+
+// 娓呯┖寰呬繚瀛樼殑鏂囦欢鍒楄〃
+const clearUploadedFiles = () => {
+  uploadedBlobIds.value = [];
+  ElMessage.success("宸叉竻绌哄緟淇濆瓨鏂囦欢鍒楄〃");
+};
+
+// 鍒犻櫎鏂囦欢
+const deleteFile = async (row) => {
+  try {
+    await ElMessageBox.confirm(
+      "纭畾瑕佸垹闄よ鏂囦欢鍚�?鍒犻櫎鍚庡皢鏃犳硶鎭㈠鍚戦噺鏁版嵁",
+      "鍒犻櫎纭",
+      {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+      }
+    );
+
+    // 鉁� DELETE璇锋眰浣跨敤data浼犻�扞D鏁扮粍
+    await deleteKnowledgeBaseFile([row.id]);  // 娉ㄦ剰: row.id鏄悜閲忚褰旾D,涓嶆槸storageBlobId
+    ElMessage.success("鍒犻櫎鎴愬姛");
+    loadFileList();
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error("鍒犻櫎鏂囦欢澶辫触:", error);
+      ElMessage.error("鍒犻櫎鏂囦欢澶辫触");
+    }
+  }
+};
+
+// 鐘舵�佹枃鏈槧灏�
+const getStatusText = (status) => {
+  const map = {
+    0: '寰呭鐞�',
+    1: '澶勭悊涓�',
+    2: '宸插畬鎴�',
+    3: '澶辫触'
+  };
+  return map[status] || '鏈煡';
+};
+
+// 鐘舵�佹爣绛剧被鍨嬫槧灏�
+const getStatusType = (status) => {
+  const map = {
+    0: 'info',
+    1: 'warning',
+    2: 'success',
+    3: 'danger'
+  };
+  return map[status] || 'info';
+};
+
+// 鍏抽棴鏂囦欢绠$悊寮圭獥
+const closeFilesDialog = () => {
+  filesDialogVisible.value = false;
+  currentKnowledgeBase.value = null;
+  fileList.value = [];
+  uploadedBlobIds.value = [];
+  stopVectorStatusPolling(); // 鍋滄杞
+  getList(); // 鍒锋柊涓诲垪琛�,鏇存柊鏂囦欢鏁伴噺
+};
+
+// ============ 鐭ヨ瘑搴撻棶绛旂浉鍏� ============
+
+// 鐢熸垚UUID鐨刦allback鏂规
+const generateUUID = () => {
+  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
+    return crypto.randomUUID();
+  }
+  // fallback: 鍏煎涓嶆敮鎸� crypto.randomUUID 鐨勭幆澧�
+  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+    const r = Math.random() * 16 | 0;
+    const v = c === 'x' ? r : (r & 0x3 | 0x8);
+    return v.toString(16);
+  });
+};
+
+// 鎵撳紑闂瓟寮圭獥
+const openChatDialog = (row) => {
+  currentKnowledgeBase.value = row;
+  chatDialogVisible.value = true;
+  memoryId.value = generateUUID();
+  messages.value = [];
+  inputQuestion.value = "";
+};
+
+// 鍙戦�佹秷鎭�
+const sendMessage = async () => {
+  // 鍙傛暟鏍¢獙
+  if (!inputQuestion.value.trim()) {
+    ElMessage.warning("璇疯緭鍏ラ棶棰�");
+    return;
+  }
+
+  if (!currentKnowledgeBase.value?.id) {
+    ElMessage.error("鐭ヨ瘑搴撲俊鎭紓甯�");
+    return;
+  }
+
+  const question = inputQuestion.value.trim();
+
+  // 娣诲姞鐢ㄦ埛娑堟伅
+  messages.value.push({
+    role: 'user',
+    content: question
+  });
+
+  inputQuestion.value = "";
+  chatLoading.value = true;
+
+  // 婊氬姩鍒板簳閮�
+  await nextTick();
+  scrollToBottom();
+
+  try {
+    // 鉁� 娴佸紡璇锋眰浣跨敤Fetch API
+    const response = await knowledgeChat({
+      knowledgeBaseId: currentKnowledgeBase.value.id,  // 鐭ヨ瘑搴揑D
+      memoryId: memoryId.value,                         // 浼氳瘽ID
+      question: question                                // 鐢ㄦ埛闂
+    });
+
+    if (!response.ok) {
+      const errorText = await response.text();
+      throw new Error(errorText || '璇锋眰澶辫触');
+    }
+
+    // 鉁� 鍚庣杩斿洖 text/stream;charset=utf-8
+    const reader = response.body.getReader();
+    const decoder = new TextDecoder();
+    let aiContent = '';
+
+    messages.value.push({ role: 'assistant', content: '' });
+
+    while (true) {
+      const { done, value } = await reader.read();
+      if (done) break;
+
+      const text = decoder.decode(value, { stream: true });  // 鉁� 娣诲姞stream閫夐」
+      aiContent += text;
+      messages.value[messages.value.length - 1].content = aiContent;
+
+      // 婊氬姩鍒板簳閮�
+      await nextTick();
+      scrollToBottom();
+    }
+
+    // 濡傛灉AI杩斿洖绌哄唴瀹�,鏄剧ず鎻愮ず
+    if (!aiContent.trim()) {
+      messages.value[messages.value.length - 1].content = '鎶辨瓑,鐭ヨ瘑搴撲腑鏈壘鍒扮浉鍏冲唴瀹�,璇峰皾璇曞叾浠栭棶棰樸��';
+    }
+  } catch (error) {
+    console.error("闂瓟璇锋眰澶辫触:", error);
+    ElMessage.error("闂瓟璇锋眰澶辫触,璇风◢鍚庨噸璇�");
+    messages.value.push({
+      role: 'assistant',
+      content: '鎶辨瓑,鍙戠敓浜嗛敊璇�,璇风◢鍚庨噸璇�'
+    });
+  } finally {
+    chatLoading.value = false;
+  }
+};
+
+// 娓呯┖瀵硅瘽
+const clearMessages = () => {
+  ElMessageBox.confirm(
+    "纭畾瑕佹竻绌烘墍鏈夊璇濊褰曞悧?",
+    "娓呯┖纭",
+    {
+      confirmButtonText: "纭畾",
+      cancelButtonText: "鍙栨秷",
+      type: "warning"
+    }
+  ).then(() => {
+    messages.value = [];
+    memoryId.value = generateUUID(); // 閲嶆柊鐢熸垚浼氳瘽ID
+    ElMessage.success("瀵硅瘽宸叉竻绌�");
+  }).catch(() => {
+    // 鐢ㄦ埛鍙栨秷
+  });
+};
+
+// 婊氬姩鍒板簳閮�
+const scrollToBottom = () => {
+  if (chatMessagesRef.value) {
+    chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
+  }
+};
+
+// 鍏抽棴闂瓟寮圭獥
+const closeChatDialog = () => {
+  chatDialogVisible.value = false;
+  currentKnowledgeBase.value = null;
+  messages.value = [];
+  inputQuestion.value = "";
+};
 </script>
 
 <style scoped>
@@ -755,4 +1349,113 @@
   font-size: 14px;
   color: #909399;
 }
+
+/* 鏂囦欢绠$悊鏍峰紡 */
+.file-manager {
+  padding: 20px 0;
+}
+
+.upload-section {
+  display: flex;
+  align-items: center;
+}
+
+.uploaded-list {
+  margin-top: 16px;
+  padding: 12px;
+  background: #f0f9ff;
+  border-radius: 6px;
+  border: 1px solid #b3d8ff;
+}
+
+.uploaded-tip {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: #409eff;
+  font-size: 14px;
+}
+
+/* 鐭ヨ瘑搴撻棶绛旀牱寮� */
+.knowledge-chat {
+  display: flex;
+  flex-direction: column;
+  height: 500px;
+}
+
+.chat-header {
+  margin-bottom: 16px;
+}
+
+.chat-messages {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 16px;
+}
+
+.message {
+  margin-bottom: 16px;
+  max-width: 80%;
+}
+
+.message.user {
+  margin-left: auto;
+  text-align: right;
+}
+
+.message.assistant {
+  margin-right: auto;
+}
+
+.message-role {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+
+.message-content {
+  display: inline-block;
+  padding: 10px 14px;
+  border-radius: 8px;
+  line-height: 1.6;
+  word-wrap: break-word;
+  white-space: pre-wrap;
+}
+
+.message.user .message-content {
+  background: #409eff;
+  color: white;
+}
+
+.message.assistant .message-content {
+  background: white;
+  color: #303133;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.typing {
+  animation: typing 1.5s infinite;
+}
+
+@keyframes typing {
+  0%, 50%, 100% {
+    opacity: 1;
+  }
+  25%, 75% {
+    opacity: 0.5;
+  }
+}
+
+.chat-input {
+  margin-top: auto;
+}
+
+.chat-actions {
+  margin-top: 8px;
+  text-align: right;
+}
+
 </style>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue b/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
index b2fb88d..ac7a123 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
@@ -1,154 +1,148 @@
 <template>
-  <div>
-
-    <!-- 鐢宠绫诲瀷閫夋嫨 -->
-    <el-card class="type-card">
-      <div class="type-selector">
-        <div
-            v-for="type in applicationTypes"
-            :key="type.value"
-            class="type-item"
-            :class="{ active: currentType === type.value }"
-            @click="changeType(type.value)"
-        >
-          <div class="type-icon">
-            <el-icon :size="24"><component :is="type.icon"/></el-icon>
-          </div>
-          <div class="type-info">
-            <div class="type-name">{{ type.name }}</div>
-            <div class="type-desc">{{ type.desc }}</div>
-          </div>
-        </div>
-      </div>
-    </el-card>
-
-    <!-- 浼氳鐢宠琛ㄥ崟 -->
-    <el-card>
-      <div class="form-header">
-        <h3>{{ getCurrentTypeName() }}鐢宠</h3>
-      </div>
-
-      <el-form
-          ref="meetingFormRef"
-          :model="meetingForm"
-          :rules="rules"
-          label-width="100px"
-      >
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="浼氳涓婚" prop="title">
-              <el-input v-model="meetingForm.title" placeholder="璇疯緭鍏ヤ細璁富棰�"/>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="浼氳瀹�" prop="roomId">
-              <el-select v-model="meetingForm.roomId" placeholder="璇烽�夋嫨浼氳瀹�" style="width: 100%">
-                <el-option
-                    v-for="room in meetingRooms"
-                    :key="room.id"
-                    :label="`${room.name} (${room.location})`"
-                    :value="room.id"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="涓绘寔浜�" prop="host">
-              <el-input v-model="meetingForm.host" placeholder="璇疯緭鍏ヤ富鎸佷汉濮撳悕"/>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="浼氳鏃ユ湡" prop="meetingDate">
-              <el-date-picker
-                  v-model="meetingForm.meetingDate"
-                  type="date"
-                  placeholder="璇烽�夋嫨浼氳鏃ユ湡"
-                  value-format="YYYY-MM-DD"
-                  format="YYYY-MM-DD"
-                  :disabled-date="disabledDate"
-                  style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <!-- 绌哄垪锛屼繚鎸佸竷灞� -->
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="寮�濮嬫椂闂�" prop="startTime">
-              <el-select
-                  v-model="meetingForm.startTime"
-                  placeholder="璇烽�夋嫨寮�濮嬫椂闂�"
-                  style="width: 100%"
-              >
-                <el-option
-                    v-for="time in startTimeOptions"
-                    :key="time.value"
-                    :label="time.label"
-                    :value="time.value"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="缁撴潫鏃堕棿" prop="endTime">
-              <el-select
-                  v-model="meetingForm.endTime"
-                  placeholder="璇烽�夋嫨缁撴潫鏃堕棿"
-                  style="width: 100%"
-              >
-                <el-option
-                    v-for="time in endTimeOptions"
-                    :key="time.value"
-                    :label="time.label"
-                    :value="time.value"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-form-item label="鍙備細浜哄憳" prop="participants">
-          <el-select
-              v-model="meetingForm.participants"
-              multiple
-              filterable
-              placeholder="璇烽�夋嫨鍙備細浜哄憳"
-              style="width: 100%"
-          >
-            <el-option
-                v-for="person in employees"
-                :key="person.id"
-                :label="`${person.staffName}${person.postName ? ` (${person.postName})` : ''}`"
-                :value="person.id"
-            />
-          </el-select>
-        </el-form-item>
-
-        <el-form-item label="浼氳璇存槑" prop="description">
-          <el-input
-              v-model="meetingForm.description"
-              type="textarea"
-              :rows="4"
-              placeholder="璇疯緭鍏ヤ細璁鏄�"
-          />
-        </el-form-item>
-      </el-form>
-
-      <div class="form-footer">
-        <el-button @click="resetForm">閲嶇疆</el-button>
-        <el-button type="primary" @click="submitForm">鎻愪氦</el-button>
-      </div>
-    </el-card>
-  </div>
+	<div>
+		
+		<!-- 鐢宠绫诲瀷閫夋嫨 -->
+		<el-card class="type-card">
+			<div class="type-selector">
+				<div
+					v-for="type in applicationTypes"
+					:key="type.value"
+					class="type-item"
+					:class="{ active: currentType === type.value }"
+					@click="changeType(type.value)"
+				>
+					<div class="type-icon">
+						<el-icon :size="24"><component :is="type.icon"/></el-icon>
+					</div>
+					<div class="type-info">
+						<div class="type-name">{{ type.name }}</div>
+						<div class="type-desc">{{ type.desc }}</div>
+					</div>
+				</div>
+			</div>
+		</el-card>
+		
+		<!-- 浼氳鐢宠琛ㄥ崟 -->
+		<el-card>
+			<div class="form-header">
+				<h3>{{ getCurrentTypeName() }}鐢宠</h3>
+			</div>
+			
+			<el-form
+				ref="meetingFormRef"
+				:model="meetingForm"
+				:rules="rules"
+				label-width="100px"
+			>
+				<el-row :gutter="20">
+					<el-col :span="12">
+						<el-form-item label="浼氳涓婚" prop="title">
+							<el-input v-model="meetingForm.title" placeholder="璇疯緭鍏ヤ細璁富棰�"/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				
+				<el-row :gutter="20">
+					<el-col :span="12">
+						<el-form-item label="浼氳瀹�" prop="roomId">
+							<el-select v-model="meetingForm.roomId" placeholder="璇烽�夋嫨浼氳瀹�" style="width: 100%">
+								<el-option
+									v-for="room in meetingRooms"
+									:key="room.id"
+									:label="`${room.name} (${room.location})`"
+									:value="room.id"
+								/>
+							</el-select>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="涓绘寔浜�" prop="host">
+							<el-input v-model="meetingForm.host" placeholder="璇疯緭鍏ヤ富鎸佷汉濮撳悕"/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				
+				<el-row :gutter="20">
+					<el-col :span="12">
+						<el-form-item label="浼氳鏃ユ湡" prop="meetingDate">
+							<el-date-picker
+								v-model="meetingForm.meetingDate"
+								type="date"
+								placeholder="璇烽�夋嫨浼氳鏃ユ湡"
+								value-format="YYYY-MM-DD"
+								format="YYYY-MM-DD"
+								:disabled-date="disabledDate"
+								style="width: 100%"
+							/>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<!-- 绌哄垪锛屼繚鎸佸竷灞� -->
+					</el-col>
+				</el-row>
+				
+				<el-row :gutter="20">
+					<el-col :span="12">
+						<el-form-item label="寮�濮嬫椂闂�" prop="startTime">
+							<el-time-picker
+								v-model="meetingForm.startTime"
+								placeholder="璇烽�夋嫨寮�濮嬫椂闂�"
+								format="HH:mm"
+								value-format="HH:mm"
+								:disabled-hours="disabledStartHours"
+								:disabled-minutes="disabledStartMinutes"
+								style="width: 100%"
+							/>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="缁撴潫鏃堕棿" prop="endTime">
+							<el-time-picker
+								v-model="meetingForm.endTime"
+								placeholder="璇烽�夋嫨缁撴潫鏃堕棿"
+								format="HH:mm"
+								value-format="HH:mm"
+								:disabled-hours="disabledEndHours"
+								:disabled-minutes="disabledEndMinutes"
+								style="width: 100%"
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+				
+				<el-form-item label="鍙備細浜哄憳" prop="participants">
+					<el-select
+						v-model="meetingForm.participants"
+						multiple
+						filterable
+						placeholder="璇烽�夋嫨鍙備細浜哄憳"
+						style="width: 100%"
+					>
+						<el-option
+							v-for="person in employees"
+							:key="person.id"
+							:label="`${person.staffName}${person.postName ? ` (${person.postName})` : ''}`"
+							:value="person.id"
+						/>
+					</el-select>
+				</el-form-item>
+				
+				<el-form-item label="浼氳璇存槑" prop="description">
+					<el-input
+						v-model="meetingForm.description"
+						type="textarea"
+						:rows="4"
+						placeholder="璇疯緭鍏ヤ細璁鏄�"
+					/>
+				</el-form-item>
+			</el-form>
+			
+			<div class="form-footer">
+				<el-button @click="resetForm">閲嶇疆</el-button>
+				<el-button type="primary" @click="submitForm">鎻愪氦</el-button>
+			</div>
+		</el-card>
+	</div>
 </template>
 
 <script setup>
@@ -163,37 +157,37 @@
 
 // 鐢宠绫诲瀷閫夐」
 const applicationTypes = ref([
-  {
-    value: 'approval',
-    name: '瀹℃壒娴佺▼浼氳',
-    desc: '闇�瑕佺粡杩囧绾у鎵圭殑浼氳鐢宠',
-    icon: Document
-  },
-  {
-    value: 'department',
-    name: '閮ㄩ棬绾т細璁�',
-    desc: '閮ㄩ棬鍐呴儴浼氳鐢宠娴佺▼',
-    icon: Promotion
-  },
-  {
-    value: 'notification',
-    name: '浼氳閫氱煡',
-    desc: '鏃犻渶瀹℃壒鐩存帴鍙戝竷鐨勪細璁�氱煡',
-    icon: Bell
-  }
+	{
+		value: 'approval',
+		name: '瀹℃壒娴佺▼浼氳',
+		desc: '闇�瑕佺粡杩囧绾у鎵圭殑浼氳鐢宠',
+		icon: Document
+	},
+	{
+		value: 'department',
+		name: '閮ㄩ棬绾т細璁�',
+		desc: '閮ㄩ棬鍐呴儴浼氳鐢宠娴佺▼',
+		icon: Promotion
+	},
+	{
+		value: 'notification',
+		name: '浼氳閫氱煡',
+		desc: '鏃犻渶瀹℃壒鐩存帴鍙戝竷鐨勪細璁�氱煡',
+		icon: Bell
+	}
 ])
 
 // 琛ㄥ崟鏁版嵁
 const meetingForm = reactive({
-  title: '',
-  type: '',
-  roomId: '',
-  host: '',
-  meetingDate: '',
-  startTime: '',
-  endTime: '',
-  participants: [],
-  description: ''
+	title: '',
+	type: '',
+	roomId: '',
+	host: '',
+	meetingDate: '',
+	startTime: '',
+	endTime: '',
+	participants: [],
+	description: ''
 })
 
 // 琛ㄥ崟寮曠敤
@@ -205,307 +199,270 @@
 // 鍛樺伐鍒楄〃
 const employees = ref([])
 
-// 鏃堕棿閫夐」锛堜互鍗婂皬鏃朵负闂撮殧锛�
-const timeOptions = ref([])
-
 const getTimeInMinutes = (time) => {
-  if (!time) return -1
-  const [hour, minute] = time.split(':').map(Number)
-  return hour * 60 + minute
+	if (!time) return -1
+	const [hour, minute] = time.split(':').map(Number)
+	return hour * 60 + minute
 }
 
 const isToday = (dateText) => {
-  if (!dateText) return false
-  const [year, month, day] = dateText.split('-').map(Number)
-  const now = new Date()
-  return year === now.getFullYear() && month === now.getMonth() + 1 && day === now.getDate()
+	if (!dateText) return false
+	const [year, month, day] = dateText.split('-').map(Number)
+	const now = new Date()
+	return year === now.getFullYear() && month === now.getMonth() + 1 && day === now.getDate()
 }
 
 const validateStartTime = (_rule, value, callback) => {
-  if (!value) {
-    callback()
-    return
-  }
-
-  if (isToday(meetingForm.meetingDate)) {
-    const now = new Date()
-    const currentMinutes = now.getHours() * 60 + now.getMinutes()
-    if (getTimeInMinutes(value) > currentMinutes) {
-      callback(new Error('褰撳ぉ寮�濮嬫椂闂翠笉鑳芥櫄浜庡綋鍓嶆椂闂�'))
-      return
-    }
-  }
-
-  callback()
+	if (!value) {
+		callback()
+		return
+	}
+	callback()
 }
 
 const validateEndTime = (_rule, value, callback) => {
-  if (!value || !meetingForm.startTime) {
-    callback()
-    return
-  }
-
-  if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) {
-    callback(new Error('缁撴潫鏃堕棿蹇呴』澶т簬寮�濮嬫椂闂�'))
-    return
-  }
-
-  callback()
+	if (!value || !meetingForm.startTime) {
+		callback()
+		return
+	}
+	if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) {
+		callback(new Error('缁撴潫鏃堕棿蹇呴』澶т簬寮�濮嬫椂闂�'))
+		return
+	}
+	callback()
 }
 
-// 琛ㄥ崟鏍¢獙瑙勫垯
 const rules = {
-  title: [{required: true, message: '璇疯緭鍏ヤ細璁富棰�', trigger: 'blur'}],
-  roomId: [{required: true, message: '璇烽�夋嫨浼氳瀹�', trigger: 'change'}],
-  host: [{required: true, message: '璇疯緭鍏ヤ富鎸佷汉', trigger: 'blur'}],
-  meetingDate: [{required: true, message: '璇烽�夋嫨浼氳鏃ユ湡', trigger: 'change'}],
-  startTime: [
-    {required: true, message: '璇烽�夋嫨寮�濮嬫椂闂�', trigger: 'change'},
-    {validator: validateStartTime, trigger: 'change'}
-  ],
-  endTime: [
-    {required: true, message: '璇烽�夋嫨缁撴潫鏃堕棿', trigger: 'change'},
-    {validator: validateEndTime, trigger: 'change'}
-  ],
-  participants: [{required: true, message: '璇烽�夋嫨鍙備細浜哄憳', trigger: 'change'}]
+	title: [{required: true, message: '璇疯緭鍏ヤ細璁富棰�', trigger: 'blur'}],
+	roomId: [{required: true, message: '璇烽�夋嫨浼氳瀹�', trigger: 'change'}],
+	host: [{required: true, message: '璇疯緭鍏ヤ富鎸佷汉', trigger: 'blur'}],
+	meetingDate: [{required: true, message: '璇烽�夋嫨浼氳鏃ユ湡', trigger: 'change'}],
+	startTime: [
+		{required: true, message: '璇烽�夋嫨寮�濮嬫椂闂�', trigger: 'change'},
+		{validator: validateStartTime, trigger: 'change'}
+	],
+	endTime: [
+		{required: true, message: '璇烽�夋嫨缁撴潫鏃堕棿', trigger: 'change'},
+		{validator: validateEndTime, trigger: 'change'}
+	],
+	participants: [{required: true, message: '璇烽�夋嫨鍙備細浜哄憳', trigger: 'change'}]
 }
 
-const startTimeOptions = computed(() => {
-  if (!isToday(meetingForm.meetingDate)) {
-    return timeOptions.value
-  }
-  const now = new Date()
-  const currentMinutes = now.getHours() * 60 + now.getMinutes()
-  return timeOptions.value.filter(item => getTimeInMinutes(item.value) <= currentMinutes)
-})
-
-const endTimeOptions = computed(() => {
-  if (!meetingForm.startTime) {
-    return timeOptions.value
-  }
-  const startMinutes = getTimeInMinutes(meetingForm.startTime)
-  return timeOptions.value.filter(item => getTimeInMinutes(item.value) > startMinutes)
-})
-
-// 鍒濆鍖栨椂闂撮�夐」
-const initTimeOptions = () => {
-  const options = []
-  const now = new Date()
-  const currentHour = now.getHours()
-  const currentMinute = now.getMinutes()
-  // meetingDate 鏄� "yyyy-MM-dd"
-  const meetingDate = new Date(meetingForm.meetingDate)
-
-  const isSameDay =
-    now.getFullYear() === meetingDate.getFullYear() &&
-    now.getMonth() === meetingDate.getMonth() &&
-    now.getDate() === meetingDate.getDate()
-
-  console.log('鏄惁鍚屼竴澶�:', isSameDay)
-  for (let hour = 8; hour <= 18; hour++) {
-    // 寮�濮嬫椂闂村繀椤绘櫄浜庡綋鍓嶆椂闂�
-    if (hour < currentHour && isSameDay) {
-      continue
-    }
-    if (hour === currentHour && currentMinute > 30 && isSameDay) {
-      continue
-    }
-    // 姣忎釜灏忔椂娣诲姞涓や釜閫夐」锛氭暣鐐瑰拰鍗婄偣
-    options.push({
-      value: `${hour.toString().padStart(2, '0')}:00`,
-      label: `${hour.toString().padStart(2, '0')}:00`
-    })
-
-    if (hour < 18) { // 18:00涔嬪悗娌℃湁鍗婄偣閫夐」
-      options.push({
-        value: `${hour.toString().padStart(2, '0')}:30`,
-        label: `${hour.toString().padStart(2, '0')}:30`
-      })
-    }
-  }
-  timeOptions.value = options
+// 鏃堕棿閫夋嫨鍣ㄧ鐢ㄩ�昏緫
+const disabledStartHours = () => {
+	const hours = []
+	for (let h = 0; h < 24; h++) {
+		if (h < 8 || h > 18) hours.push(h)
+	}
+	if (isToday(meetingForm.meetingDate)) {
+		const now = new Date()
+		for (let h = 8; h < now.getHours(); h++) {
+			if (!hours.includes(h)) hours.push(h)
+		}
+	}
+	return hours
 }
 
-watch(() => meetingForm.meetingDate, () => {
-  if (meetingForm.startTime && !startTimeOptions.value.some(item => item.value === meetingForm.startTime)) {
-    meetingForm.startTime = ''
-  }
-  if (meetingForm.endTime && !endTimeOptions.value.some(item => item.value === meetingForm.endTime)) {
-    meetingForm.endTime = ''
-  }
-  if (meetingForm.startTime) {
-    meetingFormRef.value?.validateField('startTime')
-  }
-  if (meetingForm.endTime) {
-    meetingFormRef.value?.validateField('endTime')
-  }
-  initTimeOptions()
-})
+const disabledStartMinutes = (hour) => {
+	const minutes = []
+	for (let m = 0; m < 60; m++) {
+		if (m !== 0 && m !== 30) minutes.push(m)
+	}
+	if (isToday(meetingForm.meetingDate)) {
+		const now = new Date()
+		if (hour === now.getHours()) {
+			if (now.getMinutes() >= 30) {
+				minutes.push(0)
+			}
+		}
+	}
+	return minutes
+}
+
+const disabledEndHours = () => {
+	const hours = []
+	for (let h = 0; h < 24; h++) {
+		if (h < 8 || h > 18) hours.push(h)
+	}
+	if (meetingForm.startTime) {
+		const startHour = parseInt(meetingForm.startTime.split(':')[0])
+		for (let h = 8; h < startHour; h++) {
+			if (!hours.includes(h)) hours.push(h)
+		}
+	}
+	return hours
+}
+
+const disabledEndMinutes = (hour) => {
+	const minutes = []
+	for (let m = 0; m < 60; m++) {
+		if (m !== 0 && m !== 30) minutes.push(m)
+	}
+	if (meetingForm.startTime) {
+		const startHour = parseInt(meetingForm.startTime.split(':')[0])
+		const startMinute = parseInt(meetingForm.startTime.split(':')[1])
+		if (hour === startHour) {
+			if (startMinute >= 0) minutes.push(0)
+			if (startMinute >= 30) minutes.push(30)
+			// only keep minutes > startMinute
+			for (let m = 0; m <= startMinute; m++) {
+				if (!minutes.includes(m)) minutes.push(m)
+			}
+		}
+	}
+	return minutes
+}
 
 watch(() => meetingForm.startTime, () => {
-  if (meetingForm.endTime && getTimeInMinutes(meetingForm.endTime) <= getTimeInMinutes(meetingForm.startTime)) {
-    meetingForm.endTime = ''
-  }
-  if (meetingForm.endTime) {
-    meetingFormRef.value?.validateField('endTime')
-  }
-  
+	if (meetingForm.endTime && getTimeInMinutes(meetingForm.endTime) <= getTimeInMinutes(meetingForm.startTime)) {
+		meetingForm.endTime = ''
+	}
+	if (meetingForm.endTime) {
+		meetingFormRef.value?.validateField('endTime')
+	}
+	
 })
 
 // 绂佺敤鏃ユ湡锛堢鐢ㄤ粖澶╀箣鍓嶇殑鏃ユ湡锛�
 const disabledDate = (time) => {
-  // 绂佺敤浠婂ぉ涔嬪墠鐨勬棩鏈�
-  return time.getTime() < Date.now() - 86400000
+	// 绂佺敤浠婂ぉ涔嬪墠鐨勬棩鏈�
+	return time.getTime() < Date.now() - 86400000
 }
 
 // 鍒囨崲鐢宠绫诲瀷
 const changeType = (type) => {
-  currentType.value = type
+	currentType.value = type
 }
 
 // 鑾峰彇褰撳墠绫诲瀷鍚嶇О
 const getCurrentTypeName = () => {
-  const type = applicationTypes.value.find(t => t.value === currentType.value)
-  return type ? type.name : ''
+	const type = applicationTypes.value.find(t => t.value === currentType.value)
+	return type ? type.name : ''
 }
 
 // 閲嶇疆琛ㄥ崟
 const resetForm = () => {
-  meetingFormRef.value?.resetFields()
+	meetingFormRef.value?.resetFields()
 }
 
 // 鎻愪氦琛ㄥ崟
 const submitForm = () => {
-  meetingFormRef.value?.validate((valid) => {
-    if (valid) {
-
-      let formData = {...meetingForm}
-      formData.applicationType = currentType.value
-      formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00`
-      formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00`
-      formData.participants = JSON.stringify(formData.participants)
-      console.log(formData)
-      saveMeetingApplication(formData).then(() => {
-
-        // 妯℃嫙鎻愪氦鎿嶄綔
-        ElMessage.success(`${getCurrentTypeName()}鎻愪氦鎴愬姛`)
-
-        // 鏍规嵁涓嶅悓绫诲瀷鎵ц涓嶅悓鎿嶄綔
-        switch (currentType.value) {
-          case 'approval':
-            ElMessage.info('浼氳宸叉彁浜ゅ鎵规祦绋�')
-            break
-          case 'department':
-            ElMessage.info('閮ㄩ棬绾т細璁敵璇峰凡鎻愪氦')
-            break
-          case 'notification':
-            ElMessage.info('浼氳閫氱煡宸插彂甯�')
-            break
-        }
-        resetForm()
-      })
-
-    }
-  })
+	meetingFormRef.value?.validate((valid) => {
+		if (valid) {
+			
+			let formData = {...meetingForm}
+			formData.applicationType = currentType.value
+			formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00`
+			formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00`
+			formData.participants = JSON.stringify(formData.participants)
+			console.log(formData)
+			saveMeetingApplication(formData).then(() => {
+				
+				// 妯℃嫙鎻愪氦鎿嶄綔
+				ElMessage.success(`${getCurrentTypeName()}鎻愪氦鎴愬姛`)
+				resetForm()
+			})
+			
+		}
+	})
 }
 
 // 椤甸潰鍔犺浇鏃跺垵濮嬪寲
 onMounted(() => {
-  initTimeOptions()
-  getRoomEnum().then(res => {
-    meetingRooms.value = res.data
-  })
-  staffOnJobListPage({
-    current: -1,
-    size: -1,
-    staffState: 1
-  }).then(res => {
-    employees.value = res.data.records.sort((a, b) => (a.postName || '').localeCompare(b.postName || ''))
-  })
+	getRoomEnum().then(res => {
+		meetingRooms.value = res.data
+	})
+	staffOnJobListPage({
+		current: -1,
+		size: -1,
+		staffState: 1
+	}).then(res => {
+		employees.value = res.data.records.sort((a, b) => (a.postName || '').localeCompare(b.postName || ''))
+	})
 })
 </script>
 
 <style scoped>
 .app-container {
-  padding: 20px;
+	padding: 20px;
 }
 
 .page-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20px;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 20px;
 }
 
 .page-header h2 {
-  margin: 0;
-  color: #303133;
+	margin: 0;
+	color: #303133;
 }
 
 .type-card {
-  margin-bottom: 20px;
+	margin-bottom: 20px;
 }
 
 .type-selector {
-  display: flex;
-  gap: 20px;
+	display: flex;
+	gap: 20px;
 }
 
 .type-item {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  padding: 20px;
-  border: 1px solid #ebeef5;
-  border-radius: 8px;
-  cursor: pointer;
-  transition: all 0.3s;
+	flex: 1;
+	display: flex;
+	align-items: center;
+	padding: 20px;
+	border: 1px solid #ebeef5;
+	border-radius: 8px;
+	cursor: pointer;
+	transition: all 0.3s;
 }
 
 .type-item:hover {
-  border-color: #409eff;
-  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+	border-color: #409eff;
+	box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
 }
 
 .type-item.active {
-  border-color: #409eff;
-  background-color: #ecf5ff;
+	border-color: #409eff;
+	background-color: #ecf5ff;
 }
 
 .type-icon {
-  margin-right: 15px;
-  color: #409eff;
+	margin-right: 15px;
+	color: #409eff;
 }
 
 .type-name {
-  font-size: 16px;
-  font-weight: 500;
-  color: #303133;
-  margin-bottom: 5px;
+	font-size: 16px;
+	font-weight: 500;
+	color: #303133;
+	margin-bottom: 5px;
 }
 
 .type-desc {
-  font-size: 14px;
-  color: #909399;
+	font-size: 14px;
+	color: #909399;
 }
 
 .form-header {
-  margin-bottom: 20px;
-  padding-bottom: 15px;
-  border-bottom: 1px solid #ebeef5;
+	margin-bottom: 20px;
+	padding-bottom: 15px;
+	border-bottom: 1px solid #ebeef5;
 }
 
 .form-header h3 {
-  margin: 0;
-  color: #303133;
+	margin: 0;
+	color: #303133;
 }
 
 .form-footer {
-  display: flex;
-  justify-content: flex-end;
-  gap: 10px;
-  margin-top: 30px;
-  padding-top: 20px;
-  border-top: 1px solid #ebeef5;
+	display: flex;
+	justify-content: flex-end;
+	gap: 10px;
+	margin-top: 30px;
+	padding-top: 20px;
+	border-top: 1px solid #ebeef5;
 }
 </style>
diff --git a/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue b/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
index 26e2c24..dea9495 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
@@ -36,7 +36,7 @@
         <el-table-column prop="location" label="浼氳鍦扮偣" align="center" width="150"/>
         <el-table-column prop="participants" label="鍙備細浜烘暟" align="center" width="100">
           <template #default="scope">
-            {{ scope.row.participants.length }}浜�
+            {{ scope.row.staffCount }}浜�
           </template>
         </el-table-column>
         <el-table-column prop="status" label="瀹℃壒鐘舵��" align="center" width="120">
@@ -233,9 +233,9 @@
   let resp = await getExamineList({...searchForm, ...queryParams})
   approvalList.value = resp.data.records.map(it => {
     let room = roomEnum.value.find(room => it.roomId === room.id)
-    it.location = `${room.name}(${room.location})`
+    it.location = room ? `${room.name}(${room.location})` : ''
     let staffs = JSON.parse(it.participants)
-    it.staffCount = staffs.size
+    it.staffCount = staffs.length
     it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
     it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
       return {
diff --git a/src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue b/src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
index 9b5325f..235faa3 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
@@ -145,10 +145,11 @@
     if (endIdx === -1) {
       endIdx = timeSlots.value.length
     }
-    console.log('endIdx111', endIdx)
-    if (startIdx !== -1 && endIdx !== -1) {
+    // 寰�鍚庡欢浼镐竴鏍硷紝璁╀細璁牸瀛愯鐩栧埌缁撴潫鏃堕棿鍒椾笂
+    const displayEndIdx = Math.min(endIdx + 1, timeSlots.value.length)
+    if (startIdx !== -1) {
       // 鏍囪琚崰鐢ㄧ殑鏃堕棿娈�
-      for (let i = startIdx; i < endIdx; i++) {
+      for (let i = startIdx; i < displayEndIdx; i++) {
         occupiedSlots.add(timeSlots.value[i].value)
       }
 
@@ -156,7 +157,7 @@
       cells.push({
         type: 'meeting',
         meeting: meeting,
-        span: endIdx - startIdx,
+        span: displayEndIdx - startIdx,
         startTime: meeting.startTime,
         endTime: meeting.endTime
       })
diff --git a/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue b/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
index dace531..a3ae223 100644
--- a/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
@@ -34,7 +34,7 @@
         <el-table-column prop="location" label="浼氳鍦扮偣" align="center" width="150"/>
         <el-table-column prop="participants" label="鍙備細浜烘暟" align="center" width="100">
           <template #default="scope">
-            {{ scope.row.participants.length }}浜�
+            {{ scope.row.staffCount }}浜�
           </template>
         </el-table-column>
         <el-table-column prop="status" label="鍙戝竷鐘舵��" align="center" width="120">
@@ -231,9 +231,9 @@
   let resp = await getMeetingPublish({...searchForm, ...queryParams})
   approvalList.value = resp.data.records.map(it => {
     let room = roomEnum.value.find(room => it.roomId === room.id)
-    it.location = `${room.name}(${room.location})`
+    it.location = room ? `${room.name}(${room.location})` : ''
     let staffs = JSON.parse(it.participants)
-    it.staffCount = staffs.size
+    it.staffCount = staffs.length
     it.status = it.publishStatus
     it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
     it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
diff --git a/src/views/collaborativeApproval/notificationManagement/summary/index.vue b/src/views/collaborativeApproval/notificationManagement/summary/index.vue
index bf6e230..9f3fc7f 100644
--- a/src/views/collaborativeApproval/notificationManagement/summary/index.vue
+++ b/src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -28,7 +28,7 @@
         <el-table-column prop="location" label="浼氳鍦扮偣" align="center" width="150" />
         <el-table-column prop="participants" label="鍙備細浜烘暟" align="center" width="100">
           <template #default="scope">
-            {{ scope.row.participants.length }}浜�
+            {{ scope.row.staffCount }}浜�
           </template>
         </el-table-column>
         <el-table-column label="鎿嶄綔" align="center" width="200" fixed="right">
@@ -207,9 +207,9 @@
   let resp = await getMeetingPublish({ ...searchForm, ...queryParams })
   meetingList.value = resp.data.records.map(it => {
     let room = roomEnum.value.find(room => it.roomId === room.id)
-    it.location = `${room.name}(${room.location})`
+    it.location = room ? `${room.name}(${room.location})` : ''
     let staffs = JSON.parse(it.participants)
-    it.staffCount = staffs.size
+    it.staffCount = staffs.length
     it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}`
     it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => {
       return {
@@ -260,7 +260,7 @@
 <p><strong>涓绘寔浜猴細</strong>${row.host}</p>
 <p><strong>鍙備細浜哄憳锛�</strong></p>
 <ol>
-  ${row.participants.map(p => `<li>${p.name}</li>`).join('')}
+  ${(row.participants || []).map(p => `<li>${p?.name || ''}</li>`).join('')}
 </ol>
 <p><strong>浼氳鍐呭锛�</strong></p>
 <ol>
diff --git a/src/views/financialManagement/assets/intangibleAssets.vue b/src/views/financialManagement/assets/intangibleAssets.vue
index 9aef2bf..b2c13b1 100644
--- a/src/views/financialManagement/assets/intangibleAssets.vue
+++ b/src/views/financialManagement/assets/intangibleAssets.vue
@@ -72,7 +72,7 @@
         </template>
         <template #operation="{ row }">
           <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
-          <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+          <el-button v-if="row.status !== 'amortized'" type="primary" link @click="edit(row)">缂栬緫</el-button>
           <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
         </template>
       </PIMTable>
@@ -160,7 +160,7 @@
               <el-select v-model="form.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%;">
                 <el-option label="鍦ㄧ敤" value="in_use" />
                 <el-option label="闂茬疆" value="idle" />
-                <el-option label="宸叉憡閿�瀹屾瘯" value="amortized" />
+                <el-option v-if="isView" label="宸叉憡閿�瀹屾瘯" value="amortized" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -244,6 +244,7 @@
 );
 
 const createDefaultForm = () => ({
+  id: null,
   assetCode: "",
   assetName: "",
   category: "",
diff --git a/src/views/financialManagement/payable/input-invoice.vue b/src/views/financialManagement/payable/input-invoice.vue
index 86ebd09..ae40709 100644
--- a/src/views/financialManagement/payable/input-invoice.vue
+++ b/src/views/financialManagement/payable/input-invoice.vue
@@ -51,6 +51,8 @@
         :column="columns"
         :tableData="dataList"
         :tableLoading="tableLoading"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -375,6 +377,26 @@
   amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
 };
 
+const summaryProps = ["amount", "taxAmount", "totalAmount"];
+
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (summaryProps.includes(col.property)) {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur[col.property]);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const formatMoney = (value) => {
   if (value === undefined || value === null) return "0.00";
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
diff --git a/src/views/financialManagement/payable/payment.vue b/src/views/financialManagement/payable/payment.vue
index 18e7941..dd386d8 100644
--- a/src/views/financialManagement/payable/payment.vue
+++ b/src/views/financialManagement/payable/payment.vue
@@ -65,6 +65,8 @@
                 :column="columns"
                 :tableData="dataList"
                 :tableLoading="tableLoading"
+                isShowSummary
+                :summaryMethod="getSummaries"
                 :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -149,7 +151,25 @@
     dataList.value.reduce((sum, item) => sum + Number(item.amount ?? 0), 0)
   );
 
-  const formatMoney = value => {
+  const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (col.property === "amount") {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur.amount);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
+const formatMoney = value => {
     if (value === undefined || value === null) return "0.00";
     return Number(value)
       .toFixed(2)
diff --git a/src/views/financialManagement/payable/paymentApply.vue b/src/views/financialManagement/payable/paymentApply.vue
index e34793f..309d6d3 100644
--- a/src/views/financialManagement/payable/paymentApply.vue
+++ b/src/views/financialManagement/payable/paymentApply.vue
@@ -52,6 +52,8 @@
         :column="columns"
         :tableData="dataList"
         :tableLoading="tableLoading"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -477,6 +479,24 @@
   applyDate: [{ required: true, message: "璇烽�夋嫨鐢宠鏃ユ湡", trigger: "change" }],
 };
 
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (col.property === "amount") {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur.amount);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const formatMoney = (value) => {
   if (value === undefined || value === null) return "0.00";
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
diff --git a/src/views/financialManagement/payable/purchaseIn.vue b/src/views/financialManagement/payable/purchaseIn.vue
index 532bcb4..b105859 100644
--- a/src/views/financialManagement/payable/purchaseIn.vue
+++ b/src/views/financialManagement/payable/purchaseIn.vue
@@ -49,6 +49,8 @@
                 :column="columns"
                 :tableData="dataList"
                 :tableLoading="tableLoading"
+                isShowSummary
+                :summaryMethod="getSummaries"
                 :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -189,7 +191,25 @@
     getTableData();
   };
 
-  const handleOut = () => {
+  const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (col.property === "inboundAmount") {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur.inboundAmount);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
+const handleOut = () => {
     proxy.download(
       "/accountPurchase/exportAccountPurchaseInbound",
       buildFilterParams(),
diff --git a/src/views/financialManagement/payable/purchaseReturn.vue b/src/views/financialManagement/payable/purchaseReturn.vue
index eeec383..4171df2 100644
--- a/src/views/financialManagement/payable/purchaseReturn.vue
+++ b/src/views/financialManagement/payable/purchaseReturn.vue
@@ -44,6 +44,8 @@
         :column="columns"
         :tableData="dataList"
         :tableLoading="tableLoading"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -175,6 +177,24 @@
   getTableData();
 };
 
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (col.property === "totalAmount") {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur.totalAmount);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const handleOut = () => {
   proxy.download(
     "/accountPurchase/exportAccountPurchaseReturn",
diff --git a/src/views/financialManagement/payable/reconciliation.vue b/src/views/financialManagement/payable/reconciliation.vue
index e749e56..a3b8495 100644
--- a/src/views/financialManagement/payable/reconciliation.vue
+++ b/src/views/financialManagement/payable/reconciliation.vue
@@ -35,6 +35,8 @@
         :column="columns"
         :tableData="dataList"
         :tableLoading="tableLoading"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -405,6 +407,26 @@
   accountStatementDetails: selectedPurchases.value.map(buildDetailSubmitItem),
 });
 
+const summaryProps = ["openingBalance", "currentPlan", "currentActually", "closingBalance"];
+
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (summaryProps.includes(col.property)) {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur[col.property]);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const formatMoney = (value) => {
   if (value === undefined || value === null) return "0.00";
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
diff --git a/src/views/financialManagement/receivable/invoiceApply.vue b/src/views/financialManagement/receivable/invoiceApply.vue
index 85f30b2..21b5f0e 100644
--- a/src/views/financialManagement/receivable/invoiceApply.vue
+++ b/src/views/financialManagement/receivable/invoiceApply.vue
@@ -48,6 +48,8 @@
         v-loading="tableLoading"
         :column="columns"
         :tableData="dataList"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -648,6 +650,27 @@
   proxy.download("/accountInvoiceApplication/exportAccountInvoiceApplication", params, filename);
 };
 
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (col.property === "amount") {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur.amount);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = total.toLocaleString("zh-CN", {
+        minimumFractionDigits: 2,
+        maximumFractionDigits: 2,
+      });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const formatMoney = (value) => {
   if (value === undefined || value === null) return "0.00";
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
diff --git a/src/views/financialManagement/receivable/outputInvoice.vue b/src/views/financialManagement/receivable/outputInvoice.vue
index d746aea..22cc202 100644
--- a/src/views/financialManagement/receivable/outputInvoice.vue
+++ b/src/views/financialManagement/receivable/outputInvoice.vue
@@ -46,6 +46,8 @@
         v-loading="tableLoading"
         :column="columns"
         :tableData="dataList"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -324,6 +326,27 @@
   amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
 };
 
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (col.property === "amount") {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur.amount);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = total.toLocaleString("zh-CN", {
+        minimumFractionDigits: 2,
+        maximumFractionDigits: 2,
+      });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const formatMoney = (value) => {
   if (value === undefined || value === null) return "0.00";
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
diff --git a/src/views/financialManagement/receivable/reconciliation.vue b/src/views/financialManagement/receivable/reconciliation.vue
index b1bff0e..03400ef 100644
--- a/src/views/financialManagement/receivable/reconciliation.vue
+++ b/src/views/financialManagement/receivable/reconciliation.vue
@@ -30,6 +30,8 @@
         :column="columns"
         :tableData="dataList"
         :tableLoading="tableLoading"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -388,6 +390,29 @@
   accountStatementDetails: selectedSales.value.map(buildDetailSubmitItem),
 });
 
+const summaryProps = ["openingBalance", "currentPlan", "currentActually", "closingBalance"];
+
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (summaryProps.includes(col.property)) {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur[col.property]);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", {
+        minimumFractionDigits: 2,
+        maximumFractionDigits: 2,
+      });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const formatMoney = (value) => {
   if (value === undefined || value === null) return "0.00";
   return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
diff --git a/src/views/financialManagement/receivable/salesOut.vue b/src/views/financialManagement/receivable/salesOut.vue
index 0e24b37..f6205d8 100644
--- a/src/views/financialManagement/receivable/salesOut.vue
+++ b/src/views/financialManagement/receivable/salesOut.vue
@@ -43,6 +43,8 @@
                 :column="columns"
                 :tableData="dataList"
                 :tableLoading="tableLoading"
+                isShowSummary
+                :summaryMethod="getSummaries"
                 :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -86,7 +88,6 @@
       label: "閲戦",
       prop: "outboundAmount",
       minWidth: "120",
-      align: "right",
       formatData: val =>
         val === null || val === undefined || val === ""
           ? ""
@@ -158,6 +159,27 @@
     getTableData();
   };
 
+  const getSummaries = ({ columns, data }) => {
+    const sums = [];
+    columns.forEach((col, index) => {
+      if (index === 0) {
+        sums[index] = "鍚堣";
+      } else if (col.property === "outboundAmount") {
+        const total = data.reduce((prev, cur) => {
+          const v = Number(cur.outboundAmount);
+          return prev + (isNaN(v) ? 0 : v);
+        }, 0);
+        sums[index] = total.toLocaleString("zh-CN", {
+          minimumFractionDigits: 2,
+          maximumFractionDigits: 2,
+        });
+      } else {
+        sums[index] = "";
+      }
+    });
+    return sums;
+  };
+
   const handleOut = () => {
     proxy.download(
       "/accountSales/exportAccountSalesOutbound",
diff --git a/src/views/financialManagement/receivable/salesReturn.vue b/src/views/financialManagement/receivable/salesReturn.vue
index c58d330..afe363c 100644
--- a/src/views/financialManagement/receivable/salesReturn.vue
+++ b/src/views/financialManagement/receivable/salesReturn.vue
@@ -37,6 +37,8 @@
         :column="columns"
         :tableData="dataList"
         :tableLoading="tableLoading"
+        isShowSummary
+        :summaryMethod="getSummaries"
         :page="{
           current: pagination.currentPage,
           size: pagination.pageSize,
@@ -80,7 +82,6 @@
     label: "閫�娆炬�婚",
     prop: "refundAmount",
     minWidth: "120",
-    align: "right",
     formatData: (val) =>
       val === null || val === undefined || val === ""
         ? ""
@@ -149,6 +150,27 @@
   getTableData();
 };
 
+const getSummaries = ({ columns, data }) => {
+  const sums = [];
+  columns.forEach((col, index) => {
+    if (index === 0) {
+      sums[index] = "鍚堣";
+    } else if (col.property === "refundAmount") {
+      const total = data.reduce((prev, cur) => {
+        const v = Number(cur.refundAmount);
+        return prev + (isNaN(v) ? 0 : v);
+      }, 0);
+      sums[index] = total.toLocaleString("zh-CN", {
+        minimumFractionDigits: 2,
+        maximumFractionDigits: 2,
+      });
+    } else {
+      sums[index] = "";
+    }
+  });
+  return sums;
+};
+
 const handleOut = () => {
   proxy.download(
     "/accountSales/exportAccountSalesReturn",
diff --git a/src/views/financialManagement/voucher/index.vue b/src/views/financialManagement/voucher/index.vue
index 1aa6f69..0c02d51 100644
--- a/src/views/financialManagement/voucher/index.vue
+++ b/src/views/financialManagement/voucher/index.vue
@@ -25,7 +25,7 @@
         </el-select>
       </el-form-item>
       <el-form-item>
-        <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+        <el-button type="primary" @click="onSearch">鎼滅储</el-button>
         <el-button @click="resetFilters">閲嶇疆</el-button>
       </el-form-item>
     </el-form>
@@ -453,6 +453,11 @@
   return map[key] || "";
 };
 
+const onSearch = () => {
+  pagination.currentPage = 1;
+  getTableData();
+};
+
 // 鑱旇皟绾﹀畾锛氬垎椤靛弬鏁颁娇鐢� current/size锛屾棩鏈熻寖鍥存媶鍒嗕负 startDate/endDate
 const getTableData = async () => {
   try {
@@ -518,9 +523,9 @@
   getTableData();
 };
 
-const changePage = ({ current, size }) => {
-  pagination.currentPage = current;
-  pagination.pageSize = size;
+const changePage = ({ page, limit }) => {
+  pagination.currentPage = page;
+  pagination.pageSize = limit;
   getTableData();
 };
 
diff --git a/src/views/inventoryManagement/stockManagement/New.vue b/src/views/inventoryManagement/stockManagement/New.vue
index 7ee84ea..9a49c46 100644
--- a/src/views/inventoryManagement/stockManagement/New.vue
+++ b/src/views/inventoryManagement/stockManagement/New.vue
@@ -86,8 +86,9 @@
         <el-form-item label="搴撳瓨鏁伴噺"
                       prop="qualitity">
           <el-input-number v-model="formState.qualitity"
-                           :step="1"
-                           :min="1"
+                           :step="0.01"
+                           :min="0.01"
+                           :precision="2"
                            style="width: 100%" />
         </el-form-item>
         <el-form-item label="鎵瑰彿"
@@ -99,9 +100,10 @@
                       label="搴撳瓨棰勮鏁伴噺"
                       prop="warnNum">
           <el-input-number v-model="formState.warnNum"
-                           :step="1"
+                           :step="0.01"
                            :min="0"
                            :max="formState.qualitity"
+                           :precision="2"
                            style="width: 100%" />
         </el-form-item>
         <el-form-item label="澶囨敞"
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
index ecff3a4..2d70a44 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
@@ -19,9 +19,9 @@
             {{ approvalTypeLabel(row.approvalType) }}
           </span>
         </el-descriptions-item>
-        <el-descriptions-item label="鐢宠浜虹紪鍙�">{{ row.applicantNo || "鈥�" }}</el-descriptions-item>
-        <el-descriptions-item label="鐢宠浜哄悕绉�">{{ row.applicantName || "鈥�" }}</el-descriptions-item>
-        <el-descriptions-item label="鐢宠鎽樿">{{ row.summary || "鈥�" }}</el-descriptions-item>
+        <el-descriptions-item label="鍙戣捣浜虹紪鍙�">{{ row.applicantNo || "鈥�" }}</el-descriptions-item>
+        <el-descriptions-item label="鍙戣捣浜哄悕绉�">{{ row.applicantName || "鈥�" }}</el-descriptions-item>
+        <el-descriptions-item label="鎽樿">{{ row.summary || "鈥�" }}</el-descriptions-item>
         <el-descriptions-item v-if="row.rejectReason"
                               label="椹冲洖鍘熷洜"
                               :span="2">
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
index 3f0e99c..9442a5a 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
@@ -140,7 +140,7 @@
 
   const tableColumn = ref([
     // { label: "鐢宠浜虹紪鍙�", prop: "applicantNo", width: 110 },
-    { label: "鐢宠浜哄悕绉�", prop: "applicantName", minWidth: 100 },
+    { label: "鍙戣捣浜�", prop: "applicantName", minWidth: 100 },
     { label: "妯℃澘绫诲瀷", prop: "businessName", minWidth: 120 },
     {
       label: "瀹℃壒绫诲瀷",
@@ -154,7 +154,9 @@
       prop: "unread",
       width: 90,
       align: "center",
+      dataType: "tag",
       formatData: (v) => (v ? "鏄�" : "鍚�"),
+      formatType: (v) => (v ? "success" : "danger"),
     },
     {
       label: "瀹℃壒鐘舵��",
diff --git a/src/views/officeProcessAutomation/AttendManage/overtime-apply/index.vue b/src/views/officeProcessAutomation/AttendManage/overtime-apply/index.vue
index 9b3d91e..3c381dc 100644
--- a/src/views/officeProcessAutomation/AttendManage/overtime-apply/index.vue
+++ b/src/views/officeProcessAutomation/AttendManage/overtime-apply/index.vue
@@ -24,7 +24,7 @@
         <el-button @click="resetSearch">閲嶇疆</el-button>
       </div>
       <div class="search_actions">
-        <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>
+<!--        <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>-->
         <el-button type="primary" @click="openAddWithTemplate">鏂板鍔犵彮鐢宠</el-button>
       </div>
     </div>
diff --git a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
index c9da4fc..661c990 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
+++ b/src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/index.vue
@@ -16,8 +16,8 @@
         <el-button @click="resetSearch">閲嶇疆</el-button>
       </div>
       <div class="search_actions">
-        <el-button type="success" plain @click="handleImportClick">瀵煎叆</el-button>
-        <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>
+<!--        <el-button type="success" plain @click="handleImportClick">瀵煎叆</el-button>-->
+<!--        <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>-->
         <el-button type="primary" @click="openFormDialog('add')">鏂板璐圭敤鎶ラ攢</el-button>
       </div>
     </div>
diff --git a/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue b/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue
index 17737e3..e2b5977 100644
--- a/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue
+++ b/src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/index.vue
@@ -16,8 +16,8 @@
         <el-button @click="resetSearch">閲嶇疆</el-button>
       </div>
       <div class="search_actions">
-        <el-button type="success" plain @click="handleImportClick">瀵煎叆</el-button>
-        <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>
+<!--        <el-button type="success" plain @click="handleImportClick">瀵煎叆</el-button>-->
+<!--        <el-button type="warning" plain @click="handleExport">瀵煎嚭</el-button>-->
         <el-button type="primary" @click="openFormDialog('add')">鏂板宸梾鎶ラ攢</el-button>
       </div>
     </div>
diff --git a/src/views/personnelManagement/socialSecuritySet/components/formDia.vue b/src/views/personnelManagement/socialSecuritySet/components/formDia.vue
index 71f5e61..985a6b1 100644
--- a/src/views/personnelManagement/socialSecuritySet/components/formDia.vue
+++ b/src/views/personnelManagement/socialSecuritySet/components/formDia.vue
@@ -1,34 +1,31 @@
 <template>
   <div>
-    <FormDialog
-      v-model="dialogFormVisible"
-      :operation-type="operationType"
-      :title="dialogTitle"
-      width="80%"
-      @close="closeDia"
-      @confirm="submitForm"
-      @cancel="closeDia"
-    >
-      <el-form ref="formRef" :model="form" :rules="rules" label-position="top">
+    <FormDialog v-model="dialogFormVisible"
+                :operation-type="operationType"
+                :title="dialogTitle"
+                width="80%"
+                @close="closeDia"
+                @confirm="submitForm"
+                @cancel="closeDia">
+      <el-form ref="formRef"
+               :model="form"
+               :rules="rules"
+               label-position="top">
         <el-row :gutter="24">
           <!-- 宸︿晶锛氶�傜敤浜哄憳 -->
           <el-col :span="8">
-            <el-form-item label="閫傜敤浜哄憳锛�" prop="deptIds">
+            <el-form-item label="閫傜敤浜哄憳锛�"
+                          prop="deptIds">
               <div class="dept-checkbox-wrap">
-                <el-checkbox-group
-                  v-model="form.deptIds"
-                  :disabled="isDetail"
-                >
-                  <div
-                    v-for="dept in deptList"
-                    :key="dept.deptId"
-                    class="dept-checkbox-item"
-                  >
+                <el-checkbox-group v-model="form.deptIds"
+                                   :disabled="isDetail">
+                  <div v-for="dept in deptList"
+                       :key="dept.deptId"
+                       class="dept-checkbox-item">
                     <el-checkbox :value="dept.deptId">
                       {{ dept.deptName }}
-                      <span v-if="dept.personCount != null" class="dept-count"
-                        >{{ dept.personCount }}浜�</span
-                      >
+                      <span v-if="dept.personCount != null"
+                            class="dept-count">{{ dept.personCount }}浜�</span>
                     </el-checkbox>
                   </div>
                 </el-checkbox-group>
@@ -38,124 +35,111 @@
           <!-- 鍙充晶锛氬熀纭�淇℃伅 + 淇濋櫓绫诲瀷 -->
           <el-col :span="16">
             <!-- 鍩虹淇℃伅 -->
-            <el-card class="form-card" shadow="never">
+            <el-card class="form-card"
+                     shadow="never">
               <template #header>
                 <span class="card-title"><span class="card-title-line">|</span> 鍩虹淇℃伅</span>
-                <el-icon class="card-collapse"><ArrowUp /></el-icon>
+                <el-icon class="card-collapse">
+                  <ArrowUp />
+                </el-icon>
               </template>
-              <el-form-item label="鏂规鏍囬锛�" prop="title">
-                <el-input
-                  v-model="form.title"
-                  placeholder="璇疯緭鍏�"
-                  clearable
-                  :disabled="isDetail"
-                />
+              <el-form-item label="鏂规鏍囬锛�"
+                            prop="title">
+                <el-input v-model="form.title"
+                          placeholder="璇疯緭鍏�"
+                          clearable
+                          :disabled="isDetail" />
               </el-form-item>
-              <el-form-item label="澶囨敞锛�" prop="remark">
-                <el-input
-                  v-model="form.remark"
-                  type="textarea"
-                  :rows="2"
-                  placeholder="璇疯緭鍏�"
-                  clearable
-                  :disabled="isDetail"
-                />
+              <el-form-item label="澶囨敞锛�"
+                            prop="remark">
+                <el-input v-model="form.remark"
+                          type="textarea"
+                          :rows="2"
+                          placeholder="璇疯緭鍏�"
+                          clearable
+                          :disabled="isDetail" />
               </el-form-item>
             </el-card>
-
             <!-- 淇濋櫓绫诲瀷 -->
-            <el-card class="form-card" shadow="never">
+            <el-card class="form-card"
+                     shadow="never">
               <template #header>
                 <span class="card-title"><span class="card-title-line">|</span> 淇濋櫓绫诲瀷</span>
-                <el-button
-                  v-if="!isDetail"
-                  type="primary"
-                  size="small"
-                  @click="addInsuranceBenefit"
-                >
+                <el-button v-if="!isDetail"
+                           type="primary"
+                           size="small"
+                           @click="addInsuranceBenefit">
                   娣诲姞淇濋櫓绂忓埄
                 </el-button>
               </template>
               <el-row :gutter="16">
-                <el-col
-                  v-for="(item, index) in form.insuranceBenefits"
-                  :key="item._key"
-                  :span="12"
-                >
+                <el-col v-for="(item, index) in form.insuranceBenefits"
+                        :key="item._key"
+                        :span="12">
                   <div class="insurance-benefit-card">
                     <div class="insurance-benefit-title">
                       淇濋櫓绂忓埄{{ index + 1 }}
-                      <el-button
-                        v-if="!isDetail && form.insuranceBenefits.length > 1"
-                        type="danger"
-                        link
-                        size="small"
-                        class="card-delete-btn"
-                        @click="removeInsuranceBenefit(index)"
-                      >
+                      <el-button v-if="!isDetail && form.insuranceBenefits.length > 1"
+                                 type="danger"
+                                 link
+                                 size="small"
+                                 class="card-delete-btn"
+                                 @click="removeInsuranceBenefit(index)">
                         鍒犻櫎
                       </el-button>
                     </div>
-                    <el-form-item
-                      :prop="'insuranceBenefits.' + index + '.insuranceType'"
-                      label="淇濋櫓绫诲瀷锛�"
-                      label-width="100px"
-                    >
-                      <el-select
-                        v-model="item.insuranceType"
-                        placeholder="璇烽�夋嫨"
-                        clearable
-                        style="width: 100%"
-                        :disabled="isDetail"
-                      >
-                        <el-option
-                          v-for="opt in insuranceTypeOptions"
-                          :key="opt.value"
-                          :label="opt.label"
-                          :value="opt.value"
-                        />
+                    <el-form-item :prop="'insuranceBenefits.' + index + '.insuranceType'"
+                                  label="淇濋櫓绫诲瀷锛�"
+                                  label-width="100px">
+                      <el-select v-model="item.insuranceType"
+                                 placeholder="璇烽�夋嫨"
+                                 clearable
+                                 style="width: 100%"
+                                 :disabled="isDetail">
+                        <el-option v-for="opt in insuranceTypeOptions"
+                                   :key="opt.value"
+                                   :label="opt.label"
+                                   :value="opt.value" />
                       </el-select>
                     </el-form-item>
-                    <el-form-item label="缂磋垂鍩烘暟锛�" label-width="100px">
+                    <el-form-item label="缂磋垂鍩烘暟锛�"
+                                  label-width="100px">
                       <div class="base-salary-wrap">
-                        <el-input
-                          v-model="item.paymentBase"
-                          placeholder="鏍规嵁鍩烘湰宸ヨ祫缂寸撼"
-                          clearable
-                          style="width: 120px"
-                          type="number"
-                          :disabled="isDetail || item.useBasicSalary"
-                          @input="handlePaymentBaseInput(item)"
-                        />
-                        <el-checkbox
-                          v-model="item.useBasicSalary"
-                          @change="handleUseBasicSalaryChange(item)"
-                          :disabled="isDetail"
-                        >
+                        <el-input v-model="item.paymentBase"
+                                  placeholder="鏍规嵁鍩烘湰宸ヨ祫缂寸撼"
+                                  clearable
+                                  style="width: 120px"
+                                  type="number"
+                                  :disabled="isDetail || item.useBasicSalary"
+                                  @input="handlePaymentBaseInput(item)" />
+                        <el-checkbox v-model="item.useBasicSalary"
+                                     @change="handleUseBasicSalaryChange(item)"
+                                     :disabled="isDetail">
                           璋冪敤鍩烘湰宸ヨ祫
                         </el-checkbox>
                       </div>
                     </el-form-item>
-                    <el-form-item label="涓汉缂磋垂姣斾緥锛�" label-width="100px">
+                    <el-form-item label="涓汉缂磋垂姣斾緥锛�"
+                                  label-width="100px">
                       <div class="personal-ratio-wrap">
-                        <el-input
-                          v-model="item.personalRatio"
-                          placeholder="璇疯緭鍏�"
-                          clearable
-                          style="width: 100px"
-                          type="number"
-                          :disabled="isDetail"
-                        />
+                        <el-input v-model="item.personalRatio"
+                                  placeholder="璇疯緭鍏�"
+                                  clearable
+                                  style="width: 100px"
+                                  type="number"
+                                  :disabled="isDetail"
+                                  :min="0"
+                                  @input="handlePersonalRatioInput(item)" />
                         <span class="ratio-unit">(%)</span>
                         <span class="ratio-plus">+</span>
-                        <el-input
-                          v-model="item.personalFixed"
-                          placeholder="璇疯緭鍏�"
-                          clearable
-                          style="width: 100px"
-                          type="number"
-                          :disabled="isDetail"
-                        />
+                        <el-input v-model="item.personalFixed"
+                                  placeholder="璇疯緭鍏�"
+                                  clearable
+                                  style="width: 100px"
+                                  type="number"
+                                  :disabled="isDetail"
+                                  :min="0"
+                                  @input="handlePersonalFixedInput(item)" />
                       </div>
                     </el-form-item>
                   </div>
@@ -170,301 +154,327 @@
 </template>
 
 <script setup>
-import { ref, reactive, toRefs, getCurrentInstance, nextTick, computed } from "vue";
-import FormDialog from "@/components/Dialog/FormDialog.vue";
-import { ArrowUp } from "@element-plus/icons-vue";
-import { listDept } from "@/api/system/dept.js";
-import { socialSecurityAdd, socialSecurityUpdate } from "@/api/personnelManagement/socialSecuritySet.js";
+  import {
+    ref,
+    reactive,
+    toRefs,
+    getCurrentInstance,
+    nextTick,
+    computed,
+  } from "vue";
+  import FormDialog from "@/components/Dialog/FormDialog.vue";
+  import { ArrowUp } from "@element-plus/icons-vue";
+  import { listDept } from "@/api/system/dept.js";
+  import {
+    socialSecurityAdd,
+    socialSecurityUpdate,
+  } from "@/api/personnelManagement/socialSecuritySet.js";
 
-const emit = defineEmits(["close"]);
-const { proxy } = getCurrentInstance();
+  const emit = defineEmits(["close"]);
+  const { proxy } = getCurrentInstance();
 
-const dialogFormVisible = ref(false);
-const operationType = ref("add");
-const formRef = ref(null);
-const deptList = ref([]);
+  const dialogFormVisible = ref(false);
+  const operationType = ref("add");
+  const formRef = ref(null);
+  const deptList = ref([]);
 
-const isDetail = computed(() => operationType.value === "detail");
+  const isDetail = computed(() => operationType.value === "detail");
 
-const dialogTitle = () =>
-  operationType.value === "add"
-    ? "鏂板鏂规"
-    : operationType.value === "edit"
-    ? "缂栬緫鏂规"
-    : "鏂规璇︽儏";
+  const dialogTitle = () =>
+    operationType.value === "add"
+      ? "鏂板鏂规"
+      : operationType.value === "edit"
+      ? "缂栬緫鏂规"
+      : "鏂规璇︽儏";
 
-// 淇濋櫓绫诲瀷閫夐」锛堝彲鎸夊瓧鍏告浛鎹級
-const insuranceTypeOptions = [
-  { label: "鍏昏�佷繚闄�", value: "鍏昏�佷繚闄�" },
-  { label: "鍖荤枟淇濋櫓", value: "鍖荤枟淇濋櫓" },
-  { label: "澶变笟淇濋櫓", value: "澶变笟淇濋櫓" },
-  { label: "宸ヤ激淇濋櫓", value: "宸ヤ激淇濋櫓" },
-  { label: "鐢熻偛淇濋櫓", value: "鐢熻偛淇濋櫓" },
-  { label: "鍏Н閲�", value: "鍏Н閲�" },
-];
+  // 淇濋櫓绫诲瀷閫夐」锛堝彲鎸夊瓧鍏告浛鎹級
+  const insuranceTypeOptions = [
+    { label: "鍏昏�佷繚闄�", value: "鍏昏�佷繚闄�" },
+    { label: "鍖荤枟淇濋櫓", value: "鍖荤枟淇濋櫓" },
+    { label: "澶变笟淇濋櫓", value: "澶变笟淇濋櫓" },
+    { label: "宸ヤ激淇濋櫓", value: "宸ヤ激淇濋櫓" },
+    { label: "鐢熻偛淇濋櫓", value: "鐢熻偛淇濋櫓" },
+    { label: "鍏Н閲�", value: "鍏Н閲�" },
+  ];
 
-const defaultBenefit = () => ({
-  _key: Math.random().toString(36).slice(2),
-  insuranceType: "",
-  paymentBase: "",
-  useBasicSalary: false,
-  personalRatio: "",
-  personalFixed: "",
-});
+  const defaultBenefit = () => ({
+    _key: Math.random().toString(36).slice(2),
+    insuranceType: "",
+    paymentBase: "",
+    useBasicSalary: false,
+    personalRatio: "",
+    personalFixed: "",
+  });
 
-const data = reactive({
-  form: {
-    id: undefined,
-    title: "",
-    remark: "",
-    deptIds: [],
-    insuranceBenefits: [defaultBenefit()],
-  },
-  rules: {
-    title: [{ required: true, message: "璇疯緭鍏ユ柟妗堟爣棰�", trigger: "blur" }],
-    deptIds: [
-      {
-        required: true,
-        type: "array",
-        min: 1,
-        message: "璇疯嚦灏戦�夋嫨涓�涓�傜敤閮ㄩ棬",
-        trigger: "change",
-      },
-    ],
-  },
-});
-const { form, rules } = toRefs(data);
+  const data = reactive({
+    form: {
+      id: undefined,
+      title: "",
+      remark: "",
+      deptIds: [],
+      insuranceBenefits: [defaultBenefit()],
+    },
+    rules: {
+      title: [{ required: true, message: "璇疯緭鍏ユ柟妗堟爣棰�", trigger: "blur" }],
+      deptIds: [
+        {
+          required: true,
+          type: "array",
+          min: 1,
+          message: "璇疯嚦灏戦�夋嫨涓�涓�傜敤閮ㄩ棬",
+          trigger: "change",
+        },
+      ],
+    },
+  });
+  const { form, rules } = toRefs(data);
 
-function flattenDept(tree, list = []) {
-  if (!tree || !tree.length) return list;
-  tree.forEach((node) => {
-    list.push({
-      deptId: node.deptId,
-      deptName: node.deptName,
-      personCount: node.personCount ?? null,
+  function flattenDept(tree, list = []) {
+    if (!tree || !tree.length) return list;
+    tree.forEach(node => {
+      list.push({
+        deptId: node.deptId,
+        deptName: node.deptName,
+        personCount: node.personCount ?? null,
+      });
+      if (node.children && node.children.length) {
+        flattenDept(node.children, list);
+      }
     });
-    if (node.children && node.children.length) {
-      flattenDept(node.children, list);
-    }
-  });
-  return list;
-}
-
-const loadDeptList = () => {
-  listDept().then((res) => {
-    const tree = res.data ?? [];
-    deptList.value = flattenDept(tree);
-  });
-};
-
-const addInsuranceBenefit = () => {
-  form.value.insuranceBenefits.push(defaultBenefit());
-};
-
-const removeInsuranceBenefit = (index) => {
-  form.value.insuranceBenefits.splice(index, 1);
-};
-
-const handleUseBasicSalaryChange = (item) => {
-  if (item.useBasicSalary) {
-    item.paymentBase = "";
+    return list;
   }
-};
 
-const handlePaymentBaseInput = (item) => {
-  if (item.paymentBase !== "" && item.paymentBase != null) {
-    item.useBasicSalary = false;
-  }
-};
-
-const resetForm = () => {
-  form.value = {
-    id: undefined,
-    title: "",
-    remark: "",
-    deptIds: [],
-    insuranceBenefits: [defaultBenefit()],
+  const loadDeptList = () => {
+    listDept().then(res => {
+      const tree = res.data ?? [];
+      deptList.value = flattenDept(tree);
+    });
   };
-};
 
-const openDialog = (type, row) => {
-  operationType.value = type;
-  dialogFormVisible.value = true;
-  loadDeptList();
-  resetForm();
-  if ((type === "edit" || type === "detail") && row) {
-    const d = row || {};
-    form.value.id = d.id;
-    form.value.title = d.title;
-    form.value.remark = d.remark ?? "";
-    // deptIds 鍚庣鍙兘鏄�楀彿鍒嗛殧瀛楃涓叉垨鏁扮粍锛岃繖閲岀粺涓�杞负鏁扮粍骞跺敖閲忚繕鍘熸暟鍊肩被鍨�
-    if (d.deptIds) {
-      form.value.deptIds = String(d.deptIds)
-        .split(",")
-        .filter((v) => v !== "")
-        .map((v) => {
-          const num = Number(v);
-          return Number.isNaN(num) ? v : num;
-        });
-    } else {
-      form.value.deptIds = [];
+  const addInsuranceBenefit = () => {
+    form.value.insuranceBenefits.push(defaultBenefit());
+  };
+
+  const removeInsuranceBenefit = index => {
+    form.value.insuranceBenefits.splice(index, 1);
+  };
+
+  const handleUseBasicSalaryChange = item => {
+    if (item.useBasicSalary) {
+      item.paymentBase = "";
     }
-    const detailList = d.schemeInsuranceDetailList || [];
-    form.value.insuranceBenefits =
-      detailList.length > 0
-        ? detailList.map((b) => ({
-            _key: Math.random().toString(36).slice(2),
-            insuranceType: b.insuranceType || "",
-            paymentBase: b.paymentBase ?? "",
-            useBasicSalary: b.useBasicSalary === 2,
-            personalRatio: b.personalRatio ?? "",
-            personalFixed: b.personalFixed ?? "",
-          }))
-        : [defaultBenefit()];
-  }
-};
+  };
 
-const submitForm = () => {
-  // 璇︽儏妯″紡涓嬩笉鎻愪氦锛屽彧鍏抽棴寮圭獥
-  if (operationType.value === "detail") {
-    closeDia();
-    return;
-  }
-  formRef.value?.validate((valid) => {
-    if (!valid) return;
-    const deptIds =
-      Array.isArray(form.value.deptIds) && form.value.deptIds.length
-        ? form.value.deptIds.join(",")
-        : "";
-    const schemeInsuranceDetailList = (form.value.insuranceBenefits || []).map(
-      ({ _key, ...rest }) => ({
-        ...rest,
-        useBasicSalary: rest.useBasicSalary ? 2 : 1,
-      })
-    );
-    const insuranceTypes = schemeInsuranceDetailList
-      .map((item) => item.insuranceType)
-      .filter((v) => v)
-      .join(",");
-    // 閮ㄩ棬鍚嶇О锛屽涓娇鐢ㄩ�楀彿闅斿紑锛堟牴鎹�変腑鐨� deptIds 涓� deptList 璁$畻锛�
-    const deptNames = (deptList.value || [])
-      .filter((d) =>
-        (form.value.deptIds || []).some(
-          (id) => String(id) === String(d.deptId)
-        )
-      )
-      .map((d) => d.deptName)
-      .join(",");
-    const submitData = {
-      id: form.value.id,
-      title: form.value.title,
-      remark: form.value.remark ?? "",
-      deptIds,
-      insuranceTypes,
-      deptNames,
-      schemeInsuranceDetailList,
+  const handlePaymentBaseInput = item => {
+    if (item.paymentBase !== "" && item.paymentBase != null) {
+      item.useBasicSalary = false;
+    }
+  };
+
+  const handlePersonalRatioInput = item => {
+    if (item.personalRatio !== "" && item.personalRatio != null) {
+      const value = Number(item.personalRatio);
+      if (value < 0) {
+        item.personalRatio = "";
+      }
+    }
+  };
+
+  const handlePersonalFixedInput = item => {
+    if (item.personalFixed !== "" && item.personalFixed != null) {
+      const value = Number(item.personalFixed);
+      if (value < 0) {
+        item.personalFixed = "";
+      }
+    }
+  };
+
+  const resetForm = () => {
+    form.value = {
+      id: undefined,
+      title: "",
+      remark: "",
+      deptIds: [],
+      insuranceBenefits: [defaultBenefit()],
     };
-    if (operationType.value === "add") {
-      socialSecurityAdd(submitData).then(() => {
-        proxy.$modal.msgSuccess("鏂板鎴愬姛");
-        closeDia();
-      });
-    } else {
-      socialSecurityUpdate(submitData).then(() => {
-        proxy.$modal.msgSuccess("淇敼鎴愬姛");
-        closeDia();
-      });
+  };
+
+  const openDialog = (type, row) => {
+    operationType.value = type;
+    dialogFormVisible.value = true;
+    loadDeptList();
+    resetForm();
+    if ((type === "edit" || type === "detail") && row) {
+      const d = row || {};
+      form.value.id = d.id;
+      form.value.title = d.title;
+      form.value.remark = d.remark ?? "";
+      // deptIds 鍚庣鍙兘鏄�楀彿鍒嗛殧瀛楃涓叉垨鏁扮粍锛岃繖閲岀粺涓�杞负鏁扮粍骞跺敖閲忚繕鍘熸暟鍊肩被鍨�
+      if (d.deptIds) {
+        form.value.deptIds = String(d.deptIds)
+          .split(",")
+          .filter(v => v !== "")
+          .map(v => {
+            const num = Number(v);
+            return Number.isNaN(num) ? v : num;
+          });
+      } else {
+        form.value.deptIds = [];
+      }
+      const detailList = d.schemeInsuranceDetailList || [];
+      form.value.insuranceBenefits =
+        detailList.length > 0
+          ? detailList.map(b => ({
+              _key: Math.random().toString(36).slice(2),
+              insuranceType: b.insuranceType || "",
+              paymentBase: b.paymentBase ?? "",
+              useBasicSalary: b.useBasicSalary === 2,
+              personalRatio: b.personalRatio ?? "",
+              personalFixed: b.personalFixed ?? "",
+            }))
+          : [defaultBenefit()];
     }
-  });
-};
+  };
 
-const closeDia = () => {
-  proxy.resetForm?.("formRef");
-  dialogFormVisible.value = false;
-  emit("close");
-};
+  const submitForm = () => {
+    // 璇︽儏妯″紡涓嬩笉鎻愪氦锛屽彧鍏抽棴寮圭獥
+    if (operationType.value === "detail") {
+      closeDia();
+      return;
+    }
+    formRef.value?.validate(valid => {
+      if (!valid) return;
+      const deptIds =
+        Array.isArray(form.value.deptIds) && form.value.deptIds.length
+          ? form.value.deptIds.join(",")
+          : "";
+      const schemeInsuranceDetailList = (form.value.insuranceBenefits || []).map(
+        ({ _key, ...rest }) => ({
+          ...rest,
+          useBasicSalary: rest.useBasicSalary ? 2 : 1,
+        })
+      );
+      const insuranceTypes = schemeInsuranceDetailList
+        .map(item => item.insuranceType)
+        .filter(v => v)
+        .join(",");
+      // 閮ㄩ棬鍚嶇О锛屽涓娇鐢ㄩ�楀彿闅斿紑锛堟牴鎹�変腑鐨� deptIds 涓� deptList 璁$畻锛�
+      const deptNames = (deptList.value || [])
+        .filter(d =>
+          (form.value.deptIds || []).some(id => String(id) === String(d.deptId))
+        )
+        .map(d => d.deptName)
+        .join(",");
+      const submitData = {
+        id: form.value.id,
+        title: form.value.title,
+        remark: form.value.remark ?? "",
+        deptIds,
+        insuranceTypes,
+        deptNames,
+        schemeInsuranceDetailList,
+      };
+      if (operationType.value === "add") {
+        socialSecurityAdd(submitData).then(() => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          closeDia();
+        });
+      } else {
+        socialSecurityUpdate(submitData).then(() => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          closeDia();
+        });
+      }
+    });
+  };
 
-defineExpose({ openDialog });
+  const closeDia = () => {
+    proxy.resetForm?.("formRef");
+    dialogFormVisible.value = false;
+    emit("close");
+  };
+
+  defineExpose({ openDialog });
 </script>
 
 <style scoped>
-.card-title-line {
-  color: #f56c6c;
-  margin-right: 4px;
-}
-.form-card {
-  margin-bottom: 16px;
-}
-.form-card :deep(.el-card__header) {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 12px 16px;
-}
-.card-title {
-  font-weight: 500;
-}
-.card-collapse {
-  color: #999;
-  cursor: pointer;
-}
-.dept-checkbox-wrap {
-  max-height: 320px;
-  overflow-y: auto;
-  padding: 8px 0;
-  border: 1px solid var(--el-border-color);
-  border-radius: 4px;
-  background: #fff;
-}
-.dept-checkbox-item {
-  padding: 6px 12px;
-}
-.dept-count {
-  color: #909399;
-  font-size: 12px;
-  margin-left: 4px;
-}
-.insurance-benefit-card {
-  border: 1px solid var(--el-border-color-lighter);
-  border-radius: 4px;
-  padding: 12px 16px;
-  margin-bottom: 12px;
-  background: #fafafa;
-}
-.insurance-benefit-title {
-  font-size: 14px;
-  margin-bottom: 12px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-.card-delete-btn {
-  margin-left: auto;
-}
-.checkbox-group-inline {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16px;
-}
-.base-salary-wrap {
-  display: flex;
-  flex-wrap: wrap;
-  align-items: center;
-  gap: 8px;
-}
-.base-salary-text {
-  color: #606266;
-  font-size: 14px;
-}
-.personal-ratio-wrap {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-.ratio-unit,
-.ratio-plus {
-  color: #606266;
-  font-size: 14px;
-}
+  .card-title-line {
+    color: #f56c6c;
+    margin-right: 4px;
+  }
+  .form-card {
+    margin-bottom: 16px;
+  }
+  .form-card :deep(.el-card__header) {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 16px;
+  }
+  .card-title {
+    font-weight: 500;
+  }
+  .card-collapse {
+    color: #999;
+    cursor: pointer;
+  }
+  .dept-checkbox-wrap {
+    max-height: 320px;
+    overflow-y: auto;
+    padding: 8px 0;
+    border: 1px solid var(--el-border-color);
+    border-radius: 4px;
+    background: #fff;
+  }
+  .dept-checkbox-item {
+    padding: 6px 12px;
+  }
+  .dept-count {
+    color: #909399;
+    font-size: 12px;
+    margin-left: 4px;
+  }
+  .insurance-benefit-card {
+    border: 1px solid var(--el-border-color-lighter);
+    border-radius: 4px;
+    padding: 12px 16px;
+    margin-bottom: 12px;
+    background: #fafafa;
+  }
+  .insurance-benefit-title {
+    font-size: 14px;
+    margin-bottom: 12px;
+    font-weight: 500;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .card-delete-btn {
+    margin-left: auto;
+  }
+  .checkbox-group-inline {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 16px;
+  }
+  .base-salary-wrap {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 8px;
+  }
+  .base-salary-text {
+    color: #606266;
+    font-size: 14px;
+  }
+  .personal-ratio-wrap {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  .ratio-unit,
+  .ratio-plus {
+    color: #606266;
+    font-size: 14px;
+  }
 </style>
diff --git a/src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue b/src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue
new file mode 100644
index 0000000..f0ac5a6
--- /dev/null
+++ b/src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue
@@ -0,0 +1,236 @@
+<template>
+  <div class="material-node">
+    <!-- 褰撳墠鑺傜偣鍗$墖 -->
+    <div :class="['node-card', isRoot ? 'root-card' : (row.nodeType === 'semiFinished' ? 'semi-finished-card' : 'child-card')]">
+      <div class="node-header">
+        <div class="node-label">
+          <el-tag :type="isRoot ? '' : (row.nodeType === 'semiFinished' ? 'warning' : 'success')" size="small" effect="dark">
+            {{ isRoot ? '鎴愬搧' : (row.nodeType === 'semiFinished' ? '鍗婃垚鍝�' : '鍘熸枡') }}
+          </el-tag>
+          <span class="node-title">{{ row.productName || '鏈�夋嫨浜у搧' }}</span>
+          <span v-if="row.model" class="node-sub">瑙勬牸: {{ row.model }}</span>
+          <span v-if="row.unit" class="node-sub">鍗曚綅: {{ row.unit }}</span>
+        </div>
+        <div class="node-actions">
+          <template v-if="editable && (isRoot || row.nodeType === 'semiFinished')">
+            <el-button type="primary"
+                       text
+                       size="small"
+                       @click="handleAdd('semiFinished')">
+              + 娣诲姞鍗婃垚鍝�
+            </el-button>
+            <el-button type="primary"
+                       text
+                       size="small"
+                       @click="handleAdd('rawMaterial')">
+              + 娣诲姞鍘熸枡
+            </el-button>
+          </template>
+          <el-button v-if="editable"
+                     type="danger"
+                     text
+                     size="small"
+                     @click="$emit('remove', row.tempId)">
+            鍒犻櫎
+          </el-button>
+        </div>
+      </div>
+
+      <!-- 缂栬緫妯″紡涓嬬殑琛ㄥ崟 -->
+      <div v-if="editable" class="node-body">
+        <el-row :gutter="12">
+          <el-col :span="7">
+            <el-form-item label="浜у搧" :rules="[{ required: true, message: '璇烽�夋嫨浜у搧' }]" style="margin:0">
+              <el-input :model-value="row.productName || ''"
+                        readonly
+                        placeholder="鐐瑰嚮閫夋嫨浜у搧"
+                        @click="openSelect"
+                        style="width:100%">
+                <template #suffix>
+                  <el-icon><component :is="SearchIcon" /></el-icon>
+                </template>
+              </el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="5">
+            <el-form-item label="瑙勬牸" style="margin:0">
+              <el-select v-model="row.model"
+                         placeholder="璇烽�夋嫨瑙勬牸"
+                         clearable
+                         style="width:100%"
+                         @visible-change="(v:boolean) => { if (v) openSelect() }">
+                <el-option v-if="row.model" :label="row.model" :value="row.model" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col v-if="!isRoot" :span="5">
+            <el-form-item label="宸ュ簭" :rules="[{ required: true, message: '璇烽�夋嫨宸ュ簭' }]" style="margin:0">
+              <el-select v-model="row.processId"
+                         placeholder="璇烽�夋嫨"
+                         filterable
+                         clearable
+                         style="width:100%"
+                         @change="(v:any) => $emit('processChange', row, v)">
+                <el-option v-for="item in processOptions"
+                           :key="item.id"
+                           :label="item.name"
+                           :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="鏁伴噺" :rules="[{ required: true, message: '璇峰~鍐欐暟閲�' }]" style="margin:0">
+              <el-input-number v-model="row.unitQuantity"
+                               :min="0"
+                               :precision="2"
+                               :step="1"
+                               controls-position="right"
+                               style="width:100%"
+                               @change="$emit('quantityChange')" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="3">
+            <el-form-item label="鍗曚綅" style="margin:0">
+              <el-input v-model="row.unit" placeholder="鍗曚綅" clearable style="width:100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </div>
+
+      <!-- 闈炵紪杈戞ā寮忥細绠�娲佹樉绀� -->
+      <div v-else class="node-view">
+        <span v-if="!isRoot && row.processName">宸ュ簭: {{ row.processName }} | </span>
+        <span>鏁伴噺: {{ row.unitQuantity || '-' }}</span>
+      </div>
+    </div>
+
+    <!-- 閫掑綊娓叉煋瀛愯妭鐐� -->
+    <div v-if="row.children && row.children.length > 0" class="node-children">
+      <MaterialCard
+        v-for="child in row.children"
+        :key="child.tempId"
+        :row="child"
+        :depth="depth + 1"
+        :editable="editable"
+        :process-options="processOptions"
+        @remove="(id:string) => $emit('remove', id)"
+        @add="(id:string, nodeType:string) => $emit('add', id, nodeType)"
+        @select-product="(tempId: string, data: any) => $emit('selectProduct', tempId, data)"
+        @process-change="(row: any, v: any) => $emit('processChange', row, v)"
+        @quantity-change="$emit('quantityChange')"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { Search } from '@element-plus/icons-vue'
+
+const SearchIcon = Search
+
+const props = defineProps<{
+  row: any
+  depth: number
+  editable: boolean
+  processOptions: any[]
+}>()
+
+const emit = defineEmits<{
+  remove: [tempId: string]
+  add: [tempId: string, nodeType: string]
+  selectProduct: [tempId: string, data: any]
+  processChange: [row: any, value: any]
+  quantityChange: []
+}>()
+
+const isRoot = computed(() => props.depth === 0)
+
+const openSelect = () => {
+  emit('selectProduct', props.row.tempId, null)
+}
+
+const handleAdd = (nodeType: string) => {
+  emit('add', props.row.tempId, nodeType)
+}
+</script>
+
+<script lang="ts">
+export default { name: 'MaterialCard' }
+</script>
+
+<style scoped>
+.material-node {
+  margin: 4px 0;
+}
+
+.node-card {
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.root-card {
+  border-color: #409eff;
+  border-left: 4px solid #409eff;
+  background-color: #f0f5ff;
+}
+
+.child-card {
+  border-left: 4px solid #67c23a;
+  background-color: #f0f9eb;
+}
+
+.semi-finished-card {
+  border-left: 4px solid #e6a23c;
+  background-color: #fdf6ec;
+}
+
+.node-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 12px;
+  background-color: rgba(0,0,0,0.03);
+  flex-wrap: wrap;
+  gap: 4px;
+}
+
+.node-label {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+
+.node-title {
+  font-weight: 600;
+  color: #303133;
+}
+
+.node-sub {
+  font-size: 12px;
+  color: #909399;
+}
+
+.node-actions {
+  display: flex;
+  gap: 4px;
+}
+
+.node-body {
+  padding: 10px 12px;
+}
+
+.node-view {
+  padding: 6px 12px;
+  font-size: 13px;
+  color: #606266;
+}
+
+.node-children {
+  margin-left: 36px;
+  padding-left: 16px;
+  border-left: 2px dashed #dcdfe6;
+}
+</style>
diff --git a/src/views/productionManagement/productStructure/DetailNew/index.vue b/src/views/productionManagement/productStructure/DetailNew/index.vue
new file mode 100644
index 0000000..1262c80
--- /dev/null
+++ b/src/views/productionManagement/productStructure/DetailNew/index.vue
@@ -0,0 +1,626 @@
+<template>
+  <div class="app-container">
+    <PageHeader content="浜у搧缁撴瀯璇︽儏">
+      <template #right-button>
+        <el-button v-if="!dataValue.isEdit && !isOrderPage"
+                   type="primary"
+                   @click="dataValue.isEdit = true">缂栬緫
+        </el-button>
+        <el-button v-if="dataValue.isEdit && !isOrderPage"
+                   type="primary"
+                   @click="cancelEdit">鍙栨秷
+        </el-button>
+        <el-button v-if="!isOrderPage"
+                   type="primary"
+                   :loading="dataValue.loading"
+                   @click="submit"
+                   :disabled="!dataValue.isEdit">纭
+        </el-button>
+      </template>
+    </PageHeader>
+    <el-table :data="tableData"
+              border
+              :preserve-expanded-content="false"
+              :default-expand-all="true"
+              style="width: 100%">
+      <el-table-column type="expand">
+        <template #default="props">
+          <el-form ref="form" :model="dataValue">
+            <div class="tree-container">
+              <div class="tree-legend">
+                <el-tag type="" size="small" effect="dark">鎴愬搧</el-tag>
+                <span style="margin:0 4px">鈫� 鏈�涓婂眰锛堜骇鍑虹墿锛�</span>
+                <el-divider direction="vertical" />
+                <el-tag type="warning" size="small" effect="dark">鍗婃垚鍝�</el-tag>
+                <span style="margin:0 4px">锛堝彲缁х画灞曞紑锛�</span>
+                <el-divider direction="vertical" />
+                <span style="margin:0 4px">鏈�涓嬪眰锛堟姇鍏ョ墿锛夆啋</span>
+                <el-tag type="success" size="small" effect="dark">鍘熸枡</el-tag>
+              </div>
+
+              <div v-if="dataValue.dataList.length === 0 && dataValue.isEdit" class="empty-hint">
+                璇风偣鍑讳笅鏂规寜閽坊鍔犳垚鍝�
+              </div>
+
+              <MaterialCard
+                v-for="(item, index) in dataValue.dataList"
+                :key="item.tempId"
+                :row="item"
+                :depth="0"
+                :editable="dataValue.isEdit"
+                :process-options="dataValue.processOptions"
+                @remove="(id: string) => removeItem(id)"
+                @add="(id: string, nodeType: string) => addChildItem(id, nodeType)"
+                @select-product="(tempId: string, _data: any) => { dataValue.currentRowName = tempId; dataValue.showProductDialog = true }"
+                @process-change="(row: any, v: any) => handleProcessChange(row, v)"
+                @quantity-change="handleUnitQuantityChange"
+              />
+
+              <el-button v-if="dataValue.isEdit"
+                         type="primary"
+                         plain
+                         style="margin-top:12px"
+                         @click="addRootItem">
+                + 娣诲姞鎴愬搧
+              </el-button>
+            </div>
+          </el-form>
+        </template>
+      </el-table-column>
+      <el-table-column label="BOM缂栧彿"
+                       prop="bomNo" />
+      <el-table-column label="浜у搧鍚嶇О"
+                       prop="productName" />
+      <el-table-column label="瑙勬牸鍨嬪彿"
+                       prop="model" />
+    </el-table>
+    <product-select-dialog v-if="dataValue.showProductDialog"
+                           v-model:model-value="dataValue.showProductDialog"
+                           :single="true"
+                           @confirm="handleProduct" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import {
+    computed,
+    defineAsyncComponent,
+    defineComponent,
+    onMounted,
+    reactive,
+    ref,
+  } from "vue";
+  import {
+    queryList,
+    addBomDetail,
+  } from "@/api/productionManagement/productStructure.js";
+  import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
+  import { list } from "@/api/productionManagement/productionProcess";
+  import { ElMessage } from "element-plus";
+  import { useRoute, useRouter } from "vue-router";
+
+  defineComponent({
+    name: "StructureEdit",
+  });
+
+  const ProductSelectDialog = defineAsyncComponent(
+    () => import("@/views/basicData/product/ProductSelectDialog.vue")
+  );
+  import MaterialCard from "./MaterialCard.vue";
+  const emit = defineEmits(["update:router"]);
+  const form = ref();
+
+  const route = useRoute();
+  const router = useRouter();
+  const routeId = computed({
+    get() {
+      return route.query.id;
+    },
+
+    set(val) {
+      emit("update:router", val);
+    },
+  });
+
+  // 浠庤矾鐢卞弬鏁拌幏鍙栦骇鍝佷俊鎭�
+  const routeBomNo = computed(() => route.query.bomNo || "");
+  const routeProductName = computed(() => route.query.productName || "");
+  const routeProductModelName = computed(
+    () => route.query.productModelName || ""
+  );
+  const routeOrderId = computed(() => route.query.orderId);
+  const pageType = computed(() => route.query.type);
+  const isOrderPage = computed(
+    () => pageType.value === "order" && routeOrderId.value
+  );
+
+  const dataValue = reactive({
+    dataList: [],
+    productOptions: [],
+    processOptions: [],
+    showProductDialog: false,
+    currentRowIndex: null,
+    currentRowName: null,
+    loading: false,
+    isEdit: false,
+  });
+
+  const normalizeListData = (source: any) => {
+    if (Array.isArray(source)) {
+      return source;
+    }
+    if (Array.isArray(source?.records)) {
+      return source.records;
+    }
+    return [];
+  };
+
+  const getProcessOptionById = (id: any) => {
+    if (id === undefined || id === null || id === "") {
+      return null;
+    }
+    return (
+      normalizeListData(dataValue.processOptions).find(
+        option => String(option.id) === String(id)
+      ) || null
+    );
+  };
+
+  const syncProcessOperationFields = (item: any) => {
+    const processId = item.processId ?? item.operationId ?? "";
+    if (!processId) {
+      item.processId = "";
+      item.operationId = "";
+      item.processName = "";
+      item.operationName = "";
+      return;
+    }
+
+    const option = getProcessOptionById(processId);
+    const processName =
+      option?.name || item.processName || item.operationName || "";
+
+    item.processId = processId;
+    item.operationId = processId;
+    item.processName = processName;
+    item.operationName = processName;
+  };
+
+  const normalizeTreeData = (items: any[], depth: number = 0) => {
+    items.forEach((item: any) => {
+      item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`;
+      syncProcessOperationFields(item);
+      if (depth > 0 && !item.nodeType) {
+        item.nodeType = Array.isArray(item.children) && item.children.length > 0
+          ? 'semiFinished'
+          : 'rawMaterial';
+      }
+      if (Array.isArray(item.children) && item.children.length > 0) {
+        normalizeTreeData(item.children, depth + 1);
+      }
+    });
+  };
+
+  const toQuantityNumber = (value: any) => {
+    const numberValue = Number(value);
+    if (!Number.isFinite(numberValue)) {
+      return 0;
+    }
+    return Number(numberValue.toFixed(2));
+  };
+
+  const syncDemandedQuantityTree = (
+    items: any[],
+    parentDemandedQuantity: number | null = null
+  ) => {
+    items.forEach((item: any) => {
+      if (parentDemandedQuantity !== null) {
+        item.demandedQuantity = toQuantityNumber(
+          parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
+        );
+      }
+
+      if (Array.isArray(item.children) && item.children.length > 0) {
+        syncDemandedQuantityTree(
+          item.children,
+          toQuantityNumber(item.demandedQuantity)
+        );
+      }
+    });
+  };
+
+  const recalculateDemandedQuantities = () => {
+    if (!isOrderPage.value) {
+      return;
+    }
+
+    syncDemandedQuantityTree(dataValue.dataList);
+  };
+
+  const buildSubmitTree = (items: any[]) => {
+    return items.map((item: any) => {
+      const current = { ...item };
+      syncProcessOperationFields(current);
+      current.children = Array.isArray(current.children)
+        ? buildSubmitTree(current.children)
+        : [];
+      return current;
+    });
+  };
+
+  const findSiblings = (items: any[], tempId: string): any[] | null => {
+    if (!items || items.length === 0) return null;
+    // 妫�鏌ュ綋鍓嶅眰绾�
+    if (items.some(item => item.tempId === tempId)) {
+      return items;
+    }
+    // 閫掑綊鏌ユ壘瀛愮骇
+    for (const item of items) {
+      if (item.children && item.children.length > 0) {
+        const result = findSiblings(item.children, tempId);
+        if (result) return result;
+      }
+    }
+    return null;
+  };
+
+  const handleProcessChange = (row: any, value: any) => {
+    row.processId = value || "";
+    syncProcessOperationFields(row);
+    
+    // 妫�鏌ュ悓涓�灞傜骇鏄惁宸茬粡鏈夊叾浠栦笉鍚岀殑宸ュ簭琚�変腑
+    const siblings = findSiblings(dataValue.dataList, row.tempId);
+    if (siblings && value) {
+      const hasDifferentProcess = siblings.some(sibling => {
+        return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value;
+      });
+      if (hasDifferentProcess) {
+        ElMessage.warning("鍚屼竴灞傜骇宸插瓨鍦ㄤ笉鍚岀殑宸ュ簭锛岃鍏堢粺涓�宸ュ簭鍚庡啀杩涜淇敼");
+      }
+    }
+  };
+
+  const handleUnitQuantityChange = () => {
+    recalculateDemandedQuantities();
+  };
+
+  const tableData = reactive([
+    {
+      productName: "",
+      model: "",
+      bomNo: "",
+    },
+  ]);
+
+  const openDialog = (tempId: any) => {
+    console.log(tempId, "tempId");
+    dataValue.currentRowName = tempId;
+    dataValue.showProductDialog = true;
+  };
+
+  const fetchData = async () => {
+    if (isOrderPage.value) {
+      // 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
+      const { data } = await listProcessBom({ orderId: routeOrderId.value });
+      dataValue.dataList = (data as any) || [];
+      normalizeTreeData(dataValue.dataList);
+      recalculateDemandedQuantities();
+    } else {
+      // 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
+      const { data } = await queryList(routeId.value);
+      dataValue.dataList = (data as any) || [];
+      console.log(dataValue);
+      normalizeTreeData(dataValue.dataList);
+      console.log(dataValue.dataList, "dataValue.dataList");
+    }
+  };
+
+  const fetchProcessOptions = async () => {
+    const { data } = await list({});
+    console.log(data, "dataValue.dataList");
+    dataValue.processOptions = normalizeListData(data);
+  };
+
+  const handleProduct = (row: any) => {
+    if (!Array.isArray(row) || row.length === 0) {
+      ElMessage.warning("璇烽�夋嫨涓�涓骇鍝�");
+      return;
+    }
+    // 鍙厑璁镐竴涓細濡傛灉涓婃父杩斿洖浜嗗涓紝榛樿浣跨敤鏈�鍚庝竴娆¢�夋嫨骞惰鐩栧綋鍓嶅��
+    const productData = row[row.length - 1];
+
+    //  鏈�澶栧眰缁勪欢涓紝涓庡綋鍓嶄骇鍝佺浉鍚岀殑浜у搧鍙兘鏈変竴涓�
+    const isTopLevel = dataValue.dataList.some(
+      item => (item as any).tempId === dataValue.currentRowName
+    );
+    if (isTopLevel) {
+      if (
+        productData.productName === tableData[0].productName &&
+        productData.model === tableData[0].model
+      ) {
+        //  鏌ユ壘鏄惁宸茬粡鏈夊叾浠栭《灞傝宸茬粡鏄繖涓骇鍝�
+        const hasOther = dataValue.dataList.some(
+          item =>
+            (item as any).tempId !== dataValue.currentRowName &&
+            (item as any).productName === tableData[0].productName &&
+            (item as any).model === tableData[0].model
+        );
+        if (hasOther) {
+          ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
+          return;
+        }
+      }
+    }
+    // dataValue.dataList[dataValue.currentRowIndex].productName =
+    //   row[0].productName;
+    // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
+    // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+    // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
+    dataValue.dataList.map(item => {
+      if (item.tempId === dataValue.currentRowName) {
+        item.productName = productData.productName;
+        item.model = productData.model;
+        item.productModelId = productData.id;
+        item.unit = productData.unit || "";
+        return;
+      }
+      childItem(item, dataValue.currentRowName, productData);
+    });
+    dataValue.showProductDialog = false;
+  };
+  const childItem = (item: any, tempId: any, productData: any) => {
+    if (item.tempId === tempId) {
+      item.productName = productData.productName;
+      item.model = productData.model;
+      item.productModelId = productData.id;
+      item.unit = productData.unit || "";
+      return true;
+    }
+    if (item.children && item.children.length > 0) {
+      for (let child of item.children) {
+        if (childItem(child, tempId, productData)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  };
+
+  // 閫掑綊鏍¢獙鎵�鏈夊眰绾х殑琛ㄥ崟鏁版嵁
+  const validateAll = () => {
+    let isValid = true;
+
+    // 鏍¢獙涓�缁勫厔寮熻妭鐐圭殑宸ュ簭鏄惁閮界浉鍚�
+    const checkProcessUniqueness = (items: any[]) => {
+      if (!items || items.length === 0 || !isValid) return;
+
+      // 鑾峰彇绗竴涓潪绌虹殑宸ュ簭ID浣滀负鍙傝��
+      const firstProcessId = items.find(item => item.processId)?.processId;
+      
+      // 濡傛灉鏈夊伐搴廔D锛屾鏌ユ墍鏈夐」鏄惁閮戒娇鐢ㄧ浉鍚岀殑宸ュ簭
+      if (firstProcessId) {
+        for (const item of items) {
+          if (item.processId && item.processId !== firstProcessId) {
+            const option1 = getProcessOptionById(firstProcessId);
+            const option2 = getProcessOptionById(item.processId);
+            const processName1 = option1?.name || "鏈煡宸ュ簭";
+            const processName2 = option2?.name || "鏈煡宸ュ簭";
+            ElMessage.error(
+              `褰撳墠灞傜骇涓嬪伐搴忎笉涓�鑷达紝璇蜂娇鐢ㄧ浉鍚岀殑宸ュ簭銆傚瓨鍦ㄣ��${processName1}銆嶅拰銆�${processName2}銆峘
+            );
+            isValid = false;
+            return;
+          }
+        }
+      }
+
+      // 閫掑綊鏍¢獙瀛愮骇鐨勫厔寮熻妭鐐�
+      for (const item of items) {
+        if (item.children && item.children.length > 0) {
+          checkProcessUniqueness(item.children);
+        }
+      }
+    };
+
+    // 鏍¢獙鍑芥暟
+    const validateItem = (item: any, isTopLevel = false) => {
+      if (!isValid) return;
+      // 鏍¢獙褰撳墠椤圭殑蹇呭~瀛楁
+      if (!item.model) {
+        ElMessage.error("璇烽�夋嫨瑙勬牸");
+        isValid = false;
+        return;
+      }
+      if (!isTopLevel && !item.processId) {
+        ElMessage.error("璇烽�夋嫨娑堣�楀伐搴�");
+        isValid = false;
+        return;
+      }
+      if (!item.unitQuantity) {
+        ElMessage.error("璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺");
+        isValid = false;
+        return;
+      }
+      if (isOrderPage.value && !item.demandedQuantity) {
+        ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
+        isValid = false;
+        return;
+      }
+      // if (!item.unit) {
+      //   ElMessage.error("璇疯緭鍏ュ崟浣�");
+      //   isValid = false;
+      //   return;
+      // }
+
+      // 閫掑綊鏍¢獙瀛愰」瀛楁
+      if (item.children && item.children.length > 0) {
+        item.children.forEach(child => {
+          validateItem(child, false);
+        });
+      }
+    };
+
+    // 1. 棣栧厛鏍¢獙鍚屼竴鐖剁骇涓嬬殑鍚屽眰娑堣�楀伐搴忔槸鍚﹀敮涓�
+    checkProcessUniqueness(dataValue.dataList);
+    if (!isValid) return false;
+
+    // 2. 鐒跺悗閬嶅巻鏍¢獙鎵�鏈夐《灞傞」鐨勫瓧娈靛繀濉儏鍐�
+    dataValue.dataList.forEach(item => {
+      validateItem(item, true);
+    });
+
+    return isValid;
+  };
+
+  const submit = () => {
+    dataValue.loading = true;
+    normalizeTreeData(dataValue.dataList);
+    recalculateDemandedQuantities();
+
+    // 鍏堣繘琛岃〃鍗曟牎楠�
+    const valid = validateAll();
+    console.log(dataValue.dataList, "dataValue.dataList");
+    if (valid) {
+      addBomDetail({
+        bomId: routeId.value,
+        children: buildSubmitTree(dataValue.dataList || []),
+      })
+        .then(res => {
+          router.go(-1);
+          ElMessage.success("淇濆瓨鎴愬姛");
+          dataValue.loading = false;
+        })
+        .catch(() => {
+          dataValue.loading = false;
+        });
+    } else {
+      dataValue.loading = false;
+    }
+  };
+
+  const removeItem = (tempId: string) => {
+    const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId);
+    if (topIndex !== -1) {
+      dataValue.dataList.splice(topIndex, 1);
+      return;
+    }
+
+    const delchildItem = (items: any[], tempId: any) => {
+      for (let i = 0; i < items.length; i++) {
+        const item = items[i];
+        if (item.tempId === tempId) {
+          items.splice(i, 1);
+          return true;
+        }
+        if (item.children && item.children.length > 0) {
+          if (delchildItem(item.children, tempId)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    };
+
+    dataValue.dataList.forEach(item => {
+      if (item.children && item.children.length > 0) {
+        delchildItem(item.children, tempId);
+      }
+    });
+  };
+
+  const newChildNode = (parentItem: any, nodeType: string = 'rawMaterial') => ({
+    parentId: parentItem.id || "",
+    parentTempId: parentItem.tempId || "",
+    productName: "",
+    productId: "",
+    model: undefined,
+    productModelId: undefined,
+    processId: "",
+    processName: "",
+    operationId: "",
+    operationName: "",
+    unitQuantity: 1,
+    demandedQuantity: 0,
+    unit: "",
+    nodeType,
+    children: [],
+    tempId: new Date().getTime(),
+  });
+
+  const addRootItem = () => {
+    dataValue.dataList.push(newChildNode({ id: "", tempId: "" }));
+  };
+
+  const addChildItem = (parentTempId: string, nodeType: string = 'rawMaterial') => {
+    const addToItem = (items: any[]): boolean => {
+      for (const item of items) {
+        if (item.tempId === parentTempId) {
+          if (!item.children) item.children = [];
+          item.children.push(newChildNode(item, nodeType));
+          recalculateDemandedQuantities();
+          return true;
+        }
+        if (item.children?.length > 0) {
+          if (addToItem(item.children)) return true;
+        }
+      }
+      return false;
+    };
+    addToItem(dataValue.dataList);
+  };
+
+  const getPropPath = (row, field) => {
+    // 涓烘瘡涓猺ow鐢熸垚鍞竴鐨勮矾寰�
+    // 浣跨敤row.id鎴栫储寮曚綔涓哄敮涓�鏍囪瘑
+    let path = "dataList";
+
+    // 绠�鍗曞疄鐜帮細浣跨敤row鐨刬d鎴栦竴涓敮涓�鏍囪瘑
+    const uniqueId = row.id || Math.floor(Math.random() * 10000);
+    path += `.${uniqueId}`;
+
+    return path + `.${field}`;
+  };
+
+  const cancelEdit = () => {
+    dataValue.isEdit = false;
+    // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
+    fetchData();
+  };
+
+  onMounted(async () => {
+    // 浠庤矾鐢卞弬鏁板洖鏄炬暟鎹�
+    tableData[0].productName = routeProductName.value as string;
+    tableData[0].model = routeProductModelName.value as string;
+    tableData[0].bomNo = routeBomNo.value as string;
+
+    // 璁㈠崟鎯呭喌涓嬬鐢ㄧ紪杈�
+    if (isOrderPage.value) {
+      dataValue.isEdit = false;
+    }
+
+    // 鍏堝姞杞藉伐搴忛�夐」锛屽啀鍔犺浇鏁版嵁锛岀‘淇漞l-select鑳藉姝g‘鍥炴樉
+    await fetchProcessOptions();
+    await fetchData();
+  });
+</script>
+
+<style scoped>
+.tree-container {
+  padding: 8px 0;
+}
+.tree-legend {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+  padding: 8px 12px;
+  background: #f5f7fa;
+  border-radius: 6px;
+  font-size: 13px;
+  color: #606266;
+}
+.empty-hint {
+  text-align: center;
+  color: #909399;
+  padding: 24px 0;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 9c7682b..4d71881 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -487,7 +487,7 @@
     if (!Number.isFinite(n)) return 0;
     if (n <= 0) return 0;
     if (n >= 100) return 100;
-    return Math.round(n);
+    return parseFloat(n.toFixed(2));
   };
 
   // 30/50/80/100 鍒嗘棰滆壊锛氱孩/姗�/钃�/缁�
diff --git a/src/views/productionManagement/workOrder/index.vue b/src/views/productionManagement/workOrder/index.vue
index 8c76b2e..d3b0f93 100644
--- a/src/views/productionManagement/workOrder/index.vue
+++ b/src/views/productionManagement/workOrder/index.vue
@@ -475,7 +475,7 @@
     if (!Number.isFinite(n)) return 0;
     if (n <= 0) return 0;
     if (n >= 100) return 100;
-    return Math.round(n);
+    return parseFloat(n.toFixed(2));
   };
   const progressColor = percentage => {
     const p = toProgressPercentage(percentage);
diff --git a/src/views/productionManagement/workOrderEdit/index.vue b/src/views/productionManagement/workOrderEdit/index.vue
index 37cbb4e..98d29f5 100644
--- a/src/views/productionManagement/workOrderEdit/index.vue
+++ b/src/views/productionManagement/workOrderEdit/index.vue
@@ -297,7 +297,7 @@
     if (!Number.isFinite(n)) return 0;
     if (n <= 0) return 0;
     if (n >= 100) return 100;
-    return Math.round(n);
+    return parseFloat(n.toFixed(2));
   };
 
   const progressColor = percentage => {
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 8bc6dc2..b721de6 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -549,7 +549,7 @@
     if (!Number.isFinite(n)) return 0;
     if (n <= 0) return 0;
     if (n >= 100) return 100;
-    return Math.round(n);
+    return parseFloat(n.toFixed(2));
   };
   const progressColor = percentage => {
     const p = toProgressPercentage(percentage);
diff --git a/src/views/qualityManagement/metricBinding/index.vue b/src/views/qualityManagement/metricBinding/index.vue
index 1ac268a..b5d2a8f 100644
--- a/src/views/qualityManagement/metricBinding/index.vue
+++ b/src/views/qualityManagement/metricBinding/index.vue
@@ -128,6 +128,7 @@
           <el-tree-select
             v-model="selectedProductIds"
             multiple
+            filterable
             collapse-tags
             collapse-tags-tooltip
             placeholder="璇烽�夋嫨浜у搧锛堝彲澶氶�夛級"
diff --git a/vite.config.js b/vite.config.js
index fcc019d..03311be 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -12,7 +12,7 @@
           : env.VITE_BASE_API;
   const javaUrl =
       env.VITE_APP_ENV === "development"
-          ? "http://1.15.17.182:9048"
+          ? "http://1.15.17.182:9049"
           : env.VITE_JAVA_API;
   return {
     define:{

--
Gitblit v1.9.3