浏览代码

累积更新

lamphua 3 天前
父节点
当前提交
276eea2745
共有 24 个文件被更改,包括 1207 次插入411 次删除
  1. 3
    0
      oa-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  2. 5
    5
      oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java
  3. 1
    1
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java
  4. 60
    29
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java
  5. 15
    9
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java
  6. 4
    4
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java
  7. 5
    5
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java
  8. 1
    1
      oa-ui-app/api/qywx/index.js
  9. 87
    31
      oa-ui-app/pages/form/borrow/borrow.vue
  10. 65
    16
      oa-ui-app/pages/form/car/car.vue
  11. 112
    45
      oa-ui-app/pages/form/device/device.vue
  12. 5
    1
      oa-ui-app/pages/work/index.vue
  13. 二进制
      oa-ui-app/static/images/female.png
  14. 6
    2
      oa-ui-app/store/modules/user.js
  15. 19
    13
      oa-ui/src/api/llm/rag.js
  16. 257
    0
      oa-ui/src/api/llm/session.js
  17. 24
    0
      oa-ui/src/views/flowable/form/changeForm.vue
  18. 118
    86
      oa-ui/src/views/flowable/form/finance/borrowForm.vue
  19. 46
    16
      oa-ui/src/views/flowable/form/oa/carForm.vue
  20. 82
    33
      oa-ui/src/views/flowable/form/oa/deviceForm.vue
  21. 279
    106
      oa-ui/src/views/llm/chat/index.vue
  22. 3
    3
      oa-ui/src/views/llm/knowledge/index.vue
  23. 2
    2
      oa-ui/src/views/system/user/profile/index.vue
  24. 8
    3
      oa-ui/src/views/system/user/profile/userInfo.vue

+ 3
- 0
oa-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java 查看文件

@@ -64,6 +64,9 @@ public class SysProfileController extends BaseController
64 64
         currentUser.setNickName(user.getNickName());
65 65
         currentUser.setEmail(user.getEmail());
66 66
         currentUser.setPhonenumber(user.getPhonenumber());
67
+        currentUser.setHomePlace(user.getHomePlace());
68
+        currentUser.setContact(user.getContact());
69
+        currentUser.setTelephone(user.getTelephone());
67 70
         currentUser.setSex(user.getSex());
68 71
         if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser))
69 72
         {

+ 5
- 5
oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java 查看文件

@@ -80,10 +80,10 @@ public class McpServiceImpl implements IMcpService {
80 80
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
81 81
             throw new IllegalStateException("Milvus service URL 未配置");
82 82
         }
83
-//        milvusClient = new MilvusClientV2(
84
-//                ConnectConfig.builder()
85
-//                        .uri(milvusServiceUrl)
86
-//                        .build());
83
+        milvusClient = new MilvusClientV2(
84
+                ConnectConfig.builder()
85
+                        .uri(milvusServiceUrl)
86
+                        .build());
87 87
     }
88 88
     /**
89 89
      * 调用LLM+RAG(外部文件+知识库)生成回答
@@ -220,7 +220,7 @@ public class McpServiceImpl implements IMcpService {
220 220
      */
221 221
     public String generateAnswer(String prompt) throws IOException {
222 222
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
223
-                .model("Qwen2.5")
223
+                .model("Qwen")
224 224
                 .build();
225 225
 
226 226
         List<ChatMessage> messages = new ArrayList<>();

+ 1
- 1
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java 查看文件

@@ -60,7 +60,7 @@ public class McpController extends BaseController
60 60
                 .url("http://localhost:8080/mcp/sse")
61 61
                 .build();
62 62
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
63
-                .model("Qwen2.5")
63
+                .model("Qwen")
64 64
                 .defaultToolsAdd(clientProvider)
65 65
                 .build();
66 66
 

+ 60
- 29
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java 查看文件

@@ -17,11 +17,13 @@ import org.noear.solon.ai.mcp.client.McpClientProvider;
17 17
 import org.reactivestreams.Publisher;
18 18
 import org.springframework.beans.factory.annotation.Autowired;
19 19
 import org.springframework.beans.factory.annotation.Value;
20
+import org.springframework.http.MediaType;
20 21
 import org.springframework.web.bind.annotation.GetMapping;
21 22
 import org.springframework.web.bind.annotation.RequestMapping;
22 23
 import org.springframework.web.bind.annotation.RestController;
23 24
 import reactor.core.publisher.Flux;
24 25
 
26
+import javax.annotation.PostConstruct;
25 27
 import java.io.*;
26 28
 import java.util.ArrayList;
27 29
 import java.util.List;
@@ -45,20 +47,30 @@ public class SessionController extends BaseController
45 47
     @Value("${llmService.url}")
46 48
     private String llmServiceUrl;
47 49
 
48
-    /**
49
-     * 生成回答
50
-     */
51
-    @GetMapping("/answer")
52
-    public Flux<AssistantMessage> answer(String topicId, String question) throws IOException {
53
-        McpClientProvider clientProvider = McpClientProvider.builder()
50
+    // 复用 ChatModel 实例
51
+    private ChatModel chatModel;
52
+    private McpClientProvider clientProvider;
53
+
54
+    @PostConstruct
55
+    public void init() {
56
+        // 初始化 McpClientProvider
57
+        clientProvider = McpClientProvider.builder()
54 58
                 .channel(McpChannel.SSE)
55 59
                 .url("http://localhost:8080/mcp/sse")
56 60
                 .build();
57
-        ChatModel chatModel = ChatModel.of(llmServiceUrl)
58
-                .model("Qwen2.5")
61
+
62
+        // 初始化 ChatModel
63
+        chatModel = ChatModel.of(llmServiceUrl)
64
+                .model("Qwen")
59 65
                 .defaultToolsAdd(clientProvider)
60 66
                 .build();
67
+    }
61 68
 
69
+    /**
70
+     * 生成回答
71
+     */
72
+    @GetMapping(value = "/answer", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
73
+    public Flux<AssistantMessage> answer(String topicId, String question) throws IOException {
62 74
         List<ChatMessage> messages = new ArrayList<>();
63 75
         CmcChat cmcChat = new CmcChat();
64 76
         cmcChat.setTopicId(topicId);
@@ -96,32 +108,51 @@ public class SessionController extends BaseController
96 108
                 "7. 输出格式:使用[]包含sql文本,例如:[select 1 from dual]\n";
97 109
         messages.add(ChatMessage.ofUser(prompt));
98 110
         ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
99
-        ChatResponse response = chatModel.prompt(chatSession).call();
100
-        String resultContent = response.lastChoice().getMessage().getResultContent();
101
-
102
-        if (resultContent.contains("<tool_call>")) {
103
-            resultContent = resultContent.split("<tool_call>\n")[1];
104
-            String content = resultContent.replace("\n</tool_call>", "");
105
-            JSONObject jsonObject = JSONObject.parseObject(content);
106
-            String name = jsonObject.getString("name");
107
-            JSONObject arguments = jsonObject.getJSONObject("arguments");
108
-            resultContent = clientProvider.callToolAsText(name, arguments).getContent();
109
-            resultContent = "根据查询结果:\n" +
110
-                    resultContent + "回答问题:\n" +
111
-                    question + " \n";
112
-            return langChainMilvusService.generateAnswer(topicId, resultContent);
113
-        } else {
114
-            // 不需要调用工具,直接返回原始响应,避免重复调用大模型
115
-            // 创建一个包含原始响应内容的 AssistantMessage 并返回 Flux
116
-            AssistantMessage assistantMessage = ChatMessage.ofAssistant(resultContent);
117
-            return Flux.just(assistantMessage);
118
-        }
111
+
112
+        Publisher<ChatResponse> responsePublisher = chatModel.prompt(chatSession).stream();
113
+
114
+        // 收集完整的响应内容,用于判断是否需要工具调用
115
+        StringBuilder resultBuilder = new StringBuilder();
116
+
117
+        // 先创建一个处理器,用于处理流式响应
118
+        Flux<AssistantMessage> flux = Flux.from(responsePublisher)
119
+                .map(chatResponse -> chatResponse.lastChoice().getMessage())
120
+                .doOnNext(assistantMessage -> {
121
+                    String content = assistantMessage.getResultContent();
122
+                    resultBuilder.append(content);
123
+                });
124
+
125
+        // 检查是否需要工具调用(异步处理)
126
+        return flux.thenMany(Flux.defer(() -> {
127
+            String resultContent = resultBuilder.toString();
128
+
129
+            if (resultContent.contains("<tool_call>")) {
130
+                // 需要工具调用,解析工具调用内容
131
+                resultContent = resultContent.split("<tool_call>\n")[1];
132
+                String content = resultContent.replace("\n</tool_call>", "");
133
+                JSONObject jsonObject = JSONObject.parseObject(content);
134
+                String name = jsonObject.getString("name");
135
+                JSONObject arguments = jsonObject.getJSONObject("arguments");
136
+
137
+                // 调用工具
138
+                resultContent = clientProvider.callToolAsText(name, arguments).getContent();
139
+                resultContent = "根据查询结果:\n" +
140
+                        resultContent + "回答问题:\n" +
141
+                        question + " \n";
142
+
143
+                // 再次调用LLM生成最终回答
144
+                return langChainMilvusService.generateAnswer(topicId, resultContent);
145
+            } else {
146
+                // 不需要工具调用,已经通过上面的flux返回了流式内容
147
+                return Flux.just(ChatMessage.ofAssistant(resultContent));
148
+            }
149
+        }));
119 150
     }
120 151
 
121 152
     /**
122 153
      * 调用LLM+RAG(外部文件)生成回答
123 154
      */
124
-    @GetMapping("/answerWithDocument")
155
+    @GetMapping(value = "/answerWithDocument", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
125 156
     public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws Exception
126 157
     {
127 158
         return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question);

+ 15
- 9
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java 查看文件

@@ -74,16 +74,27 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
74 74
     private String milvusServiceUrl;
75 75
 
76 76
     private MilvusClientV2 milvusClient;
77
+    
78
+    // 复用 ChatModel 实例
79
+    private ChatModel chatModel;
77 80
 
78 81
     @PostConstruct
79 82
     public void initMilvusClient() {
80 83
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
81 84
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
82 85
         }
83
-//        milvusClient = new MilvusClientV2(
84
-//                ConnectConfig.builder()
85
-//                        .uri(milvusServiceUrl)
86
-//                        .build());
86
+        milvusClient = new MilvusClientV2(
87
+                ConnectConfig.builder()
88
+                        .uri(milvusServiceUrl)
89
+                        .build());
90
+    }
91
+    
92
+    @PostConstruct
93
+    public void initChatModel() {
94
+        // 初始化 ChatModel
95
+        chatModel = ChatModel.of(llmServiceUrl)
96
+                .model("Qwen")
97
+                .build();
87 98
     }
88 99
 
89 100
     /**
@@ -242,11 +253,6 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
242 253
      */
243 254
     @Override
244 255
     public Flux<AssistantMessage> generateAnswer(String topicId, String prompt) {
245
-        ChatModel chatModel = ChatModel.of(llmServiceUrl)
246
-
247
-                .model("Qwen2.5")
248
-                .build();
249
-
250 256
         List<ChatMessage> messages = new ArrayList<>();
251 257
         if (topicId != null) {
252 258
             CmcChat cmcChat = new CmcChat();

+ 4
- 4
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java 查看文件

@@ -34,10 +34,10 @@ public class MilvusServiceImpl implements IMilvusService {
34 34
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
35 35
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
36 36
         }
37
-//        milvusClient = new MilvusClientV2(
38
-//                ConnectConfig.builder()
39
-//                        .uri(milvusServiceUrl)
40
-//                        .build());
37
+        milvusClient = new MilvusClientV2(
38
+                ConnectConfig.builder()
39
+                        .uri(milvusServiceUrl)
40
+                        .build());
41 41
     }
42 42
 
43 43
     /**

+ 5
- 5
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java 查看文件

@@ -90,10 +90,10 @@ public class CmcAgentServiceImpl implements ICmcAgentService
90 90
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
91 91
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
92 92
         }
93
-//        milvusClient = new MilvusClientV2(
94
-//                ConnectConfig.builder()
95
-//                        .uri(milvusServiceUrl)
96
-//                        .build());
93
+        milvusClient = new MilvusClientV2(
94
+                ConnectConfig.builder()
95
+                        .uri(milvusServiceUrl)
96
+                        .build());
97 97
     }
98 98
 
99 99
     /**
@@ -644,7 +644,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
644 644
      */
645 645
     public String generateAnswer(String prompt) throws IOException {
646 646
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
647
-                .model("Qwen2.5")
647
+                .model("Qwen")
648 648
                 .build();
649 649
 
650 650
         List<ChatMessage> messages = new ArrayList<>();

+ 1
- 1
oa-ui-app/api/qywx/index.js 查看文件

@@ -3,7 +3,7 @@ import request from '@/utils/request'
3 3
 // 发送企业微信消息
4 4
 export function sendQyMessage(data) {
5 5
   return request({
6
-    url: '/qywx/message/send',
6
+    url: '/qyweixin/sendMessage',
7 7
     method: 'post',
8 8
     data: data
9 9
   })

+ 87
- 31
oa-ui-app/pages/form/borrow/borrow.vue 查看文件

@@ -154,6 +154,8 @@ import { listProject, getProject } from "@/api/oa/project/project";
154 154
 import { listSite, delSite } from "@/api/oa/budget/budgetSite.js";
155 155
 import { listBorrow, getBorrow, delBorrow, addBorrow, updateBorrow } from "@/api/oa/borrow/borrow";
156 156
 import { getUserByPost, getUsersDeptLeader, getUsersDeptLeaderByDept, getUsersViceDeptLeaderByDept, getUsersManageLeaderByDept } from '@/api/system/post.js'
157
+import { sendQyMessage } from "@/api/qywx/index"
158
+import { listUser, getUser } from "@/api/system/user";
157 159
 import BorrowDetail from './borrowDetail.vue';
158 160
 import FlowNote from '@/pages/components/flowNote.vue';
159 161
 import ReturnPopup from '@/pages/components/returnPopup.vue';
@@ -179,6 +181,7 @@ export default {
179 181
     this.deptId = this.$store.getters.deptId;
180 182
     this.applierUserName = this.startUserName;
181 183
     this.initForm();
184
+    this.getUserList();
182 185
     this.initRules();
183 186
     uni.setNavigationBarTitle({
184 187
       title: '借款审批'
@@ -194,6 +197,7 @@ export default {
194 197
   },
195 198
   data() {
196 199
     return {
200
+      userList: [],
197 201
       applierUserName: '',
198 202
       form: {
199 203
         submitTime: '',
@@ -303,8 +307,8 @@ export default {
303 307
       getBorrow(this.taskForm.formId).then(async res => {
304 308
         if (res.data) {
305 309
           this.formTotal = 1;
306
-          this.form = res.data;
307
-          if (this.form.applyDept == 104) //董事长分管的综合事务部,跳过分管审批
310
+          this.form = res.data;
311
+          if (this.form.applyDept == 104) //董事长分管的综合事务部,跳过分管审批
308 312
             this.need = false;
309 313
           if (!this.applierUserName) {
310 314
             this.applierUserName = this.form.applierUser.nickName;
@@ -328,6 +332,17 @@ export default {
328 332
       })
329 333
 
330 334
     },
335
+    // 获取用户列表
336
+    getUserList() {
337
+      listUser({
338
+        pageNum: 1,
339
+        pageSize: 9999
340
+      }).then(res => {
341
+        if (res.code == 200) {
342
+          this.userList = res.rows
343
+        }
344
+      })
345
+    },
331 346
     async getMaxExpense() {
332 347
       let budgetData = await listBudget({ projectId: this.form.projectId })
333 348
       if (budgetData.total == 1) {
@@ -469,51 +484,62 @@ export default {
469 484
             // 处理不同的流程节点
470 485
             if (this.taskName == '借款申请') {
471 486
               // 处理借款申请节点
472
-              await this.borrowApprovalFun();
487
+              let userId = await this.borrowApprovalFun();
488
+              let userIds = [];
489
+              if (userId) {
490
+                userIds.push(userId);
491
+              }
492
+              await this.sendQyMessage(userIds);
473 493
             } else if (this.taskName == '部门审核') {
474 494
               this.taskForm.variables.need = this.need;
495
+              let userIds = [];
475 496
               if (this.need) {
476
-				  // 得到分管领导根据部门
477
-				  const res = await getUsersManageLeaderByDept({ deptId: this.form.applyDept });
478
-				  let userIds = [];
479
-				  if (res.data) {
480
-					res.data.forEach(item => {
481
-					  userIds.push(item.userId)
482
-					})
483
-				  }
484
-				  this.taskForm.variables.approvalList = userIds;
485
-				  this.handleComplete(this.taskForm);
497
+			  // 得到分管领导根据部门
498
+			  const res = await getUsersManageLeaderByDept({ deptId: this.form.applyDept });
499
+			  if (res.data) {
500
+				res.data.forEach(item => {
501
+				  userIds.push(item.userId)
502
+				})
486 503
 			  }
487
-			  else {
488
-				  const res = await getUsersManageLeaderByDept({ deptId: this.form.applyDept });
489
-				  let userIds = [];
490
-				  if (res.data) {
491
-					res.data.forEach(item => {
492
-					  userIds.push(item.userId)
493
-					})
494
-				  }
495
-				  this.taskForm.variables.approvalList = userIds;
496
-				  const response = await getUserByPost({ postName: '总经理' });
497
-				  this.taskForm.variables.approval = response.data[0].userId;
498
-				  this.handleComplete(this.taskForm);
504
+			  this.taskForm.variables.approvalList = userIds;
505
+			  this.handleComplete(this.taskForm);
506
+		  }
507
+		  else {
508
+			  const res = await getUsersManageLeaderByDept({ deptId: this.form.applyDept });
509
+			  if (res.data) {
510
+				res.data.forEach(item => {
511
+				  userIds.push(item.userId)
512
+				})
499 513
 			  }
514
+			  this.taskForm.variables.approvalList = userIds;
515
+			  const response = await getUserByPost({ postName: '总经理' });
516
+			  this.taskForm.variables.approval = response.data[0].userId;
517
+			  userIds = [response.data[0].userId];
518
+			  this.handleComplete(this.taskForm);
519
+		  }
520
+              await this.sendQyMessage(userIds);
500 521
             } else if (this.taskName == '分管审核') {
501 522
               // 得到总经理
502 523
               const res = await getUserByPost({ postName: '总经理' });
503 524
               this.taskForm.variables.approval = res.data[0].userId;
504 525
               this.handleComplete(this.taskForm);
526
+              await this.sendQyMessage([res.data[0].userId]);
505 527
             } else if (this.taskName == '总经理审核') {
506 528
               this.exceed = false;
507 529
               this.taskForm.variables.exceed = this.exceed;
530
+              let userIds = [];
508 531
               if (!this.exceed) {
509
-                this.submitFD(); // 提交给财务部门
532
+                userIds = await this.submitFD(); // 提交给财务部门
510 533
               } else {
511 534
                 const res = await getUserByPost({ postName: '董事长' });
512 535
                 this.taskForm.variables.approval = res.data[0].userId;
513 536
                 this.handleComplete(this.taskForm);
537
+                userIds = [res.data[0].userId];
514 538
               }
539
+              await this.sendQyMessage(userIds);
515 540
             } else if (this.taskName == '董事长批准' || this.taskName == '党工团审核') {
516
-              this.submitFD(); // 提交给财务部门
541
+              let userIds = await this.submitFD(); // 提交给财务部门
542
+              await this.sendQyMessage(userIds);
517 543
             } else if (this.taskName == '财务处理') {
518 544
               uni.showModal({
519 545
                 title: '提示',
@@ -563,22 +589,25 @@ export default {
563 589
         this.form.managerAmount = this.form.applyAmount;
564 590
         await updateBorrow(this.form);
565 591
         const res = await getUserByPost({ postName: '总经理' });
592
+        userId = res.data[0].userId;
566 593
         this.taskForm.variables.dept = this.deptId;
567
-        this.taskForm.variables.approval = res.data[0].userId;
594
+        this.taskForm.variables.approval = userId;
568 595
         this.handleComplete(this.taskForm);
569 596
       } else if (this.deptId == 101) { //如果是董事会申请,走董事长批准
570 597
         const res = await getUserByPost({ postName: '董事长' });
598
+        userId = res.data[0].userId;
571 599
         this.taskForm.variables.dept = this.deptId;
572
-        this.taskForm.variables.approval = res.data[0].userId;
600
+        this.taskForm.variables.approval = userId;
573 601
         this.taskForm.variables.exceed = true;
574 602
         this.handleComplete(this.taskForm);
575 603
       } else { //普通员工申请项目借款
576 604
         const res = await getUsersDeptLeader({ userId: this.$store.getters.userId });
577
-        let userId = res.data.userId;
605
+        userId = res.data.userId;
578 606
         this.taskForm.variables.approval = userId;
579 607
         this.taskForm.variables.dept = this.deptId;
580 608
         this.handleComplete(this.taskForm);
581 609
       }
610
+      return userId;
582 611
     },
583 612
     // 提交给财务部审核
584 613
     async submitFD() {
@@ -589,6 +618,7 @@ export default {
589 618
       approvalList.push(res1.data.userId);
590 619
       this.taskForm.variables.approvalList = approvalList;
591 620
       this.handleComplete(this.taskForm);
621
+      return approvalList;
592 622
     },
593 623
     commentByRole() {
594 624
       if (this.taskName == '部门审核') {
@@ -613,7 +643,33 @@ export default {
613 643
     uploadSuccess(fileName) {
614 644
       this.form.borrowDocument = fileName
615 645
       updateBorrow(this.form)
616
-    }
646
+    },
647
+    // 发送企业微信消息
648
+    async sendQyMessage(userIds) {
649
+      if (userIds && userIds.length > 0) {
650
+        let formData = new FormData();
651
+        let message = "您有一条新的借款申请:  \n>" + 
652
+        "申请人:<font color='info'>" + this.getUserName(this.form.applier) + "</font> \n>" + 
653
+        "借款金额:<font color='warning'>" + this.form.applyAmount + "</font> 元  \n>" + 
654
+        "借款说明:" + (this.form.applyReason ? this.form.applyReason : this.form.remark) + "  \n>";
655
+        formData.append('message', message);
656
+        let userString = [];
657
+        for (let u of userIds) {
658
+          let { data } = await getUser(u);
659
+          if (data && data.pinyin) {
660
+            userString.push(data.pinyin);
661
+          }
662
+        }
663
+        formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
664
+        await sendQyMessage(formData);
665
+      }
666
+    },
667
+    // 获取用户名称
668
+    getUserName(userId) {
669
+      if (!userId || !this.userList) return '';
670
+      let user = this.userList.find(u => u.userId == userId);
671
+      return user ? user.nickName : '';
672
+    },
617 673
   },
618 674
 }
619 675
 </script>

+ 65
- 16
oa-ui-app/pages/form/car/car.vue 查看文件

@@ -184,7 +184,8 @@ import { listCarApproval, getCarApproval, updateCarApproval, addCarApproval, mod
184 184
 import { listProject, getProject } from "@/api/oa/project/project";
185 185
 import { getUserByRole } from "@/api/system/role";
186 186
 import { getUsersManageLeader, getUserByPost, getUsersDeptLeader } from '@/api/system/post.js'
187
-import { getUser } from '@/api/system/user';
187
+import { listUser, getUser } from "@/api/system/user";
188
+import { sendQyMessage } from "@/api/qywx/index";
188 189
 import FlowNote from '@/pages/components/flowNote.vue';
189 190
 import ProjectPicker from '@/pages/components/ProjectPicker.vue';
190 191
 import ProjectInfo from '@/pages/components/ProjectInfo.vue';
@@ -259,6 +260,7 @@ export default {
259 260
         value: '4',
260 261
         disable: false
261 262
       }],
263
+      userList: [],
262 264
       dept: 0,
263 265
       deptUser: '',
264 266
       managerUser: '',
@@ -391,6 +393,7 @@ export default {
391 393
   },
392 394
   created() {
393 395
     this.applierUserName = this.startUserName;
396
+    this.getUserList();
394 397
     this.initForm();
395 398
     this.getCarList();
396 399
     this.getDriverList();
@@ -444,6 +447,17 @@ export default {
444 447
         }
445 448
       })
446 449
     },
450
+    // 获取用户列表
451
+    getUserList() {
452
+      listUser({
453
+        pageNum: 1,
454
+        pageSize: 9999
455
+      }).then(res => {
456
+        if (res.code == 200) {
457
+          this.userList = res.rows
458
+        }
459
+      })
460
+    },
447 461
     initAuditor() {
448 462
 
449 463
     },
@@ -615,42 +629,48 @@ export default {
615 629
           });
616 630
         };
617 631
 
618
-        const setApprovalAndComplete = (approval) => {
632
+        const setApprovalAndComplete = async (approval) => {
619 633
           this.$set(this.taskForm.variables, "approval", approval);
620 634
           handleComplete();
635
+          await this.sendQyMessage([approval]);
621 636
         };
622 637
 
623
-        const setApprovalListAndComplete = (approvalList) => {
638
+        const setApprovalListAndComplete = async (approvalList) => {
639
+          let userIds = [];
640
+          if (Array.isArray(approvalList)) {
641
+            userIds = approvalList.map(item => typeof item === 'object' ? item.userId : item);
642
+          }
624 643
           this.$set(this.taskForm.variables, "approvalList", approvalList);
625 644
           handleComplete();
645
+          await this.sendQyMessage(userIds);
626 646
         };
627 647
 
628 648
         if (this.taskName == '用车申请') {
629 649
           this.$set(this.taskForm.variables, "dept", this.dept);
630 650
 
631 651
           if (this.dept == 101) {
632
-            getUserByPost({ postName: '董事长' }).then(result => {
633
-              setApprovalAndComplete(result.data[0].userId);
652
+            getUserByPost({ postName: '董事长' }).then(async result => {
653
+              await setApprovalAndComplete(result.data[0].userId);
634 654
             }).catch(error => {
635 655
               reject(error);
636 656
             });
637 657
           } else if (this.dept == 102) {
638
-            getUserByPost({ postName: '总经理' }).then(result => {
639
-              setApprovalAndComplete(result.data[0].userId);
658
+            getUserByPost({ postName: '总经理' }).then(async result => {
659
+              await setApprovalAndComplete(result.data[0].userId);
640 660
             }).catch(error => {
641 661
               reject(error);
642 662
             });
643 663
           } else if (this.dept == 0) {
644 664
             const postName = this.getChooseType();
645
-            getUserByPost({ postName }).then(result => {
646
-              setApprovalAndComplete(result.data[0].userId);
665
+            getUserByPost({ postName }).then(async result => {
666
+              await setApprovalAndComplete(result.data[0].userId);
647 667
             }).catch(error => {
648 668
               reject(error);
649 669
             });
650 670
           } else {
651
-            getUsersDeptLeader({ userId: this.$store.getters.userId }).then(res => {
671
+            getUsersDeptLeader({ userId: this.$store.getters.userId }).then(async res => {
652 672
               if (res.data) {
653
-                setApprovalAndComplete(res.data.userId);
673
+                await setApprovalAndComplete(res.data.userId);
654 674
               } else {
655 675
                 reject(new Error('未找到部门领导'));
656 676
               }
@@ -659,15 +679,15 @@ export default {
659 679
             });
660 680
           }
661 681
         } else if (this.taskName == '部门审核') {
662
-          getUsersManageLeader({ userId: this.$store.getters.userId }).then(res => {
663
-            const userId = res.data.map(user => user.userId);
664
-            setApprovalListAndComplete(userId);
682
+          getUsersManageLeader({ userId: this.$store.getters.userId }).then(async res => {
683
+            const userIds = res.data.map(user => user.userId);
684
+            await setApprovalListAndComplete(userIds);
665 685
           }).catch(error => {
666 686
             reject(error);
667 687
           });
668 688
         } else if (['分管审核', '党工团审核', '总经理审核', '董事长审核'].includes(this.taskName)) {
669
-          getUserByRole({ roleId: 5 }).then(result => {
670
-            setApprovalListAndComplete(result.data);
689
+          getUserByRole({ roleId: 5 }).then(async result => {
690
+            await setApprovalListAndComplete(result.data);
671 691
           }).catch(error => {
672 692
             reject(error);
673 693
           });
@@ -724,6 +744,35 @@ export default {
724 744
       // 只允许输入数字
725 745
       this.form.kilometers = val.replace(/[^\d]/g, '');
726 746
     },
747
+    // 发送企业微信消息
748
+    async sendQyMessage(userIds) {
749
+      if (userIds && userIds.length > 0) {
750
+        let formData = new FormData();
751
+        let message = "您有一条新的用车申请:  \n>" + 
752
+        "申请人:<font color='info'>" + this.getUserName(this.form.applier) + "</font> \n>" + 
753
+        "用车事由:" + this.form.applyReason + " \n>" + 
754
+        "开始日期:" + this.form.beginDate + " \n>" + 
755
+        "结束日期:" + this.form.endDate + " \n>" + 
756
+        "乘车人数:" + this.form.passengers + "人  \n>";
757
+        formData.append('message', message);
758
+        let userString = [];
759
+        for (let u of userIds) {
760
+          let { data } = await getUser(u);
761
+          if (data && data.pinyin) {
762
+            userString.push(data.pinyin);
763
+          }
764
+        }
765
+        formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
766
+        await sendQyMessage(formData);
767
+      }
768
+    },
769
+    // 获取用户名称
770
+    getUserName(userId) {
771
+      if (!userId || !this.userList) return '';
772
+      let user = this.userList.find(u => u.userId == userId);
773
+      return user ? user.nickName : '';
774
+    },
775
+
727 776
   },
728 777
 }
729 778
 </script>

+ 112
- 45
oa-ui-app/pages/form/device/device.vue 查看文件

@@ -268,6 +268,8 @@ import { getProject } from "@/api/oa/project/project";
268 268
 import { getDevice } from "@/api/oa/device/device";
269 269
 import { getUserByRole } from "@/api/system/role";
270 270
 import { getUsersManageLeader } from '@/api/system/post.js'
271
+import { sendQyMessage } from "@/api/qywx/index";
272
+import { listUser, getUser } from "@/api/system/user";
271 273
 import FlowNote from '@/pages/components/flowNote.vue';
272 274
 import ProjectPicker from '@/pages/components/ProjectPicker.vue';
273 275
 import ProjectInfo from '@/pages/components/ProjectInfo.vue';
@@ -292,10 +294,12 @@ export default {
292 294
   created() {
293 295
     this.applierUserName = this.startUserName;
294 296
     this.initForm();
297
+    this.getUserList();
295 298
   },
296 299
   data() {
297 300
     return {
298
-      form: {
301
+      form: {        
302
+        userList: [],
299 303
         user: {
300 304
           nickName: ''
301 305
         },
@@ -526,6 +530,17 @@ export default {
526 530
         });
527 531
       }
528 532
     },
533
+    // 获取用户列表
534
+    getUserList() {
535
+      listUser({
536
+        pageNum: 1,
537
+        pageSize: 9999
538
+      }).then(res => {
539
+        if (res.code == 200) {
540
+          this.userList = res.rows
541
+        }
542
+      })
543
+    },
529 544
     handleConfirm(project) {
530 545
       this.selectedProject = project;
531 546
       this.projectObj = project;
@@ -594,9 +609,9 @@ export default {
594 609
         this.selectDevice = [];
595 610
       }
596 611
     },
597
-    submit() {
598
-      this.$refs.form.validate().then(() => {
599
-        this.$modal.confirm('是否提交设备申请?').then(() => {
612
+    async submit() {
613
+      this.$refs.form.validate().then(async () => {
614
+        this.$modal.confirm('是否提交设备申请?').then(async () => {
600 615
           let submitData = {
601 616
             ...this.form,
602 617
             ...(this.form.formId ? {} : { formId: this.taskForm.formId }),
@@ -604,34 +619,36 @@ export default {
604 619
           };
605 620
           let jsonForm = JSON.stringify(submitData);
606 621
           if (!this.form.deviceApplyId) {
607
-            submitDeviceApproval(jsonForm).then(res => {
622
+            submitDeviceApproval(jsonForm).then(async res => {
608 623
               // 获取下一个流程节点
609
-              this.getNextFlowNodeApproval().then(() => {
610
-                uni.showToast({
611
-                  title: '提交成功',
612
-                  icon: 'success'
613
-                });
614
-                setTimeout(() => {
615
-                  uni.switchTab({
616
-                    url: '/pages/message/index'
617
-                  })
618
-                }, 500);
619
-              })
624
+              await this.getNextFlowNodeApproval();
625
+              // 发送企业微信消息
626
+              await this.sendQyMessage();
627
+              uni.showToast({
628
+                title: '提交成功',
629
+                icon: 'success'
630
+              });
631
+              setTimeout(() => {
632
+                uni.switchTab({
633
+                  url: '/pages/message/index'
634
+                })
635
+              }, 500);
620 636
             });
621 637
           } else {
622
-            modifyDeviceApproval(jsonForm).then(res => {
638
+            modifyDeviceApproval(jsonForm).then(async res => {
623 639
               // 获取下一个流程节点
624
-              this.getNextFlowNodeApproval().then(() => {
625
-                uni.showToast({
626
-                  title: '提交成功',
627
-                  icon: 'success'
628
-                });
629
-                setTimeout(() => {
630
-                  uni.switchTab({
631
-                    url: '/pages/message/index'
632
-                  })
633
-                }, 500);
634
-              })
640
+              await this.getNextFlowNodeApproval();
641
+              // 发送企业微信消息
642
+              await this.sendQyMessage();
643
+              uni.showToast({
644
+                title: '提交成功',
645
+                icon: 'success'
646
+              });
647
+              setTimeout(() => {
648
+                uni.switchTab({
649
+                  url: '/pages/message/index'
650
+                })
651
+              }, 500);
635 652
             })
636 653
           }
637 654
         })
@@ -672,8 +689,8 @@ export default {
672 689
         });
673 690
       }
674 691
     },
675
-    completeApply() {
676
-      this.$refs.form.validate().then(() => {
692
+    async completeApply() {
693
+      this.$refs.form.validate().then(async () => {
677 694
         // 在归还确认节点时,检查所有设备是否都已归还
678 695
         if (this.taskName === '归还确认') {
679 696
           const allDevices = this.modifyDeviceList.map(item => item.deviceId);
@@ -692,7 +709,7 @@ export default {
692 709
         uni.showModal({
693 710
           title: '提示',
694 711
           content: '确定提交审批吗?',
695
-          success: (res) => {
712
+          success: async (res) => {
696 713
             if (res.confirm) {
697 714
               let submitData = {
698 715
                 ...this.form,
@@ -704,21 +721,19 @@ export default {
704 721
                 submitData.modifyDevices = this.modifyDeviceList.map(item => item.deviceId);
705 722
               }
706 723
               let jsonForm = JSON.stringify(submitData);
707
-              modifyDeviceApproval(jsonForm).then(res => {
724
+              modifyDeviceApproval(jsonForm).then(async res => {
708 725
                 // 获取下一个流程节点
709
-                getNextFlowNode({ taskId: this.taskForm.taskId }).then(res => {
710
-                  this.getNextFlowNodeApproval().then(() => {
711
-                    uni.showToast({
712
-                      title: '提交成功',
713
-                      icon: 'success'
714
-                    });
715
-                    setTimeout(() => {
716
-                      uni.switchTab({
717
-                        url: '/pages/message/index'
718
-                      });
719
-                    }, 500);
720
-                  });
726
+                await getNextFlowNode({ taskId: this.taskForm.taskId });
727
+                await this.getNextFlowNodeApproval();
728
+                uni.showToast({
729
+                  title: '提交成功',
730
+                  icon: 'success'
721 731
                 });
732
+                setTimeout(() => {
733
+                  uni.switchTab({
734
+                    url: '/pages/message/index'
735
+                  });
736
+                }, 500);
722 737
               });
723 738
             }
724 739
           }
@@ -789,7 +804,59 @@ export default {
789 804
       this.repairDevicesList = this.modifyDeviceList.filter(device =>
790 805
         this.form.repairDevices.includes(device.deviceId)
791 806
       );
792
-    }
807
+    },
808
+    // 发送企业微信消息
809
+    async sendQyMessage() {
810
+      try {
811
+        let userIds = [];
812
+        let userString = [];
813
+        let message = '';
814
+        let formData = new FormData();
815
+        
816
+        // 获取下一个审批人
817
+        if (this.taskName == '设备申请') {
818
+          let result = await getUserByRole({ roleId: 4 });
819
+          if (result.data && result.data.length > 0) {
820
+            userIds.push(result.data[0]);
821
+          }
822
+        } else if (this.taskName == '安排设备') {
823
+          let res = await getUsersManageLeader({ userId: this.form.applier });
824
+          if (res.data) {
825
+            res.data.forEach(user => {
826
+              userIds.push(user.userId);
827
+            });
828
+          }
829
+        }
830
+        
831
+        for (let u of userIds) {
832
+          let { data } = await getUser(u);
833
+          if (data) {
834
+            userString.push(data.pinyin);
835
+          }
836
+        }
837
+        
838
+        message = "您有一条新的设备申请:  \n>" + 
839
+        "申请人:<font color='info'>" + this.getUserName(this.form.applier) + "</font>  \n>" + 
840
+        "开始日期:" + this.form.beginDate + " \n>" + 
841
+        "结束日期:" + this.form.endDate + " \n>" +  
842
+        "申请事由:" + this.form.applyReason + "  \n>";
843
+        
844
+        formData.append('message', message);
845
+        formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
846
+        
847
+        if (userIds.length > 0) {
848
+          await sendQyMessage(formData);
849
+        }
850
+      } catch (error) {
851
+        console.error('发送企业微信消息失败:', error);
852
+      }
853
+    },
854
+    // 获取用户名称
855
+    getUserName(userId) {
856
+      if (!userId || !this.userList) return '';
857
+      let user = this.userList.find(u => u.userId == userId);
858
+      return user ? user.nickName : '';
859
+    },
793 860
   }
794 861
 }
795 862
 </script>

+ 5
- 1
oa-ui-app/pages/work/index.vue 查看文件

@@ -110,6 +110,7 @@ export default {
110 110
       queryProcessParams: {
111 111
         pageNum: 1,
112 112
         pageSize: 9999,
113
+        parentDeploymentId: null,
113 114
         name: null,
114 115
         category: null,
115 116
         key: null,
@@ -217,7 +218,10 @@ export default {
217 218
         let list = []
218 219
         for (let i of this.definitionList) {
219 220
           if (this.flowList.includes(i.name)) {
220
-            list.push(i)
221
+			if (i.name != "借款审批")
222
+				list.push(i);
223
+			if (i.name == "借款审批" && i.version == 2)
224
+				list.push(i);
221 225
           }
222 226
         }
223 227
         this.sendFlowList = list;

二进制
oa-ui-app/static/images/female.png 查看文件


+ 6
- 2
oa-ui-app/store/modules/user.js 查看文件

@@ -75,8 +75,12 @@ const user = {
75 75
     GetInfo({ commit, state }) {
76 76
       return new Promise((resolve, reject) => {
77 77
         getInfo().then(res => {
78
-          const user = res.user
79
-          const avatar = (user == null || user.avatar == "" || user.avatar == null) ? require("@/static/images/user.png") : baseUrl + user.avatar
78
+          const user = res.user		  
79
+          let avatar = require("@/static/images/user.png")
80
+          if (user.avatar == "" || user.avatar == null)
81
+			avatar = user.sex == '0' ? avatar : require("@/static/images/female.png")
82
+          else
83
+            avatar = baseUrl + user.avatar;
80 84
           const username = (user == null || user.nickName == "" || user.nickName == null) ? "" : user.nickName
81 85
           const userId = (user == null || user.userId == "" || user.userId == null) ? "" : user.userId
82 86
           if (res.roles && res.roles.length > 0) {

+ 19
- 13
oa-ui/src/api/llm/rag.js 查看文件

@@ -96,6 +96,9 @@ export function getAnswerStream(question, collectionName, onMessage, onError, on
96 96
                 if (jsonData.resultContent) {
97 97
                   console.log('=== 准备发送剩余resultContent ===', jsonData.resultContent)
98 98
                   onMessage(jsonData.resultContent)
99
+                } else if (jsonData.choices && jsonData.choices[0] && jsonData.choices[0].delta && jsonData.choices[0].delta.content) {
100
+                  console.log('=== 准备发送剩余OpenAI格式内容 ===', jsonData.choices[0].delta.content)
101
+                  onMessage(jsonData.choices[0].delta.content)
99 102
                 } else if (typeof jsonData === 'string') {
100 103
                   console.log('=== 准备发送剩余字符串 ===', jsonData)
101 104
                   onMessage(jsonData)
@@ -154,19 +157,22 @@ export function getAnswerStream(question, collectionName, onMessage, onError, on
154 157
           }
155 158
 
156 159
           // 处理解析成功的数据
157
-          if (jsonData) {
158
-            console.log('=== 解析成功的数据 ===', jsonData)
159
-
160
-            if (jsonData.resultContent) {
161
-              console.log('=== 准备发送resultContent ===', jsonData.resultContent)
162
-              onMessage(jsonData.resultContent)
163
-            } else if (typeof jsonData === 'string') {
164
-              console.log('=== 准备发送字符串 ===', jsonData)
165
-              onMessage(jsonData)
166
-            } else {
167
-              console.log('=== 数据格式不匹配,跳过content字段 ===', jsonData)
168
-            }
169
-          }
160
+              if (jsonData) {
161
+                console.log('=== 解析成功的数据 ===', jsonData)
162
+
163
+                if (jsonData.resultContent) {
164
+                  console.log('=== 准备发送resultContent ===', jsonData.resultContent)
165
+                  onMessage(jsonData.resultContent)
166
+                } else if (jsonData.choices && jsonData.choices[0] && jsonData.choices[0].delta && jsonData.choices[0].delta.content) {
167
+                  console.log('=== 准备发送OpenAI格式内容 ===', jsonData.choices[0].delta.content)
168
+                  onMessage(jsonData.choices[0].delta.content)
169
+                } else if (typeof jsonData === 'string') {
170
+                  console.log('=== 准备发送字符串 ===', jsonData)
171
+                  onMessage(jsonData)
172
+                } else {
173
+                  console.log('=== 数据格式不匹配,跳过content字段 ===', jsonData)
174
+                }
175
+              }
170 176
         })
171 177
 
172 178
         return readStream()

+ 257
- 0
oa-ui/src/api/llm/session.js 查看文件

@@ -5,6 +5,7 @@
5 5
  * @LastEditTime: 2025-07-22 15:42:04
6 6
  */
7 7
 import request from '@/utils/request'
8
+import { getToken } from '@/utils/auth'
8 9
 
9 10
 // 查询cmc聊天记录详细
10 11
 export function getAnswer(question) {
@@ -22,4 +23,260 @@ export function getAnswerWithDocument(question) {
22 23
     method: 'get',
23 24
     params: question
24 25
   })
26
+}
27
+
28
+// 流式回答API - 使用fetch API处理流式响应
29
+export function getAnswerStream(params, onMessage, onError, onComplete) {
30
+  const baseURL = process.env.VUE_APP_BASE_API
31
+  const url = `${baseURL}/llm/session/answer?topicId=${params.topicId}&question=${encodeURIComponent(params.question)}`
32
+
33
+  const controller = new AbortController()
34
+
35
+  fetch(url, {
36
+    method: 'GET',
37
+    headers: {
38
+      'Authorization': 'Bearer ' + getToken(),
39
+      'Accept': 'application/json, text/event-stream',
40
+      'Cache-Control': 'no-cache'
41
+    },
42
+    signal: controller.signal
43
+  }).then(response => {
44
+    if (!response.ok) {
45
+      throw new Error(`HTTP error! status: ${response.status}`)
46
+    }
47
+
48
+    const reader = response.body.getReader()
49
+    const decoder = new TextDecoder()
50
+    let buffer = ''
51
+
52
+    function readStream() {
53
+      return reader.read().then(({ done, value }) => {
54
+        if (done) {
55
+          if (buffer.trim()) {
56
+            const lines = buffer.split(/\r?\n/)
57
+            lines.forEach(line => {
58
+              line = line.trim()
59
+              if (!line || line.startsWith(':')) return
60
+
61
+              let jsonData = null
62
+              if (line.startsWith('data: ')) {
63
+                try {
64
+                  jsonData = JSON.parse(line.slice(6))
65
+                } catch (error) {
66
+                  console.error('解析剩余SSE数据失败:', error, line)
67
+                }
68
+              } else if (line.startsWith('data:')) {
69
+                try {
70
+                  jsonData = JSON.parse(line.slice(5))
71
+                } catch (error) {
72
+                  console.error('解析剩余SSE数据失败(无空格):', error, line)
73
+                }
74
+              } else {
75
+                try {
76
+                  jsonData = JSON.parse(line)
77
+                } catch (error) {
78
+                  console.error('解析剩余JSON数据失败:', error, line)
79
+                }
80
+              }
81
+
82
+              if (jsonData) {
83
+                if (jsonData.resultContent) {
84
+                  onMessage(jsonData.resultContent)
85
+                } else if (jsonData.choices && jsonData.choices[0] && jsonData.choices[0].delta && jsonData.choices[0].delta.content) {
86
+                  onMessage(jsonData.choices[0].delta.content)
87
+                } else if (typeof jsonData === 'string') {
88
+                  onMessage(jsonData)
89
+                }
90
+              }
91
+            })
92
+          }
93
+
94
+          onComplete()
95
+          return
96
+        }
97
+
98
+        const chunk = decoder.decode(value, { stream: true })
99
+        buffer += chunk
100
+
101
+        const lines = buffer.split(/\r?\n/)
102
+        buffer = lines.pop() || ''
103
+
104
+        lines.forEach(line => {
105
+          line = line.trim()
106
+          if (!line || line.startsWith(':')) return
107
+
108
+          let jsonData = null
109
+          if (line.startsWith('data: ')) {
110
+            try {
111
+              jsonData = JSON.parse(line.slice(6))
112
+            } catch (error) {
113
+              console.error('解析SSE数据失败:', error, line)
114
+            }
115
+          } else if (line.startsWith('data:')) {
116
+            try {
117
+              jsonData = JSON.parse(line.slice(5))
118
+            } catch (error) {
119
+              console.error('解析SSE数据失败(无空格):', error, line)
120
+            }
121
+          } else {
122
+            try {
123
+              jsonData = JSON.parse(line)
124
+            } catch (error) {
125
+              console.error('解析JSON数据失败:', error, line)
126
+            }
127
+          }
128
+
129
+          if (jsonData) {
130
+            if (jsonData.resultContent) {
131
+              onMessage(jsonData.resultContent)
132
+            } else if (typeof jsonData === 'string') {
133
+              onMessage(jsonData)
134
+            }
135
+          }
136
+        })
137
+
138
+        return readStream()
139
+      })
140
+    }
141
+
142
+    return readStream()
143
+  })
144
+    .catch(error => {
145
+      if (error.name === 'AbortError') {
146
+        console.log('请求被取消')
147
+        return
148
+      }
149
+      console.error('流式请求错误:', error)
150
+      onError(new Error('网络连接失败,请检查网络连接后重试'))
151
+    })
152
+
153
+  return controller
154
+}
155
+
156
+// 流式回答API(带文档)- 使用fetch API处理流式响应
157
+export function getAnswerWithDocumentStream(params, onMessage, onError, onComplete) {
158
+  const baseURL = process.env.VUE_APP_BASE_API
159
+  const url = `${baseURL}/llm/session/answerWithDocument?topicId=${params.topicId}&chatId=${params.chatId}&question=${encodeURIComponent(params.question)}`
160
+
161
+  const controller = new AbortController()
162
+
163
+  fetch(url, {
164
+    method: 'GET',
165
+    headers: {
166
+      'Authorization': 'Bearer ' + getToken(),
167
+      'Accept': 'application/json, text/event-stream',
168
+      'Cache-Control': 'no-cache'
169
+    },
170
+    signal: controller.signal
171
+  }).then(response => {
172
+    if (!response.ok) {
173
+      throw new Error(`HTTP error! status: ${response.status}`)
174
+    }
175
+
176
+    const reader = response.body.getReader()
177
+    const decoder = new TextDecoder()
178
+    let buffer = ''
179
+
180
+    function readStream() {
181
+      return reader.read().then(({ done, value }) => {
182
+        if (done) {
183
+          if (buffer.trim()) {
184
+            const lines = buffer.split(/\r?\n/)
185
+            lines.forEach(line => {
186
+              line = line.trim()
187
+              if (!line || line.startsWith(':')) return
188
+
189
+              let jsonData = null
190
+              if (line.startsWith('data: ')) {
191
+                try {
192
+                  jsonData = JSON.parse(line.slice(6))
193
+                } catch (error) {
194
+                  console.error('解析剩余SSE数据失败:', error, line)
195
+                }
196
+              } else if (line.startsWith('data:')) {
197
+                try {
198
+                  jsonData = JSON.parse(line.slice(5))
199
+                } catch (error) {
200
+                  console.error('解析剩余SSE数据失败(无空格):', error, line)
201
+                }
202
+              } else {
203
+                try {
204
+                  jsonData = JSON.parse(line)
205
+                } catch (error) {
206
+                  console.error('解析剩余JSON数据失败:', error, line)
207
+                }
208
+              }
209
+
210
+              if (jsonData) {
211
+                if (jsonData.resultContent) {
212
+                  onMessage(jsonData.resultContent)
213
+                } else if (jsonData.choices && jsonData.choices[0] && jsonData.choices[0].delta && jsonData.choices[0].delta.content) {
214
+                  onMessage(jsonData.choices[0].delta.content)
215
+                } else if (typeof jsonData === 'string') {
216
+                  onMessage(jsonData)
217
+                }
218
+              }
219
+            })
220
+          }
221
+
222
+          onComplete()
223
+          return
224
+        }
225
+
226
+        const chunk = decoder.decode(value, { stream: true })
227
+        buffer += chunk
228
+
229
+        const lines = buffer.split(/\r?\n/)
230
+        buffer = lines.pop() || ''
231
+
232
+        lines.forEach(line => {
233
+          line = line.trim()
234
+          if (!line || line.startsWith(':')) return
235
+
236
+          let jsonData = null
237
+          if (line.startsWith('data: ')) {
238
+            try {
239
+              jsonData = JSON.parse(line.slice(6))
240
+            } catch (error) {
241
+              console.error('解析SSE数据失败:', error, line)
242
+            }
243
+          } else if (line.startsWith('data:')) {
244
+            try {
245
+              jsonData = JSON.parse(line.slice(5))
246
+            } catch (error) {
247
+              console.error('解析SSE数据失败(无空格):', error, line)
248
+            }
249
+          } else {
250
+            try {
251
+              jsonData = JSON.parse(line)
252
+            } catch (error) {
253
+              console.error('解析JSON数据失败:', error, line)
254
+            }
255
+          }
256
+
257
+          if (jsonData) {
258
+            if (jsonData.resultContent) {
259
+              onMessage(jsonData.resultContent)
260
+            } else if (typeof jsonData === 'string') {
261
+              onMessage(jsonData)
262
+            }
263
+          }
264
+        })
265
+
266
+        return readStream()
267
+      })
268
+    }
269
+
270
+    return readStream()
271
+  })
272
+    .catch(error => {
273
+      if (error.name === 'AbortError') {
274
+        console.log('请求被取消')
275
+        return
276
+      }
277
+      console.error('流式请求错误:', error)
278
+      onError(new Error('网络连接失败,请检查网络连接后重试'))
279
+    })
280
+
281
+  return controller
25 282
 }

+ 24
- 0
oa-ui/src/views/flowable/form/changeForm.vue 查看文件

@@ -69,6 +69,8 @@ import flow from '@/views/flowable/task/todo/detail/flow';
69 69
 import { complete, getNextFlowNode } from "@/api/flowable/todo";
70 70
 import { flowXmlAndNode } from "@/api/flowable/definition";
71 71
 import projectChoose from '@/views/flowable/form/components/chooseProject.vue';
72
+import { sendQyMessage } from "@/api/qywx/index";
73
+import { getUser } from '@/api/system/user';
72 74
 import { getDept } from "@/api/system/dept";
73 75
 
74 76
 export default {
@@ -258,6 +260,7 @@ export default {
258 260
             const params = { taskId: this.taskForm.taskId };
259 261
             getNextFlowNode(params).then(res => {
260 262
               if (this.taskName == '变更登记') {
263
+                sendQyMessage(this.leaderList);
261 264
                 this.$set(this.taskForm.variables, "approvalList", this.leaderList);
262 265
                 complete(this.taskForm).then(response => {
263 266
                   this.$modal.msgSuccess(response.msg);
@@ -281,6 +284,7 @@ export default {
281 284
             });
282 285
             const params = { taskId: this.taskForm.taskId };
283 286
             getNextFlowNode(params).then(res => {
287
+              sendQyMessage(this.leaderList);
284 288
               this.$set(this.taskForm.variables, "approvalList", this.leaderList);
285 289
               complete(this.taskForm).then(response => {
286 290
                 this.$modal.msgSuccess(response.msg);
@@ -291,6 +295,26 @@ export default {
291 295
         }
292 296
       });
293 297
     },
298
+    // 发送企业微信消息
299
+    async sendQyMessage(userIds) {
300
+      if (userIds && userIds.length > 0) {
301
+        let formData = new FormData();
302
+        let message = "经营发展部发起项目变更通知:  \n>" + 
303
+        "登记人:<font color='info'>" + this.getUserName(this.form.registrant) + "</font> \n>" + 
304
+        "变更项目:" + this.chooseProject.projectNumber + "-" + this.chooseProject.projectName + " \n>" + 
305
+        "变更内容:" + this.form.content + " \n>";
306
+        formData.append('message', message);
307
+        let userString = [];
308
+        for (let u of userIds) {
309
+          let { data } = await getUser(u);
310
+          if (data && data.pinyin) {
311
+            userString.push(data.pinyin);
312
+          }
313
+        }
314
+        formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
315
+        await sendQyMessage(formData);
316
+      }
317
+    },
294 318
   }
295 319
 };
296 320
 </script>

+ 118
- 86
oa-ui/src/views/flowable/form/finance/borrowForm.vue 查看文件

@@ -370,6 +370,8 @@ import ReturnComment from '@/views/flowable/form/components/flowBtn/returnCommen
370 370
 import ReturnBtn from '@/views/flowable/form/components/flowBtn/returnBtn.vue';
371 371
 import newBudgetInfo from '@/views/flowable/form/budget/adjust/newBudgetInfo.vue';
372 372
 import HistoryBorrow from './historyBorrow.vue';
373
+import { sendQyMessage } from "@/api/qywx/index"
374
+import { getUser } from "@/api/system/user"
373 375
 
374 376
 export default {
375 377
   dicts: ['cmc_borrow_expense', 'cmc_unit'],
@@ -557,6 +559,9 @@ export default {
557 559
         this.rules = {
558 560
           zjlComment: [
559 561
             { required: true, message: '请输入总经理审批意见', trigger: 'blur' }
562
+          ],
563
+          managerAmount: [
564
+            { required: true, message: '请输入核准金额', trigger: 'blur' }
560 565
           ]
561 566
         }
562 567
       } else if (this.taskName == '董事长审核') {
@@ -740,13 +745,17 @@ export default {
740 745
       this.$confirm(msg, '提示', {
741 746
         confirmButtonText: '确定',
742 747
         type: 'warning'
743
-      }).then(() => {
744
-        this.$refs["form"].validate(valid => {
748
+      }).then(async () => {
749
+        this.$refs["form"].validate(async (valid) => {
745 750
           if (valid) {
746 751
             if (this.form.borrowUsage == '0' && !this.form.projectId) {
747 752
               this.$message.error('请选择项目!')
748 753
               return
749 754
             }
755
+            let formData = new FormData();
756
+            let message = ''
757
+            let userIds = [];
758
+            let userString = []
750 759
             if (this.formTotal != 0) {
751 760
               // 更新借款明细项
752 761
               delBorrowDetail(this.taskForm.formId).then(res => {
@@ -782,68 +791,79 @@ export default {
782 791
               // 更新借款审批表
783 792
               updateBorrow(this.form);
784 793
               const params = { taskId: this.taskForm.taskId };
785
-              getNextFlowNode(params).then(res => {
786
-                if (this.taskName == '借款申请') {
787
-                  this.borrowAprrovalFun();
788
-                } else if (this.taskName == '部门审核') {
789
-                  this.$set(this.taskForm.variables, "need", this.need);
790
-                  if (this.need) {
791
-                    getUsersManageLeaderByDept({ deptId: this.form.applyDept }).then(res => {
792
-                      let userIds = [];
793
-                      if (res.data) {
794
-                        res.data.forEach(item => {
795
-                          userIds.push(item.userId)
796
-                        })
797
-                      }
798
-                      this.$set(this.taskForm.variables, "approvalList", userIds);
799
-                      this.handleComplete(this.taskForm);
800
-                    })
801
-                  }
802
-                  else {
803
-                    getUsersManageLeaderByDept({ deptId: this.form.applyDept }).then(res => {
804
-                      let userIds = [];
805
-                      if (res.data) {
806
-                        res.data.forEach(item => {
807
-                          userIds.push(item.userId)
808
-                        })
809
-                      }
810
-                      this.$set(this.taskForm.variables, "approvalList", userIds);
811
-                      getUserByPost({ postName: '总经理' }).then(res => {
812
-                        this.$set(this.taskForm.variables, "approval", res.data[0].userId);
813
-                        this.handleComplete(this.taskForm);
814
-                      })
794
+              const res = await getNextFlowNode(params);
795
+              if (this.taskName == '借款申请') {
796
+                let userId = await this.borrowAprrovalFun()
797
+                userIds.push(userId);
798
+              }
799
+              else if (this.taskName == '部门审核') {
800
+                this.$set(this.taskForm.variables, "need", this.need);
801
+                if (this.need) {
802
+                  const res1 = await getUsersManageLeaderByDept({ deptId: this.form.applyDept });
803
+                  if (res1.data) {
804
+                    res1.data.forEach(item => {
805
+                      userIds.push(item.userId)
815 806
                     })
816 807
                   }
808
+                  this.$set(this.taskForm.variables, "approvalList", userIds);
809
+                  this.handleComplete(this.taskForm);
817 810
                 }
818
-                else if (this.taskName == '分管审核') {
819
-                  getUserByPost({ postName: '总经理' }).then(res => {
820
-                    this.$set(this.taskForm.variables, "approval", res.data[0].userId);
821
-                    this.handleComplete(this.taskForm);
822
-                  })
823
-                }
824
-                else if (this.taskName == '总经理审核') {
825
-                  // this.exceed = false; //待删
826
-                  this.$set(this.taskForm.variables, "exceed", this.exceed);
827
-                  if (!this.exceed) { //没有超预算
828
-                    this.submitFD(); //提交到财务部审核
829
-                  }
830
-                  else {
831
-                    getUserByPost({ postName: '董事长' }).then(res => {
832
-                      this.$set(this.taskForm.variables, "approval", res.data[0].userId);
833
-                      this.handleComplete(this.taskForm);
811
+                else {
812
+                  const res1 = await getUsersManageLeaderByDept({ deptId: this.form.applyDept });
813
+                  if (res1.data) {
814
+                    res1.data.forEach(item => {
815
+                      userIds.push(item.userId)
834 816
                     })
835 817
                   }
818
+                  this.$set(this.taskForm.variables, "approvalList", userIds);
819
+                  const res2 = await getUserByPost({ postName: '总经理' });
820
+                  userIds = []
821
+                  userIds.push(res2.data[0].userId)
822
+                  this.$set(this.taskForm.variables, "approval", res2.data[0].userId);
823
+                  this.handleComplete(this.taskForm);
836 824
                 }
837
-                else if (this.taskName == '董事长批准') {
838
-                  this.submitFD(); //提交到财务部审核
839
-                }
840
-                else if (this.taskName == '党工团审核') {
841
-                  this.submitFD(); //提交到财务部审核
825
+              }
826
+              else if (this.taskName == '分管审核') {
827
+                const res1 = await getUserByPost({ postName: '总经理' });
828
+                userIds.push(res1.data[0].userId)
829
+                this.$set(this.taskForm.variables, "approval", res1.data[0].userId);
830
+                this.handleComplete(this.taskForm);
831
+              }
832
+              else if (this.taskName == '总经理审核') {
833
+                this.$set(this.taskForm.variables, "exceed", this.exceed);
834
+                if (!this.exceed) {
835
+                  userIds = this.submitFD();
842 836
                 }
843
-                else if (this.taskName == '财务处理') {
837
+                else {
838
+                  const res1 = await getUserByPost({ postName: '董事长' });
839
+                  userIds.push(res1.data[0].userId)
840
+                  this.$set(this.taskForm.variables, "approval", res1.data[0].userId);
844 841
                   this.handleComplete(this.taskForm);
845 842
                 }
846
-              })
843
+              }
844
+              else if (this.taskName == '董事长批准') {
845
+                userIds = this.submitFD();
846
+              }
847
+              else if (this.taskName == '党工团审核') {
848
+                userIds = this.submitFD();
849
+              }
850
+              else if (this.taskName == '财务处理') {
851
+                this.handleComplete(this.taskForm);
852
+              }
853
+              for (let u of userIds) {
854
+                let { data } = await getUser(u)
855
+                userString.push(data.pinyin)
856
+              }
857
+              message = "您有一条新的借款申请:  \n>" + 
858
+              "申请人:<font color='info'>" + this.form.applierUser.nickName + "</font>  \n>" + 
859
+              "借款金额:<font color='warning'>" + this.form.applyAmount + "</font> 元  \n>" + 
860
+              "借款说明:" + (this.form.applyReason ? this.form.applyReason : this.form.remark) + "  \n>"
861
+              formData.append('message', message)
862
+              formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
863
+              
864
+              if (userIds.length > 0)
865
+                sendQyMessage(formData);
866
+
847 867
             } else {
848 868
               delBorrowDetail(this.taskForm.formId).then(() => {
849 869
                 for (let detail of this.detailList) {
@@ -857,9 +877,22 @@ export default {
857 877
               });
858 878
               const params = { taskId: this.taskForm.taskId };
859 879
               // 提交到下一个流程
860
-              getNextFlowNode(params).then(res => {
861
-                this.borrowAprrovalFun();
862
-              })
880
+              const res = await getNextFlowNode(params);
881
+              let userId = await this.borrowAprrovalFun()
882
+              userIds.push(userId);
883
+              for (let u of userIds) {
884
+                let { data } = await getUser(u)
885
+                userString.push(data.pinyin)
886
+              }
887
+              message = "您有一条新的借款申请:  \n>" + 
888
+              "申请人:<font color='info'>" + this.getUserName(this.form.applier) + "</font>  \n>" + 
889
+              "借款金额:<font color='warning'>" + this.form.applyAmount + "</font> 元  \n>" + 
890
+              "借款说明:" + (this.form.applyReason ? this.form.applyReason : this.form.remark) + "  \n>"
891
+              formData.append('message', message)
892
+              formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
893
+              
894
+              if (userIds.length > 0)
895
+                sendQyMessage(formData);
863 896
             }
864 897
           } else {
865 898
             this.$message.error('请完善必填项')
@@ -875,7 +908,7 @@ export default {
875 908
     },
876 909
     // 借款申请提交方法
877 910
     async borrowAprrovalFun() {
878
-      let userId;
911
+      let userId = 0;
879 912
       // 如果是党工团申请 deptId == 0 为党工团申请
880 913
       if (this.deptId == 0) {
881 914
         // 2为工会、3为党委
@@ -891,40 +924,39 @@ export default {
891 924
         this.handleComplete(this.taskForm);
892 925
       } else if (this.deptId == 102) { //如果是经营管理层申请,走总经理审批
893 926
         this.form.managerAmount = this.form.applyAmount;
894
-        updateBorrow(this.form);
895
-        getUserByPost({ postName: '总经理' }).then(res => {
896
-          this.$set(this.taskForm.variables, "dept", this.deptId);
897
-          this.$set(this.taskForm.variables, "approval", res.data[0].userId);
898
-          this.handleComplete(this.taskForm);
899
-        })
927
+        await updateBorrow(this.form);
928
+        const res = await getUserByPost({ postName: '总经理' });
929
+        userId = res.data[0].userId;
930
+        this.$set(this.taskForm.variables, "dept", this.deptId);
931
+        this.$set(this.taskForm.variables, "approval", userId);
932
+        this.handleComplete(this.taskForm);
900 933
       } else if (this.deptId == 101) {//如果是董事会申请,走董事长批准
901
-        getUserByPost({ postName: '董事长' }).then(res => {
902
-          this.$set(this.taskForm.variables, "dept", this.deptId);
903
-          this.$set(this.taskForm.variables, "approval", res.data[0].userId);
904
-          this.$set(this.taskForm.variables, "exceed", true);
905
-          this.handleComplete(this.taskForm);
906
-        })
934
+        const res = await getUserByPost({ postName: '董事长' });
935
+        userId = res.data[0].userId;
936
+        this.$set(this.taskForm.variables, "dept", this.deptId);
937
+        this.$set(this.taskForm.variables, "approval", userId);
938
+        this.$set(this.taskForm.variables, "exceed", true);
939
+        this.handleComplete(this.taskForm);
907 940
       }
908 941
       else { //普通员工申请项目借款
909
-        getUsersDeptLeader({ userId: this.$store.getters.userId }).then(res => {
910
-          let userId = res.data.userId;
911
-          this.$set(this.taskForm.variables, "approval", userId);
912
-          this.$set(this.taskForm.variables, "dept", this.deptId);
913
-          this.handleComplete(this.taskForm);
914
-        })
942
+        const res = await getUsersDeptLeader({ userId: this.$store.getters.userId });
943
+        userId = res.data.userId;
944
+        this.$set(this.taskForm.variables, "approval", userId);
945
+        this.$set(this.taskForm.variables, "dept", this.deptId);
946
+        this.handleComplete(this.taskForm);
915 947
       }
948
+      return userId
916 949
     },
917 950
     // 提交给财务部审核
918
-    submitFD() {
951
+    async submitFD() {
919 952
       let approvalList = [];
920
-      getUsersDeptLeaderByDept({ deptId: 106 }).then(res => {
921
-        approvalList.push(res.data.userId);
922
-        getUsersViceDeptLeaderByDept({ deptId: 106 }).then(res1 => {
923
-          approvalList.push(res1.data.userId);
924
-          this.$set(this.taskForm.variables, "approvalList", approvalList);
925
-          this.handleComplete(this.taskForm);
926
-        })
927
-      });
953
+      const res = await getUsersDeptLeaderByDept({ deptId: 106 });
954
+      approvalList.push(res.data.userId);
955
+      const res1 = await getUsersViceDeptLeaderByDept({ deptId: 106 });
956
+      approvalList.push(res1.data.userId);
957
+      this.$set(this.taskForm.variables, "approvalList", approvalList);
958
+      this.handleComplete(this.taskForm);
959
+      return approvalList
928 960
     },
929 961
     showFormItem(name) {
930 962
       let isShow = false;

+ 46
- 16
oa-ui/src/views/flowable/form/oa/carForm.vue 查看文件

@@ -2,7 +2,7 @@
2 2
  * @Author: ysh
3 3
  * @Date: 2024-02-29 11:44:28
4 4
  * @LastEditors: wrh
5
- * @LastEditTime: 2025-06-23 09:35:51
5
+ * @LastEditTime: 2026-03-03 17:28:05
6 6
 -->
7 7
 
8 8
 <template>
@@ -290,6 +290,7 @@ import { listUser, getUser } from '@/api/system/user';
290 290
 import { getUserByRole } from "@/api/system/role";
291 291
 import chooseUser from "@/views/flowable/form/budget/components/choosePeople.vue";
292 292
 import projectChoose from '@/views/flowable/form/components/chooseProject.vue';
293
+import { sendQyMessage } from "@/api/qywx/index";
293 294
 import { number } from 'echarts';
294 295
 export default {
295 296
   components: {
@@ -588,63 +589,70 @@ export default {
588 589
         //   this.dept = 102
589 590
         this.$set(this.taskForm.variables, "dept", this.dept);
590 591
         if (this.dept == 101) {
591
-          getUserByPost({ postName: '董事长' }).then(result => {
592
+          getUserByPost({ postName: '董事长' }).then(async result => {
592 593
             this.$set(this.taskForm.variables, "approval", result.data[0].userId);
593 594
             complete(this.taskForm).then(response => {
594 595
               this.$modal.msgSuccess(response.msg);
595 596
               this.$emit('goBack')
596
-            })
597
+            });
598
+            await this.sendQyMessage([result.data[0].userId]);
597 599
           })
598 600
         }
599 601
         else if (this.dept == 102) {
600
-          getUserByPost({ postName: '总经理' }).then(result => {
602
+          getUserByPost({ postName: '总经理' }).then(async result => {
601 603
             this.$set(this.taskForm.variables, "approval", result.data[0].userId);
602 604
             complete(this.taskForm).then(response => {
603 605
               this.$modal.msgSuccess(response.msg);
604 606
               this.$emit('goBack')
605
-            })
607
+            });
608
+            await this.sendQyMessage([result.data[0].userId]);
606 609
           })
607 610
         }
608 611
         else if (this.dept == 0) {
609 612
           let postName = this.getChooseType()
610
-          getUserByPost({ postName: postName }).then(result => {
613
+          getUserByPost({ postName: postName }).then(async result => {
611 614
             this.$set(this.taskForm.variables, "approval", result.data[0].userId);
612 615
             complete(this.taskForm).then(response => {
613 616
               this.$modal.msgSuccess(response.msg);
614 617
               this.$emit('goBack')
615
-            })
618
+            });
619
+            await this.sendQyMessage([result.data[0].userId]);
616 620
           })
617 621
         }
618 622
         else {
619
-          getUsersDeptLeader({ userId: this.$store.getters.userId }).then(res => {
623
+          getUsersDeptLeader({ userId: this.$store.getters.userId }).then(async res => {
620 624
             if (res.data) {
621 625
               this.$set(this.taskForm.variables, "approval", res.data.userId);
622 626
               complete(this.taskForm).then(response => {
623 627
                 this.$modal.msgSuccess(response.msg);
624 628
                 this.$emit('goBack')
625
-              })
629
+              });
630
+              await this.sendQyMessage([res.data.userId]);
626 631
             }
627 632
           })
628 633
         }
629 634
       } else if (this.taskName == '部门审核') {
630
-        getUsersManageLeader({ userId: this.$store.getters.userId }).then(res => {
631
-          let userId = [];
635
+        getUsersManageLeader({ userId: this.$store.getters.userId }).then(async res => {
636
+          let userIds = [];
632 637
           res.data.forEach(user => {
633
-            userId.push(user.userId)
634
-          })
635
-          this.$set(this.taskForm.variables, "approvalList", userId);
638
+            userIds.push(user.userId)
639
+          });
640
+          this.$set(this.taskForm.variables, "approvalList", userIds);
636 641
           complete(this.taskForm).then(response => {
637 642
             this.$modal.msgSuccess(response.msg);
638 643
             this.$emit('goBack')
639 644
           });
645
+          await this.sendQyMessage(userIds);
640 646
         })
641 647
       } else if (this.taskName == '分管审核' || this.taskName == '党工团审核' || this.taskName == '总经理审核' || this.taskName == '董事长审核') {
642
-        getUserByRole({ roleId: 5 }).then(result => {
648
+        getUserByRole({ roleId: 5 }).then(async result => {
649
+          let userIds = result.data.map(item => item.userId);
643 650
           this.$set(this.taskForm.variables, "approvalList", result.data);
644 651
           complete(this.taskForm).then(response => {
645 652
             this.$modal.msgSuccess(response.msg);
646 653
             this.$emit('goBack')
647
-          })
654
+          });
655
+          await this.sendQyMessage(userIds);
648 656
         });
649 657
       } else if (this.taskName == '安排用车') {
650 658
         this.$modal.confirm('最后一个节点,提交将结束流程,是否提交?').then(() => {
@@ -789,6 +797,28 @@ export default {
789 797
         isShow = this.taskName == '董事长审核' || ((this.taskName == '安排用车' || this.taskName == '') && this.form.dszUserId != null);
790 798
       return isShow;
791 799
     },
800
+    // 发送企业微信消息
801
+    async sendQyMessage(userIds) {
802
+      if (userIds && userIds.length > 0) {
803
+        let formData = new FormData();
804
+        let message = "您有一条新的用车申请:  \n>" + 
805
+        "申请人:<font color='info'>" + this.getUserName(this.form.applier) + "</font> \n>" + 
806
+        "用车事由:" + this.form.applyReason + " \n>" + 
807
+        "开始日期:" + this.form.beginDate + " \n>" + 
808
+        "结束日期:" + this.form.endDate + " \n>" + 
809
+        "乘车人数:" + this.form.passengers + "人  \n>";
810
+        formData.append('message', message);
811
+        let userString = [];
812
+        for (let u of userIds) {
813
+          let { data } = await getUser(u);
814
+          if (data && data.pinyin) {
815
+            userString.push(data.pinyin);
816
+          }
817
+        }
818
+        formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
819
+        await sendQyMessage(formData);
820
+      }
821
+    },
792 822
   },
793 823
 }
794 824
 </script>

+ 82
- 33
oa-ui/src/views/flowable/form/oa/deviceForm.vue 查看文件

@@ -1,8 +1,8 @@
1 1
 <!--
2 2
  * @Author: ysh
3 3
  * @Date: 2024-03-07 13:44:39
4
- * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-08-27 11:15:28
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2026-03-10 15:04:05
6 6
 -->
7 7
 
8 8
 <template>
@@ -220,6 +220,8 @@ import { getUsersManageLeader } from '@/api/system/post.js'
220 220
 import flow from '@/views/flowable/task/todo/detail/flow'
221 221
 import { flowXmlAndNode } from "@/api/flowable/definition";
222 222
 import { getUserByRole } from "@/api/system/role";
223
+import { sendQyMessage } from "@/api/qywx/index";
224
+import { getUser } from "@/api/system/user";
223 225
 import projectChoose from '@/views/flowable/form/components/chooseProject.vue';
224 226
 import ChooseDevice from '../budget/components/chooseDevice.vue';
225 227
 export default {
@@ -434,8 +436,8 @@ export default {
434 436
         }
435 437
       })
436 438
     },
437
-    submit() {
438
-      this.$refs['deviceForm'].validate((valid) => {
439
+    async submit() {
440
+      this.$refs['deviceForm'].validate(async (valid) => {
439 441
         if (valid) {
440 442
           let y1 = new Date(this.form.beginDate);
441 443
           let y2 = new Date(this.form.endDate);
@@ -453,9 +455,33 @@ export default {
453 455
             submitDeviceApproval(jsonForm);
454 456
           }
455 457
           // 获取下一个流程节点
456
-          getNextFlowNode(params).then(res => {
458
+          getNextFlowNode(params).then(async res => {
457 459
             const data = res.data;
458
-            this.getNextFlowNodeApproval();
460
+            let userIds = await this.getNextFlowNodeApproval();
461
+            // 发送企业微信消息
462
+            let userString = [];
463
+            let message = '';
464
+            let formData = new FormData();
465
+
466
+            for (let u of userIds) {
467
+              let { data } = await getUser(u);
468
+              if (data) {
469
+                userString.push(data.pinyin);
470
+              }
471
+            }
472
+            
473
+            message = "您有一条新的设备申请:  \n>" + 
474
+            "申请人:<font color='info'>" + this.getUserName(this.form.applier) + "</font>  \n>" + 
475
+            "开始日期:" + this.form.beginDate + " \n>" + 
476
+            "结束日期:" + this.form.endDate + " \n>" +  
477
+            "申请事由:" + this.form.applyReason + "  \n>";
478
+            
479
+            formData.append('message', message);
480
+            formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
481
+            
482
+            if (userIds.length > 0) {
483
+              sendQyMessage(formData);
484
+            }
459 485
           })
460 486
         } else {
461 487
           this.$message.error('必填项未填写完毕')
@@ -478,8 +504,8 @@ export default {
478 504
         }
479 505
       })
480 506
     },
481
-    completeApply() {
482
-      this.$refs['deviceForm'].validate((valid) => {
507
+    async completeApply() {
508
+      this.$refs['deviceForm'].validate(async (valid) => {
483 509
         if (valid) {
484 510
           this.form.formId = this.taskForm.formId;
485 511
           this.form.deviceApplyId = this.taskForm.formId;
@@ -487,9 +513,33 @@ export default {
487 513
           modifyDeviceApproval(jsonForm);
488 514
           const params = { taskId: this.taskForm.taskId };
489 515
           // 获取下一个流程节点
490
-          getNextFlowNode(params).then(res => {
516
+          getNextFlowNode(params).then(async res => {
491 517
             const data = res.data;
492
-            this.getNextFlowNodeApproval();
518
+            let userIds = await this.getNextFlowNodeApproval();
519
+            // 发送企业微信消息
520
+            let userString = [];
521
+            let message = '';
522
+            let formData = new FormData();
523
+            
524
+            for (let u of userIds) {
525
+              let { data } = await getUser(u);
526
+              if (data) {
527
+                userString.push(data.pinyin);
528
+              }
529
+            }
530
+            
531
+            message = "您有一条新的设备申请:  \n>" + 
532
+            "申请人:<font color='info'>" + this.getUserName(this.form.applier) + "</font>  \n>" + 
533
+            "开始日期:" + this.form.beginDate + " \n>" + 
534
+            "结束日期:" + this.form.endDate + " \n>" +  
535
+            "申请事由:" + this.form.applyReason + "  \n>";
536
+            
537
+            formData.append('message', message);
538
+            formData.append('userString', userString.join('|') + '|YuSiHan|WangRongHua')
539
+            
540
+            if (userIds.length > 0) {
541
+              sendQyMessage(formData);
542
+            }
493 543
           })
494 544
         } else {
495 545
           this.$message.error('必填项未填写完毕')
@@ -497,34 +547,32 @@ export default {
497 547
       })
498 548
     },
499 549
     // 获取下一个审批人
500
-    getNextFlowNodeApproval() {
550
+    async getNextFlowNodeApproval() {
551
+      let userIds = [];
501 552
       if (this.taskName == '设备申请') {
502
-        getUserByRole({ roleId: 4 }).then(result => {
503
-          this.$set(this.taskForm.variables, "approval", result.data[0]);
504
-          complete(this.taskForm).then(response => {
505
-            this.$modal.msgSuccess(response.msg);
506
-            this.$emit('goBack')
507
-          });
553
+        const result = await getUserByRole({ roleId: 4 });
554
+        userIds.push(result.data[0]);
555
+        this.$set(this.taskForm.variables, "approval", result.data[0]);
556
+        complete(this.taskForm).then(response => {
557
+          this.$modal.msgSuccess(response.msg);
558
+          this.$emit('goBack')
508 559
         });
509 560
       } else if (this.taskName == '安排设备') {
510
-        getUsersManageLeader({ userId: this.form.applier }).then(res => {
511
-          let userId = [];
512
-          res.data.forEach(user => {
513
-            userId.push(user.userId)
514
-          })
515
-          this.$set(this.taskForm.variables, "approvalList", userId);
516
-          complete(this.taskForm).then(response => {
517
-            this.$modal.msgSuccess(response.msg);
518
-            this.$emit('goBack')
519
-          });
561
+        const res = await getUsersManageLeader({ userId: this.form.applier });        
562
+        res.data.forEach(user => {
563
+          userIds.push(user.userId)
520 564
         })
565
+        this.$set(this.taskForm.variables, "approvalList", userIds);
566
+        complete(this.taskForm).then(response => {
567
+          this.$modal.msgSuccess(response.msg);
568
+          this.$emit('goBack')
569
+        });
521 570
       } else if (this.taskName == '分管审核') {
522
-        getUserByRole({ roleId: 4 }).then(result => {
523
-          this.$set(this.taskForm.variables, "approval", result.data[0]);
524
-          complete(this.taskForm).then(response => {
525
-            this.$modal.msgSuccess(response.msg);
526
-            this.$emit('goBack')
527
-          });
571
+        const result = await getUserByRole({ roleId: 4 });
572
+        this.$set(this.taskForm.variables, "approval", result.data[0]);
573
+        complete(this.taskForm).then(response => {
574
+          this.$modal.msgSuccess(response.msg);
575
+          this.$emit('goBack')
528 576
         });
529 577
       } else if (this.taskName == '归还确认') {
530 578
         this.$modal.confirm('最后一个节点,提交将结束流程,是否提交?').then(() => {
@@ -534,6 +582,7 @@ export default {
534 582
           });
535 583
         })
536 584
       }
585
+      return userIds;
537 586
     },
538 587
     // 查询项目列表
539 588
     getProjectList() {

+ 279
- 106
oa-ui/src/views/llm/chat/index.vue 查看文件

@@ -292,7 +292,7 @@ import { Message as ElMessage, MessageBox as ElMessageBox } from 'element-ui'
292 292
 import { listTopic, getTopic, delTopic, addTopic, updateTopic } from "@/api/llm/topic";
293 293
 import { listChat, addChat, updateChat } from "@/api/llm/chat";
294 294
 import { listDocument, uploadDocument } from "@/api/llm/document";
295
-import { getAnswer, getAnswerWithDocument } from "@/api/llm/session";
295
+import { getAnswer, getAnswerWithDocument, getAnswerStream, getAnswerWithDocumentStream } from "@/api/llm/session";
296 296
 import logoImg from '@/assets/images/logo.png'
297 297
 import { marked } from 'marked';
298 298
 
@@ -516,34 +516,26 @@ export default {
516 516
         userId: this.$store.state.user.id,
517 517
         input: this.inputMessage,
518 518
         topicId: this.currentTopicId,
519
-        inputTime: parseTime(new Date(), '{y}-{m}-{d}'),
519
+        inputTime: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
520 520
         chatId: this.documentChatId || null
521 521
       };
522 522
 
523
-      // 添加用户消息到聊天记录
524 523
       this.chatMessages.push(userMessage);
524
+      const userMessageIndex = this.chatMessages.length - 1;
525 525
       const messageToSend = this.inputMessage;
526 526
       this.inputMessage = '';
527 527
 
528
-      // 暂存文件上传ID,用于后续查询
529 528
       const uploadedFileId = this.documentChatId;
530
-
531
-      // 清空文档chartId,确保每次上传只对下一条消息有效
532 529
       this.documentChatId = '';
533
-
534
-      // 清空已上传的文件列表
535 530
       this.selectedFiles = [];
536 531
 
537 532
       await this.$nextTick();
538 533
       this.scrollToBottom();
539 534
 
540
-      // 自动更改当前topic名称为用户问题
541 535
       try {
542
-        // 只有当当前topic名称为"新对话"时才更新为用户问题
543 536
         const currentTopic = this.topicList.find(item => item.topicId === this.currentTopicId);
544 537
         if (currentTopic && currentTopic.topic === '新对话') {
545 538
           await updateTopic({ topicId: this.currentTopicId, topic: messageToSend });
546
-          // 同步本地topicList
547 539
           const updateLocalTopic = (list) => {
548 540
             for (const item of list) {
549 541
               if (item.topicId === this.currentTopicId) {
@@ -557,64 +549,236 @@ export default {
557 549
         }
558 550
       } catch (e) { }
559 551
 
560
-      // 发送消息到后端,得到回答,并更新到数据库
561
-      try {
562
-        this.isLoading = true;
552
+      const aiMessage = {
553
+        userId: this.$store.state.user.id,
554
+        input: '',
555
+        output: '',
556
+        topicId: this.currentTopicId,
557
+        outputTime: parseTime(new Date(), '{y}-{m}-{d}')
558
+      };
559
+      this.chatMessages.push(aiMessage);
563 560
 
564
-        let answer;
565
-        // 判断是否存在附件,存在则使用getAnswerWithDocument API
566
-        if (uploadedFileId) {
567
-          answer = await getAnswerWithDocument({ topicId: this.currentTopicId, chatId: uploadedFileId, question: userMessage.input });
568
-        } else {
569
-          answer = await getAnswer({ topicId: this.currentTopicId, question: userMessage.input });
570
-        }
561
+      const messageIndex = this.chatMessages.length - 1;
571 562
 
572
-        // 使用Vue的响应式更新方法
573
-        const messageIndex = this.chatMessages.length - 1;
574
-        if (messageIndex >= 0) {
575
-          // 使用Vue.set或直接赋值来确保响应式更新
576
-          this.$set(this.chatMessages, messageIndex, {
577
-            ...this.chatMessages[messageIndex],
578
-            output: answer[0].content,
579
-            outputTime: parseTime(new Date(), '{y}-{m}-{d}')
580
-          });
581
-        }
563
+      this.isLoading = true;
582 564
 
583
-        // 保存到数据库
584
-        const savedMessage = await addChat(this.chatMessages[messageIndex]);
565
+      const streamParams = {
566
+        topicId: this.currentTopicId,
567
+        question: userMessage.input
568
+      };
585 569
 
586
-        // 如果保存成功,更新消息的实际ID
587
-        if (savedMessage && savedMessage.chatId) {
588
-          this.$set(this.chatMessages[messageIndex], 'chatId', savedMessage.chatId);
589
-        }
570
+      let streamController;
571
+      if (uploadedFileId) {
572
+        streamParams.chatId = uploadedFileId;
573
+        streamController = getAnswerWithDocumentStream(
574
+          streamParams,
575
+          (content) => {
576
+            const that = this;
577
+            let cleanContent = content.replace(/<\/?think>/g, '');
578
+            if (!cleanContent.trim()) {
579
+              return;
580
+            }
581
+            aiMessage.output += cleanContent;
582
+            aiMessage.outputTime = parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}');
590 583
 
591
-        // 如果当前消息包含文件,使用上传时的ID查询文件列表
592
-        if (uploadedFileId) {
593
-          try {
594
-            const fileResponse = await listDocument({ chatId: uploadedFileId });
595
-            if (fileResponse.rows && fileResponse.rows.length > 0) {
596
-              // 使用消息的chatId作为key存储文件列表
597
-              const messageChatId = this.chatMessages[messageIndex].chatId;
598
-              const keyToUse = messageChatId || uploadedFileId;
599
-              this.messageFileMap.set(keyToUse, fileResponse.rows);
600
-
601
-              // 确保消息有chatId用于显示
602
-              if (!this.chatMessages[messageIndex].chatId) {
603
-                this.$set(this.chatMessages[messageIndex], 'chatId', uploadedFileId);
584
+            if (window.responseTimeout) {
585
+              clearTimeout(window.responseTimeout);
586
+            }
587
+            window.responseTimeout = setTimeout(() => {
588
+              if (that.isLoading) {
589
+                console.log('=== 响应超时强制结束 ===');
590
+                that.isLoading = false;
591
+                if (window.responseTimeout) {
592
+                  clearTimeout(window.responseTimeout);
593
+                  window.responseTimeout = null;
594
+                }
604 595
               }
596
+            }, 300000);
597
+
598
+            that.$nextTick(() => {
599
+              that.scrollToBottom();
600
+            });
601
+          },
602
+          (error) => {
603
+            const that = this;
604
+            console.error('=== 流式回答错误 ===', error);
605
+
606
+            if (window.responseTimeout) {
607
+              clearTimeout(window.responseTimeout);
608
+              window.responseTimeout = null;
605 609
             }
606
-          } catch (error) {
607
-            console.error('Failed to load files for new message:', error);
610
+
611
+            if (aiMessage.output === '') {
612
+              aiMessage.output = '抱歉,我暂时无法回答您的问题,请稍后再试。';
613
+            } else {
614
+              aiMessage.output += '\n\n[回答生成中断]';
615
+            }
616
+            that.isLoading = false;
617
+
618
+            that.$nextTick(() => {
619
+              that.scrollToBottom();
620
+            });
621
+          },
622
+          async () => {
623
+            const that = this;
624
+            console.log('=== 回答完成 ===');
625
+
626
+            if (window.responseTimeout) {
627
+              clearTimeout(window.responseTimeout);
628
+              window.responseTimeout = null;
629
+            }
630
+
631
+            try {
632
+              // 保存用户消息
633
+              const savedUserMessage = await addChat({
634
+                ...that.chatMessages[userMessageIndex],
635
+                topicId: that.currentTopicId
636
+              });
637
+
638
+              // 保存AI消息
639
+              const savedMessage = await addChat({
640
+                ...aiMessage,
641
+                topicId: that.currentTopicId
642
+              });
643
+
644
+              if (savedUserMessage && savedUserMessage.chatId) {
645
+                that.$set(that.chatMessages, userMessageIndex, {
646
+                  ...that.chatMessages[userMessageIndex],
647
+                  chatId: savedUserMessage.chatId
648
+                });
649
+              }
650
+
651
+              if (savedMessage && savedMessage.chatId) {
652
+                that.$set(that.chatMessages, messageIndex, {
653
+                  ...that.chatMessages[messageIndex],
654
+                  chatId: savedMessage.chatId
655
+                });
656
+              }
657
+
658
+              if (uploadedFileId) {
659
+                try {
660
+                  const fileResponse = await listDocument({ chatId: uploadedFileId });
661
+                  if (fileResponse.rows && fileResponse.rows.length > 0) {
662
+                    const messageChatId = that.chatMessages[messageIndex].chatId;
663
+                    const keyToUse = messageChatId || uploadedFileId;
664
+                    that.messageFileMap.set(keyToUse, fileResponse.rows);
665
+
666
+                    if (!that.chatMessages[messageIndex].chatId) {
667
+                      that.$set(that.chatMessages[messageIndex], 'chatId', uploadedFileId);
668
+                    }
669
+                  }
670
+                } catch (error) {
671
+                  console.error('Failed to load files for new message:', error);
672
+                }
673
+              }
674
+            } catch (error) {
675
+              console.error('保存消息失败:', error);
676
+            }
677
+
678
+            that.isLoading = false;
679
+            that.$nextTick(() => {
680
+              that.scrollToBottom();
681
+            });
608 682
           }
609
-        }
683
+        );
684
+      } else {
685
+        streamController = getAnswerStream(
686
+          streamParams,
687
+          (content) => {
688
+            const that = this;
689
+            let cleanContent = content.replace(/<\/?think>/g, '');
690
+            if (!cleanContent.trim()) {
691
+              return;
692
+            }
693
+            aiMessage.output += cleanContent;
694
+            aiMessage.outputTime = parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}');
610 695
 
611
-        this.isLoading = false;
612
-        await this.$nextTick();
613
-        this.scrollToBottom();
614
-      } catch (error) {
615
-        ElMessage.error('发送消息失败');
616
-        this.isLoading = false;
696
+            if (window.responseTimeout) {
697
+              clearTimeout(window.responseTimeout);
698
+            }
699
+            window.responseTimeout = setTimeout(() => {
700
+              if (that.isLoading) {
701
+                console.log('=== 响应超时强制结束 ===');
702
+                that.isLoading = false;
703
+                if (window.responseTimeout) {
704
+                  clearTimeout(window.responseTimeout);
705
+                  window.responseTimeout = null;
706
+                }
707
+              }
708
+            }, 300000);
709
+
710
+            that.$nextTick(() => {
711
+              that.scrollToBottom();
712
+            });
713
+          },
714
+          (error) => {
715
+            const that = this;
716
+            console.error('=== 流式回答错误 ===', error);
717
+
718
+            if (window.responseTimeout) {
719
+              clearTimeout(window.responseTimeout);
720
+              window.responseTimeout = null;
721
+            }
722
+
723
+            if (aiMessage.output === '') {
724
+              aiMessage.output = '抱歉,我暂时无法回答您的问题,请稍后再试。';
725
+            } else {
726
+              aiMessage.output += '\n\n[回答生成中断]';
727
+            }
728
+            that.isLoading = false;
729
+
730
+            that.$nextTick(() => {
731
+              that.scrollToBottom();
732
+            });
733
+          },
734
+          async () => {
735
+            const that = this;
736
+            console.log('=== 回答完成 ===');
737
+
738
+            if (window.responseTimeout) {
739
+              clearTimeout(window.responseTimeout);
740
+              window.responseTimeout = null;
741
+            }
742
+
743
+            try {
744
+              // 保存用户消息
745
+              const savedUserMessage = await addChat({
746
+                ...that.chatMessages[userMessageIndex],
747
+                topicId: that.currentTopicId
748
+              });
749
+
750
+              // 保存AI消息
751
+              const savedMessage = await addChat({
752
+                ...aiMessage,
753
+                topicId: that.currentTopicId
754
+              });
755
+
756
+              if (savedUserMessage && savedUserMessage.chatId) {
757
+                that.$set(that.chatMessages, userMessageIndex, {
758
+                  ...that.chatMessages[userMessageIndex],
759
+                  chatId: savedUserMessage.chatId
760
+                });
761
+              }
762
+
763
+              if (savedMessage && savedMessage.chatId) {
764
+                that.$set(that.chatMessages, messageIndex, {
765
+                  ...that.chatMessages[messageIndex],
766
+                  chatId: savedMessage.chatId
767
+                });
768
+              }
769
+            } catch (error) {
770
+              console.error('保存消息失败:', error);
771
+            }
772
+
773
+            that.isLoading = false;
774
+            that.$nextTick(() => {
775
+              that.scrollToBottom();
776
+            });
777
+          }
778
+        );
617 779
       }
780
+
781
+      window.currentChatController = streamController;
618 782
     },
619 783
 
620 784
     handleKeyDown(event) {
@@ -704,60 +868,69 @@ export default {
704 868
           this.topicList = response.rows;
705 869
           this.total = response.total;
706 870
           this.hasRecords = true;
707
-          // 分类近期记录
871
+        } else {
872
+          // 没有记录时,清空列表
873
+          this.topicList = [];
874
+          this.total = 0;
875
+          this.hasRecords = false;
876
+          // 清空当前选中的话题
877
+          this.currentTopicId = null;
878
+          this.chatMessages = [];
879
+        }
880
+        // 分类近期记录
708 881
 
709
-          const today = new Date()
710
-          today.setHours(0, 0, 0, 0)
882
+        const today = new Date()
883
+        today.setHours(0, 0, 0, 0)
711 884
 
712
-          const yesterday = new Date(today)
713
-          yesterday.setDate(yesterday.getDate() - 1)
885
+        const yesterday = new Date(today)
886
+        yesterday.setDate(yesterday.getDate() - 1)
714 887
 
715
-          const dayBeforeYesterday = new Date(today)
716
-          dayBeforeYesterday.setDate(dayBeforeYesterday.getDate() - 2)
888
+        const dayBeforeYesterday = new Date(today)
889
+        dayBeforeYesterday.setDate(dayBeforeYesterday.getDate() - 2)
717 890
 
718
-          const sevenDaysAgo = new Date(today)
719
-          sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
891
+        const sevenDaysAgo = new Date(today)
892
+        sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
720 893
 
721
-          const thirtyDaysAgo = new Date(today)
722
-          thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
894
+        const thirtyDaysAgo = new Date(today)
895
+        thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
723 896
 
724
-          // 清空之前的分类
725
-          this.classifiedRecent = {
726
-            today: [],
727
-            yesterday: [],
728
-            dayBeforeYesterday: [],
729
-            within7Days: [],
730
-            within30Days: []
731
-          };
732
-          this.classifiedMonthly = {};
733
-
734
-          this.topicList.forEach(record => {
735
-            const recordDate = new Date(record.createTime)
736
-            recordDate.setHours(0, 0, 0, 0)
737
-
738
-            if (recordDate.getTime() === today.getTime()) {
739
-              this.classifiedRecent.today.push(record)
740
-            } else if (recordDate.getTime() === yesterday.getTime()) {
741
-              this.classifiedRecent.yesterday.push(record)
742
-            } else if (recordDate.getTime() === dayBeforeYesterday.getTime()) {
743
-              this.classifiedRecent.dayBeforeYesterday.push(record)
744
-            } else if (recordDate > sevenDaysAgo) {
745
-              this.classifiedRecent.within7Days.push(record)
746
-            } else if (recordDate > thirtyDaysAgo) {
747
-              this.classifiedRecent.within30Days.push(record)
748
-            } else if (recordDate <= thirtyDaysAgo) {
749
-              const monthKey = this.getMonthKey(recordDate)
750
-
751
-              if (!this.classifiedMonthly[monthKey]) {
752
-                this.classifiedMonthly[monthKey] = []
753
-              }
754
-
755
-              this.classifiedMonthly[monthKey].push(record)
897
+        // 清空之前的分类
898
+        this.classifiedRecent = {
899
+          today: [],
900
+          yesterday: [],
901
+          dayBeforeYesterday: [],
902
+          within7Days: [],
903
+          within30Days: []
904
+        };
905
+        this.classifiedMonthly = {};
906
+
907
+        this.topicList.forEach(record => {
908
+          const recordDate = new Date(record.createTime)
909
+          recordDate.setHours(0, 0, 0, 0)
910
+
911
+          if (recordDate.getTime() === today.getTime()) {
912
+            this.classifiedRecent.today.push(record)
913
+          } else if (recordDate.getTime() === yesterday.getTime()) {
914
+            this.classifiedRecent.yesterday.push(record)
915
+          } else if (recordDate.getTime() === dayBeforeYesterday.getTime()) {
916
+            this.classifiedRecent.dayBeforeYesterday.push(record)
917
+          } else if (recordDate > sevenDaysAgo) {
918
+            this.classifiedRecent.within7Days.push(record)
919
+          } else if (recordDate > thirtyDaysAgo) {
920
+            this.classifiedRecent.within30Days.push(record)
921
+          } else if (recordDate <= thirtyDaysAgo) {
922
+            const monthKey = this.getMonthKey(recordDate)
923
+
924
+            if (!this.classifiedMonthly[monthKey]) {
925
+              this.classifiedMonthly[monthKey] = []
756 926
             }
757
-          })
758
-        }
759
-      } catch (err) {
760
-        console.error('获取数据失败:', err)
927
+
928
+            this.classifiedMonthly[monthKey].push(record)
929
+          }
930
+        })
931
+      } catch (error) {
932
+        console.error('获取话题列表失败:', error)
933
+        ElMessage.error('获取话题列表失败')
761 934
       } finally {
762 935
         this.loading = false
763 936
       }

+ 3
- 3
oa-ui/src/views/llm/knowledge/index.vue 查看文件

@@ -586,7 +586,7 @@ export default {
586 586
             clearTimeout(window.responseTimeout);
587 587
           }
588 588
 
589
-          // 重新设置超时定时器(30秒无新消息才超时)
589
+          // 重新设置超时定时器(5分钟无新消息才超时)
590 590
           window.responseTimeout = setTimeout(() => {
591 591
             if (that.isSending) {
592 592
               console.log('=== 响应超时强制结束 ===')
@@ -597,7 +597,7 @@ export default {
597 597
               }
598 598
               window.responseTimeout = null;
599 599
             }
600
-          }, 30000); // 30秒无响应超时
600
+          }, 300000); // 30秒无响应超时
601 601
 
602 602
           // 滚动到底部
603 603
           that.$nextTick(() => {
@@ -696,7 +696,7 @@ export default {
696 696
           }
697 697
           window.responseTimeout = null;
698 698
         }
699
-      }, 30000); // 30秒超时
699
+      }, 300000); // 5分钟超时
700 700
     },
701 701
 
702 702
     /** 停止生成回答 */

+ 2
- 2
oa-ui/src/views/system/user/profile/index.vue 查看文件

@@ -20,7 +20,7 @@
20 20
               </div>
21 21
 
22 22
               <div class="info-item" v-for="item in userInfo">
23
-                <div class="item-label" v-if="item.value != '' && item.value != undefined">
23
+                <div class="item-label" v-if="item.value != '' && item.value != null">
24 24
                   <svg-icon :icon-class="item.icon" class="info-icon"></svg-icon>
25 25
                   <div class="item-label">{{ item.label }}</div>
26 26
                 </div>
@@ -215,7 +215,7 @@ export default {
215 215
           {
216 216
             label: '紧急联系方式:',
217 217
             icon: 'user',
218
-            value: this.user.contact ? this.user.contact : '' + this.user.telephone ? this.user.telephone : ''
218
+            value: (this.user.contact ? this.user.contact : '') + (this.user.telephone ? this.user.telephone : '')
219 219
           },
220 220
           {
221 221
             label: '备注:',

+ 8
- 3
oa-ui/src/views/system/user/profile/userInfo.vue 查看文件

@@ -2,10 +2,10 @@
2 2
  * @Author: wrh
3 3
  * @Date: 2024-01-03 08:55:38
4 4
  * @LastEditors: wrh
5
- * @LastEditTime: 2026-02-27 11:34:24
5
+ * @LastEditTime: 2026-02-27 15:48:01
6 6
 -->
7 7
 <template>
8
-  <el-form ref="form" :model="form" :rules="rules" label-width="100px">
8
+  <el-form ref="form" :model="form" :rules="rules" label-width="110px">
9 9
     <el-form-item label="用户昵称" prop="nickName">
10 10
       <el-input v-model="form.nickName" maxlength="30" />
11 11
     </el-form-item> 
@@ -69,7 +69,12 @@ export default {
69 69
           { required: true, message: "紧急联系人不能为空", trigger: "blur" }
70 70
         ],
71 71
         telephone: [
72
-          { required: true, message: "紧急联系电话不能为空", trigger: "blur" }
72
+          { required: true, message: "紧急联系电话不能为空", trigger: "blur" },
73
+          {
74
+            pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
75
+            message: "请输入正确的手机号码",
76
+            trigger: "blur"
77
+          }
73 78
         ],
74 79
         phonenumber: [
75 80
           { required: true, message: "手机号码不能为空", trigger: "blur" },

正在加载...
取消
保存