Selaa lähdekoodia

Merge remote-tracking branch 'origin/master'

chenkq 3 viikkoa sitten
vanhempi
sitoutus
215605498a

+ 15 - 10
java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/ChunkController.java

@@ -59,11 +59,15 @@ public class ChunkController {
     @ApiOperation("删除文本块")
     public Result<Object> deleteDataset(@PathVariable String dataset_id , @PathVariable String document_id , @PathVariable String segment_id ) {
         String url = difyProperties.getBaseUrl()  + "datasets/"+ dataset_id +"/documents/"+document_id +"/segments/"+segment_id;
-        return httpService.delete(
+        Result<Object> result = httpService.delete(
                 url,
                 null,
                 "Bearer "+difyProperties.getDatasetApiKey(),
                 new TypeReference<>() {}) ;
+        if (result.isSuccess()) {
+            return Result.result("删除成功");
+        }
+        return result;
     }
 
     @PostMapping("/{dataset_id}/{document_id}")
@@ -71,30 +75,31 @@ public class ChunkController {
     public Result<?> createSegments(
             @PathVariable String dataset_id,
             @PathVariable String document_id,
-            @RequestBody ChunkDTO request
-    ) throws JsonProcessingException {
-        String url = difyProperties.getBaseUrl()  + "datasets/"+ dataset_id +"/documents/"+document_id +"/segments/";
+            @RequestBody String  request
+    ) {
+        String url = difyProperties.getBaseUrl()  + "datasets/"+ dataset_id +"/documents/"+document_id +"/segments";
         return  httpService.post(
                 url,
-                Map.of(),
+                null,
                 "Bearer "+difyProperties.getDatasetApiKey(),
-                JacksonHolder.OBJECT_MAPPER.writeValueAsString(request),
+                request,
                 new TypeReference<>() {}
         );
-
     }
+
+
     @PostMapping("/{dataset_id}/{document_id}/{segment_id}")
     @ApiOperation("更新指定ID的文本块")
     public Result<Object> updateChunk(  @PathVariable String dataset_id,
                                         @PathVariable String document_id,
                                         @PathVariable String segment_id,
-                                        @RequestBody ChunkDTO request) throws JsonProcessingException {
+                                        @RequestBody String request) {
         String url = difyProperties.getBaseUrl()  + "datasets/"+ dataset_id +"/documents/"+document_id +"/segments/"+segment_id;
         return   httpService.post(
                 url,
-                Map.of(),
+                null,
                 "Bearer "+difyProperties.getDatasetApiKey(),
-                JacksonHolder.OBJECT_MAPPER.writeValueAsString(request),
+                request,
                 new TypeReference<>() {}
         );
     }

+ 106 - 0
java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/DocumentController.java

@@ -16,6 +16,9 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 
@@ -142,4 +145,107 @@ public class DocumentController {
         );
     }
 
+
+
+    @GetMapping("getAllDocumnets")
+    @ApiOperation("获取所有的文档")
+    public Result<Object> getAllDocumnets(String dataset_id,QueryPageDTO page) {
+        // 1. 调用Dify API获取所有知识库列表(使用较大分页以获取全部数据)
+        String url = difyProperties.getBaseUrl() + "datasets";
+        Result<Object> datasetsResult = httpService.get(
+                url,
+                Map.of("page", 1, "limit", 100),
+                "Bearer " + difyProperties.getDatasetApiKey(),
+                new TypeReference<>() {}
+        );
+
+        List<Object> results =  new ArrayList<>();
+        if (datasetsResult.isSuccess()) {
+            Object responseData = datasetsResult.getResult();
+            if (responseData instanceof Map) {
+                Map<?, ?> responseMap = (Map<?, ?>) responseData;
+                // 遍历数据集,累加文档数和文本块数
+                Object dataObj = responseMap.get("data");
+                if (dataObj instanceof List) {
+                    List<?> dataList = (List<?>) dataObj;
+                    for (Object item : dataList) {
+                        if (item instanceof Map ) {
+                            Map<?, ?> datasetMap = (Map<?, ?>) item;
+                            String datasets_id = datasetMap.get("id").toString();
+                            if (dataset_id != null && !datasets_id.equals(dataset_id)) {
+                                continue;
+                            }
+                            results.addAll(getDataSetsDocuments(datasets_id, page.getKeyword()));
+                        }
+                    }
+                }
+            }
+        }
+
+        // 对结果进行分页
+        int total = results.size();
+        int pageIndex = page.getPageIndex() != null ? page.getPageIndex() : 1;
+        int pageSize = page.getPageSize() != null ? page.getPageSize() : 10;
+        int fromIndex = (pageIndex - 1) * pageSize;
+        int toIndex = Math.min(fromIndex + pageSize, total);
+
+        List<Object> pagedResults;
+        if (fromIndex >= total) {
+            pagedResults = new ArrayList<>();
+        } else {
+            pagedResults = results.subList(fromIndex, toIndex);
+        }
+        Map<String,Object> resultMap = new HashMap<>() ;
+        resultMap.put("data", pagedResults);
+        resultMap.put("total", total);
+        resultMap.put("limit", pageSize);
+        resultMap.put("page", pageIndex);
+        resultMap.put("has_more", total > (fromIndex + pageSize));
+        return Result.ok(resultMap);
+    }
+
+
+    /**
+     * 获取单个知识库的所有的文本块
+     * @param dataset_id
+     * @return
+     */
+    public List<Object> getDataSetsDocuments(String dataset_id,String keyword){
+        List<Object> results =  new ArrayList<>();
+        //获取知识库中文档的列表
+        String url1 = difyProperties.getBaseUrl() + "datasets/"+dataset_id+"/documents";
+        int page0 = 1;
+        while(true){
+            Result<Object> documents_list  = httpService.get(
+                    url1,
+                    keyword==null?Map.of("page", 1, "limit", 100):Map.of("page", 1, "limit", 100,"keyword",keyword),
+                    "Bearer " + difyProperties.getDatasetApiKey(),
+                    new TypeReference<>() {});
+
+            if (documents_list.isSuccess()) {
+                Object responseData1 = documents_list.getResult();
+                Map<?, ?> responseMap2 = (Map<?, ?>) responseData1;
+                Object dataObj1 = responseMap2.get("data");
+                //分页上线为100
+                Object has_more = responseMap2.get("has_more");
+
+                if (dataObj1 instanceof List) {
+                    List<?> dataList1 = (List<?>) dataObj1;
+                    for (Object item2 : dataList1) {
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> datasetMap2 = (Map<String, Object>) item2;
+                        datasetMap2.put("dataset_id", dataset_id);
+                        results.add(datasetMap2);
+                    }
+                }
+                if (has_more.toString().equals("false")) {
+                    break;
+                }else{
+                    page0 +=1 ;
+                }
+            }
+        }
+        return  results;
+    }
+
 }

+ 3 - 1
java/storlead-knowledge/storlead-knowledge-core/src/main/java/com/storlead/knowledge/utils/HttpService.java

@@ -58,7 +58,9 @@ public class HttpService {
     ) {
         int status = response.statusCode();
         String body = response.body();
-
+        if (status == 204) {
+            return (Result<T>) Result.result("删除成功");
+        }
         if (status >= 200 && status < 300) {
             try {
                 return Result.result(deserializer.apply(body));

+ 2 - 0
ui/smarttrade-platform/.env.development

@@ -1,2 +1,4 @@
 # 开发环境后台接口地址
 VITE_API_BASE_URL=http://localhost:10010/
+
+VITE_API_APP_ID=7

+ 2 - 0
ui/smarttrade-platform/.env.production

@@ -1,2 +1,4 @@
 # 生产环境后台接口地址
 VITE_API_BASE_URL=/api
+
+VITE_API_APP_ID=7

+ 9 - 0
ui/smarttrade-platform/package-lock.json

@@ -11,6 +11,7 @@
         "@fortawesome/fontawesome-free": "^6.5.1",
         "axios": "^1.6.7",
         "element-plus": "^2.14.2",
+        "moment": "^2.30.1",
         "pinia": "^2.1.7",
         "vue": "^3.4.21",
         "vue-router": "^4.3.0"
@@ -1513,6 +1514,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/moment": {
+      "version": "2.30.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

+ 1 - 0
ui/smarttrade-platform/package.json

@@ -12,6 +12,7 @@
     "@fortawesome/fontawesome-free": "^6.5.1",
     "axios": "^1.6.7",
     "element-plus": "^2.14.2",
+    "moment": "^2.30.1",
     "pinia": "^2.1.7",
     "vue": "^3.4.21",
     "vue-router": "^4.3.0"

+ 31 - 1
ui/smarttrade-platform/src/api/knowledge/index.js

@@ -17,4 +17,34 @@ export const baseTraining = (params = {}) => get('/router/rest/knowledge/base/tr
 
 export const creatTraining = (data = {}) => post('/router/rest/knowledge/base/training', data)
 
-export const baseStatistics = (params = {}) => get('/router/rest/knowledge/base/statistics', params)
+export const baseStatistics = (params = {}) => get('/router/rest/knowledge/base/statistics', params)
+
+export const getAllChunks = (params = {}) => get('/router/rest/knowledge/chunk/getAllChunks', params)
+
+export const getAllDocumnets = (params = {}) => {
+    const query = { ...params }
+    if ([undefined, null, ''].includes(query.dataset_id)) {
+        delete query.dataset_id
+    }
+    return get('/router/rest/knowledge/document/getAllDocumnets', query)
+}
+
+export const delDocuments = (params = {}) => {
+    const { dataset_id, document_id, ...query } = params
+    return remove(`/router/rest/knowledge/document/${dataset_id}/documents/${document_id}`, params)
+}
+
+export const createChunk = (data = {}) => {
+    const { dataset_id,document_id, ...query } = data
+    return post(`/router/rest/knowledge/chunk/${dataset_id}/${document_id}`, data)
+}
+
+export const delChunk = (params = {}) => {
+    const { dataset_id, document_id,segment_id, ...query } = params
+    return  remove(`/router/rest/knowledge/chunk/${dataset_id}/${document_id}/${segment_id}`, params)
+}
+
+export const editChunk = (data = {}) => {
+    const { dataset_id,document_id,segment_id, ...query } = data
+    return post(`/router/rest/knowledge/chunk/${dataset_id}/${document_id}/${segment_id}`, data)
+}

+ 6 - 1
ui/smarttrade-platform/src/api/layout/index.js

@@ -1,6 +1,11 @@
 import { get, post, put, remove, upload } from '@/utils/request'
 
-export const getNoReadCount = () => post('/router/rest/sys/message/getNoReadCountByApp', {appId: 7})
+export const getNoReadCount = () => post('/router/rest/sys/message/getNoReadCount', {appId: import.meta.env.VITE_API_APP_ID})
+export const getMessageList = (data) => post('/router/rest/sys/message/pageList', data)
+export const updateRead = (data) => post('/router/rest/sys/message/read', data)
+export const updateReadAll = (data) => post('/router/rest/sys/message/readAll', data)
+export const listPageBuUserSet = (data) => post('/router/rest/sys/message/config/listPage', data)
+export const setMessageConfig = (data) => post('/router/rest/sys/message/config/setMessageConfig', data)
 export const getUserInfo = () => post('/router/rest/sys/auth/getCurrentUserInfo')
 export const updateUserPwd = (data) => post('/router/rest/sys/auth/modifyPass', data)
 export const updateRemark = (data) => post('/router/rest/sys/auth/updateRemark', data)

+ 2 - 0
ui/smarttrade-platform/src/assets/styles/main.css

@@ -1,4 +1,6 @@
 :root {
+  --el-color-primary: #6366f1;
+  
   /* 主色调 */
   --primary: #6366f1;
   --primary-light: #818cf8;

+ 13 - 3
ui/smarttrade-platform/src/components/layout/Sidebar.vue

@@ -330,6 +330,7 @@
 import { ref, onMounted } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { getUserInfo, updateUserPwd, getLoginData, updateRemark } from '@/api/layout'
+import { ElMessageBox } from 'element-plus'
 
 const route = useRoute()
 const router = useRouter()
@@ -380,9 +381,18 @@ const toggleTheme = () => {
 }
 
 const handleLogout = () => {
-  showUserMenu.value = false
-  // Handle logout
-  console.log('Logout')
+  ElMessageBox.confirm(
+    '确定要退出登录吗?',
+    '提示',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  ).then(() => {
+    localStorage.clear()
+    router.push({ path: '/login' })
+  })
 }
 
 const pwdForm = ref({

+ 162 - 245
ui/smarttrade-platform/src/components/layout/TopBar.vue

@@ -28,7 +28,7 @@
       <!-- Notifications -->
       <div class="action-btn notification-btn" @click="toggleNotifications" :class="{ active: showNotifications }">
         <i class="fas fa-bell"></i>
-        <span v-if="unreadCount > 0" class="notification-badge">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
+        <span v-if="noReadCount > 0" class="notification-badge">{{ noReadCount > 99 ? '99+' : noReadCount }}</span>
       </div>
 
       <!-- Help -->
@@ -45,11 +45,11 @@
           <div class="panel-title">
             <i class="fas fa-bell"></i>
             消息中心
-            <span class="unread-count">{{ unreadCount }}条未读</span>
+            <span class="unread-count" v-if="noReadCount > 0">{{ noReadCount }}条未读</span>
           </div>
           <div class="panel-actions">
             <button class="action-text" @click="markAllAsRead">全部已读</button>
-            <button class="action-text" @click="showSettings = true">
+            <button class="action-text" @click="openSettings">
               <i class="fas fa-cog"></i>
             </button>
           </div>
@@ -59,14 +59,13 @@
         <div class="panel-tabs">
           <div 
             v-for="tab in notificationTabs" 
-            :key="tab.key"
+            :key="tab.messageType"
             class="panel-tab"
-            :class="{ active: activeTab === tab.key }"
-            @click="activeTab = tab.key"
+            :class="{ active: activeTab === tab.messageType }"
+            @click="activeTab = tab.messageType"
           >
-            <i :class="tab.icon"></i>
-            {{ tab.label }}
-            <span v-if="tab.count > 0" class="tab-count">{{ tab.count }}</span>
+            {{ tab.messageTypeName }}
+            <span v-if="tab.stateNumber > 0" class="tab-count">{{ tab.stateNumber > 99 ? '99+' : tab.stateNumber }}</span>
           </div>
         </div>
 
@@ -77,30 +76,23 @@
               v-for="notification in filteredNotifications" 
               :key="notification.id"
               class="notification-item"
-              :class="{ unread: !notification.read, [notification.type]: true }"
+              :class="{ unread: !notification.isRead }"
               @click="handleNotificationClick(notification)"
             >
-              <div class="notification-icon" :style="{ background: getIconBg(notification.type), color: getIconColor(notification.type) }">
-                <i :class="notification.icon"></i>
+              <div class="notification-icon" :style="{ background: getIconBg('reply'), color: getIconColor('reply') }">
+                <i class="fas fa-reply"></i>
               </div>
               <div class="notification-content">
                 <div class="notification-title">{{ notification.title }}</div>
-                <div class="notification-desc">{{ notification.description }}</div>
+                <div class="notification-desc">{{ notification.content }}</div>
                 <div class="notification-meta">
                   <span class="notification-time">{{ notification.time }}</span>
-                  <span v-if="notification.source" class="notification-source">
-                    <i :class="notification.sourceIcon"></i>
-                    {{ notification.source }}
-                  </span>
                 </div>
               </div>
               <div class="notification-actions">
-                <button v-if="!notification.read" class="mark-read-btn" @click.stop="markAsRead(notification.id)">
+                <button v-if="!notification.isRead" class="mark-read-btn" @click.stop="markAsRead(notification)">
                   <i class="fas fa-check"></i>
                 </button>
-                <button class="more-btn" @click.stop="showNotificationMenu($event, notification)">
-                  <i class="fas fa-ellipsis-v"></i>
-                </button>
               </div>
             </div>
           </template>
@@ -114,9 +106,7 @@
 
         <!-- Panel Footer -->
         <div class="panel-footer">
-          <button class="btn btn-secondary btn-sm" @click="viewAllNotifications">
-            <i class="fas fa-list"></i> 查看全部消息
-          </button>
+          <div></div>
           <button class="btn btn-primary btn-sm" @click="showNotifications = false">
             关闭
           </button>
@@ -224,8 +214,8 @@
         <div class="modal notification-detail-modal">
           <div class="modal-header">
             <div class="modal-title">
-              <div class="title-icon" :style="{ background: getIconBg(selectedNotification.type), color: getIconColor(selectedNotification.type) }">
-                <i :class="selectedNotification.icon"></i>
+              <div class="title-icon" :style="{ background: getIconBg('reply'), color: getIconColor('reply') }">
+                <i class="fas fa-reply"></i>
               </div>
               <span>{{ selectedNotification.title }}</span>
             </div>
@@ -238,7 +228,7 @@
               <i class="fas fa-clock"></i>
               {{ selectedNotification.time }}
             </div>
-            <div class="detail-content" v-html="selectedNotification.detail"></div>
+            <div class="detail-content" v-html="selectedNotification.content"></div>
             
             <!-- Quick Actions -->
             <div v-if="selectedNotification.actions" class="detail-actions">
@@ -580,27 +570,38 @@
   <AddMarketing ref="addMarketingRef" />
 
   <div v-if="showSettings" class="modal-overlay" @click.self="showSettings = false">
-    <div class="modal create-modal">
+    <div class="modal create-modal" style="max-width: 800px;">
       <div class="modal-header">
           <div class="modal-title"><i class="fas fa-bell"></i> 通知设置</div>
           <button class="modal-close" @click="showSettings = false"><i class="fas fa-times"></i></button>
       </div>
       <div class="modal-body">
         <div class="settings-list">
-          <div class="settings-item" style="padding-top: 0;">
-            <div class="item-icon" style="background: rgba(99, 102, 241, 0.2); color: var(--primary);">
+          <div class="settings-item" v-for="item in settingsData">
+            <!-- <div class="item-icon" style="background: rgba(99, 102, 241, 0.2); color: var(--primary);">
               <i class="fas fa-envelope"></i>
-            </div>
+            </div> -->
             <div class="item-content">
-              <div class="item-title">邮件通知</div>
-              <div class="item-desc">重要通知发送到邮箱</div>
+              <div class="item-title">{{ item.templateServiceName }}</div>
+              <div class="item-desc">{{ item.eventName }}</div>
             </div>
             <div class="item-action">
-              <el-switch v-model="settings.emailNotify" style="--el-switch-on-color: #13ce66;" />
+              <div>
+                <span>企微消息:</span>
+                <el-switch v-model="item.wecom" @change="changeSetting(item, 'wecom')" />
+              </div>
+              <div>
+                <span>站内信:</span>
+                <el-switch v-model="item.site" @change="changeSetting(item, 'site')" />
+              </div>
+              <div>
+                <span>邮件:</span>
+                <el-switch v-model="item.mail" @change="changeSetting(item, 'mail')" />
+              </div>
             </div>
           </div>
 
-          <div class="settings-item">
+          <!-- <div class="settings-item">
             <div class="item-icon" style="background: rgba(16, 185, 129, 0.2); color: var(--success);">
               <i class="fas fa-reply"></i>
             </div>
@@ -609,9 +610,20 @@
               <div class="item-desc">客户回复邮件、消息时即时提醒</div>
             </div>
             <div class="item-action">
-              <el-switch v-model="settings.replyNotify" style="--el-switch-on-color: #13ce66;" />
+              <div>
+                <span>企微消息:</span>
+                <el-switch v-model="settings.replyNotify" />
+              </div>
+              <div>
+                <span>站内信:</span>
+                <el-switch v-model="settings.replyNotify" />
+              </div>
+              <div>
+                <span>邮件:</span>
+                <el-switch v-model="settings.replyNotify" />
+              </div>
             </div>
-          </div>
+          </div> -->
         </div>
       </div>
     </div>
@@ -622,7 +634,8 @@
 import { ref, computed, onMounted } from 'vue'
 import { useRoute } from 'vue-router'
 import AddMarketing from '../addMarketing.vue'
-import { getNoReadCount } from '@/api/layout'
+import { getNoReadCount, getMessageList, updateRead, updateReadAll, listPageBuUserSet } from '@/api/layout'
+import moment from 'moment'
 
 const route = useRoute()
 const showQuickActions = ref(false)
@@ -770,9 +783,32 @@ const generateReport = () => {
   alert('报告生成中,完成后将发送至您的邮箱...')
 }
 
+// Notification data
+const notifications = ref([])
 const toggleNotifications = () => {
   showNotifications.value = !showNotifications.value
-  if (showNotifications.value) showHelp.value = false
+  if (showNotifications.value){
+    showHelp.value = false
+    let params = {
+      pageIndex: 1,
+      pageSize: 10000,
+      appId: import.meta.env.VITE_API_APP_ID
+    }
+    getMessageList(params).then(res => {
+      notifications.value = (res.result.records || []).map(item => {
+          // 判断创建时间是不是今天
+          let date = moment(item.createTime).format("YYYY-MM-DD HH:mm:ss")
+          if(date.slice(0, 10) == moment().format("YYYY-MM-DD")){
+              item.time = "今天 " + date.slice(11, 16)
+          }else{
+              item.time = moment(item.createTime).format("MM月DD日 HH:mm")
+          }
+          return item
+      })
+    }).catch(err => {
+      console.error('获取消息列表失败:', err)
+    })
+  }
 }
 
 const toggleHelp = () => {
@@ -780,196 +816,8 @@ const toggleHelp = () => {
   if (showHelp.value) showNotifications.value = false
 }
 
-// Notification data
-const notifications = ref([
-  // 客户回复
-  {
-    id: 1,
-    type: 'reply',
-    icon: 'fas fa-reply',
-    title: 'Riyadh Tech 回复了邮件',
-    description: '对方对您的产品报价表示感兴趣,希望进一步了解交期和付款方式',
-    detail: '<p>客户 Ahmed Al-Rashid 回复了您的报价邮件:</p><blockquote style="border-left:3px solid var(--primary);padding-left:12px;margin:12px 0;color:var(--text-secondary);">您好,我们对贵公司的SSD产品很感兴趣。请问MOQ是多少?交期大概多久?付款方式有哪些?</blockquote><p>建议尽快回复客户,可以提供详细的报价单和公司介绍。</p>',
-    time: '5分钟前',
-    source: '邮件',
-    sourceIcon: 'fas fa-envelope',
-    read: false,
-    actions: [
-      { label: '立即回复', icon: 'fas fa-reply', primary: true },
-      { label: '查看邮件', icon: 'fas fa-envelope-open' },
-    ]
-  },
-  {
-    id: 2,
-    type: 'reply',
-    icon: 'fab fa-whatsapp',
-    title: 'Dubai Storage WhatsApp 已读',
-    description: '您发送的消息已被查看,对方正在输入中...',
-    detail: '<p>客户 Mohammed Khan 已读您的 WhatsApp 消息,显示对方正在输入回复。</p><p>消息内容:关于企业级SSD采购的询价</p>',
-    time: '15分钟前',
-    source: 'WhatsApp',
-    sourceIcon: 'fab fa-whatsapp',
-    read: false
-  },
-  {
-    id: 3,
-    type: 'reply',
-    icon: 'fab fa-linkedin',
-    title: 'LinkedIn 好友请求被接受',
-    description: 'Kuwait Data Systems 的 CIO Faisal 接受了您的好友请求',
-    detail: '<p>Faisal Al-Mutairi (CTO @ Kuwait Data Systems) 接受了您的 LinkedIn 连接请求。</p><p>建议:发送个性化感谢消息,介绍公司和产品。</p>',
-    time: '1小时前',
-    source: 'LinkedIn',
-    sourceIcon: 'fab fa-linkedin',
-    read: true
-  },
-  
-  // 风险预警
-  {
-    id: 4,
-    type: 'risk',
-    icon: 'fas fa-exclamation-triangle',
-    title: '⚠️ 高风险预警:客户信用下调',
-    description: 'ABC Corp 信用评级从 A 下调至 B,建议提高预付款比例',
-    detail: '<p><strong>风险等级:高</strong></p><p>客户 ABC Corp 的信用评级发生变动:</p><ul><li>原评级:A</li><li>新评级:B</li><li>原因:近期财务状况恶化</li></ul><p><strong>建议措施:</strong></p><ul><li>提高预付款比例至 50%</li><li>缩短付款周期</li><li>关注后续订单风险</li></ul>',
-    time: '30分钟前',
-    source: '风控系统',
-    sourceIcon: 'fas fa-shield-alt',
-    read: false,
-    actions: [
-      { label: '查看详情', icon: 'fas fa-info-circle', primary: true },
-      { label: '调整付款条件', icon: 'fas fa-edit' }
-    ]
-  },
-  {
-    id: 5,
-    type: 'risk',
-    icon: 'fas fa-gavel',
-    title: '欧盟能效新规即将生效',
-    description: '2026年7月生效,涉及SSD产品能效认证要求',
-    detail: '<p><strong>法规更新提醒</strong></p><p>欧盟委员会发布新的能效认证要求,将于2026年7月1日正式生效。</p><p><strong>影响范围:</strong></p><ul><li>所有出口欧盟的SSD产品</li><li>需要提供能效测试报告</li><li>认证周期约30天</li></ul><p><strong>建议:</strong>提前准备相关认证材料。</p>',
-    time: '2小时前',
-    source: '市场情报',
-    sourceIcon: 'fas fa-globe',
-    read: false
-  },
-  
-  // AI情报
-  {
-    id: 6,
-    type: 'ai',
-    icon: 'fas fa-robot',
-    title: '🤖 AI发现新商机',
-    description: '识别到新加坡 Digital Hub 近期频繁访问官网定价页面',
-    detail: '<p><strong>AI商机分析报告</strong></p><p>客户行为分析:</p><ul><li>过去7天访问定价页面 12 次</li><li>查看企业级SSD产品详情 8 次</li><li>下载产品手册 2 次</li></ul><p><strong>AI建议:</strong>该客户意向度高,建议尽快主动联系。</p>',
-    time: '45分钟前',
-    source: 'AI大脑',
-    sourceIcon: 'fas fa-brain',
-    read: false,
-    actions: [
-      { label: '立即联系', icon: 'fas fa-phone', primary: true },
-      { label: '发送报价', icon: 'fas fa-file-invoice' }
-    ]
-  },
-  {
-    id: 7,
-    type: 'ai',
-    icon: 'fas fa-lightbulb',
-    title: '💡 AI策略建议',
-    description: '基于近期数据分析,建议调整中东地区邮件发送时间',
-    detail: '<p><strong>策略优化建议</strong></p><p>AI分析了过去30天的邮件数据,发现:</p><ul><li>中东客户最佳回复时段:晚上8-10点</li><li>当前发送时段:上午10点</li><li>预计调整后回复率提升:52%</li></ul><p>是否应用此策略?</p>',
-    time: '3小时前',
-    source: 'AI策略中心',
-    sourceIcon: 'fas fa-lightbulb',
-    read: true,
-    actions: [
-      { label: '应用策略', icon: 'fas fa-check', primary: true },
-      { label: '查看详情', icon: 'fas fa-chart-bar' }
-    ]
-  },
-  
-  // 营销报告
-  {
-    id: 8,
-    type: 'marketing',
-    icon: 'fas fa-chart-line',
-    title: '📊 周度营销报告已生成',
-    description: '本周发送邮件 1,247 封,打开率 42%,回复率 18%',
-    detail: '<p><strong>本周营销数据汇总</strong></p><table style="width:100%;border-collapse:collapse;margin:12px 0;"><tr style="border-bottom:1px solid var(--border)"><td style="padding:8px 0;">邮件发送</td><td style="text-align:right;font-weight:600;">1,247 封</td></tr><tr style="border-bottom:1px solid var(--border)"><td style="padding:8px 0;">打开率</td><td style="text-align:right;font-weight:600;color:var(--success)">42% (+8%)</td></tr><tr style="border-bottom:1px solid var(--border)"><td style="padding:8px 0;">回复率</td><td style="text-align:right;font-weight:600;color:var(--success)">18% (+5%)</td></tr><tr><td style="padding:8px 0;">新增商机</td><td style="text-align:right;font-weight:600;">23 个</td></tr></table><p>详细报告已发送至您的邮箱。</p>',
-    time: '今天 09:00',
-    source: '自动报告',
-    sourceIcon: 'fas fa-file-alt',
-    read: true,
-    actions: [
-      { label: '查看完整报告', icon: 'fas fa-file-pdf', primary: true }
-    ]
-  },
-  {
-    id: 9,
-    type: 'marketing',
-    icon: 'fas fa-check-circle',
-    title: '✅ 营销活动完成',
-    description: '中东潜客开发活动已完成,共触达 156 位客户',
-    detail: '<p><strong>活动执行报告</strong></p><p>活动名称:中东潜客开发</p><p>执行时间:2026-03-12 ~ 2026-03-19</p><p><strong>执行结果:</strong></p><ul><li>目标客户:156 位</li><li>邮件送达:152 封 (97%)</li><li>邮件打开:68 封 (45%)</li><li>客户回复:12 封 (8%)</li><li>意向客户:5 位</li></ul>',
-    time: '昨天 18:30',
-    source: '营销系统',
-    sourceIcon: 'fas fa-paper-plane',
-    read: true
-  },
-  
-  // 寻客通知
-  {
-    id: 10,
-    type: 'prospect',
-    icon: 'fas fa-user-plus',
-    title: '🎯 发现 12 个高匹配潜客',
-    description: '基于您的历史成交客户,AI 推荐 12 个相似潜客',
-    detail: '<p><strong>AI寻客报告</strong></p><p>寻客条件:中东地区 + SSD进口商</p><p><strong>推荐潜客 TOP 3:</strong></p><ol><li>Riyadh Tech Distribution (92分)</li><li>Dubai Storage Solutions (88分)</li><li>Saudi Digital Hub (76分)</li></ol><p>点击查看完整列表。</p>',
-    time: '今天 14:30',
-    source: '自动寻客',
-    sourceIcon: 'fas fa-crosshairs',
-    read: false,
-    actions: [
-      { label: '查看潜客列表', icon: 'fas fa-list', primary: true },
-      { label: '发送营销邮件', icon: 'fas fa-paper-plane' }
-    ]
-  },
-  {
-    id: 11,
-    type: 'prospect',
-    icon: 'fas fa-search',
-    title: '寻客任务更新',
-    description: '欧洲企业级存储寻客任务进度 75%,已发现 45 家目标公司',
-    detail: '<p><strong>任务进度更新</strong></p><p>任务名称:欧洲企业级存储寻客</p><p>当前进度:75%</p><p><strong>已发现:</strong></p><ul><li>目标公司:45 家</li><li>关键人信息:28 位</li><li>高匹配度:12 家</li></ul>',
-    time: '今天 11:20',
-    source: '寻客任务',
-    sourceIcon: 'fas fa-tasks',
-    read: true
-  },
-  
-  // 系统通知
-  {
-    id: 12,
-    type: 'system',
-    icon: 'fas fa-cog',
-    title: '系统更新通知',
-    description: '领存智贸 AI 已更新至 v2.3,新增策略自进化功能',
-    detail: '<p><strong>版本更新 v2.3</strong></p><p><strong>新增功能:</strong></p><ul><li>AI策略自进化 - 自动测试优化营销策略</li><li>智能跟进提醒 - 基于客户行为预测最佳跟进时机</li><li>知识库问答优化 - 支持更多行业知识</li></ul><p><strong>优化:</strong></p><ul><li>邮件生成速度提升 30%</li><li>潜客匹配准确率提升 15%</li></ul>',
-    time: '昨天 10:00',
-    source: '系统',
-    sourceIcon: 'fas fa-server',
-    read: true
-  }
-])
-
 // Computed
-const notificationTabs = computed(() => [
-  { key: 'all', label: '全部', icon: 'fas fa-inbox', count: notifications.value.filter(n => !n.read).length },
-  { key: 'reply', label: '客户回复', icon: 'fas fa-reply', count: notifications.value.filter(n => n.type === 'reply' && !n.read).length },
-  { key: 'marketing', label: '营销报告', icon: 'fas fa-chart-line', count: notifications.value.filter(n => n.type === 'marketing' && !n.read).length }
-])
-
-const unreadCount = computed(() => notifications.value.filter(n => !n.read).length)
+const notificationTabs = ref([])
 
 const filteredNotifications = computed(() => {
   if (activeTab.value === 'all') return notifications.value
@@ -1002,30 +850,81 @@ const getIconColor = (type) => {
 }
 
 const handleNotificationClick = (notification) => {
-  if (!notification.read) {
-    notification.read = true
-  }
+  setRead(notification)
   selectedNotification.value = notification
+  // if(notification.callbackUrl){
+  //     window.open(item.callbackUrl, '_blank')
+  // }
 }
 
-const markAsRead = (id) => {
-  const notification = notifications.value.find(n => n.id === id)
-  if (notification) {
-    notification.read = true
-  }
+const markAsRead = (notification) => {
+  setRead(notification)
 }
 
-const markAllAsRead = () => {
-  notifications.value.forEach(n => n.read = true)
+const setRead = (item) => {
+  if(!item.isRead){
+      updateRead({ messageLogIds: [item.id], appId: import.meta.env.VITE_API_APP_ID }).then(res => {
+        item.isRead = 1
+        getCount()
+        // item.stateNumber --
+      }).catch(err => {
+        ElMessage.error(err.msg);
+      })
+  }
 }
 
-const viewAllNotifications = () => {
-  showNotifications.value = false
-  // Navigate to notifications page
+const markAllAsRead = () => {
+  if(notifications.value.length == 0){
+      ElMessage.error('列表暂无数据');
+      return
+  }
+  updateReadAll({ appId: import.meta.env.VITE_API_APP_ID }).then(res => {
+      if(res.success){
+          ElMessage.success('操作成功');
+          notifications.value.map(item => item.isRead = 1)
+          getCount()
+      }else{
+          ElMessage.error(res.msg);
+      }
+  })
 }
 
-const showNotificationMenu = (event, notification) => {
-  // Show context menu
+const settingsData = ref([])
+const openSettings = () => {
+  listPageBuUserSet({ pageIndex: 1, pageSize: 9999, appId: import.meta.env.VITE_API_APP_ID }).then(res => {
+    settingsData.value = res.result.records.map(item => {
+      item.wecom = handleData('wecom', item)
+      item.site = handleData('site', item)
+      item.mail = handleData('mail', item)
+      return item
+    })
+  })
+  showSettings.value = true
+}
+const handleData = (type, column) => {
+  let enabled = true
+	let templateData = null
+	if(column.userDetails && column.userDetails.length > 0) {
+		templateData = column.userDetails.find(item => item.templateEventId == column.id && item.templateDetailType == type)
+      //找到对应的配置就按照对应的配置显示
+      if(templateData) {
+          enabled = templateData?.enabled
+      }
+  }
+  return enabled
+}
+const changeSetting = (item, type) => {
+  let params = {
+    appId: import.meta.env.VITE_API_APP_ID,
+    templateDetailType: type,
+    templateEventId: item.id,
+    enabled: item[type]
+  }
+  setMessageConfig(params).then(res => {
+    ElMessage.success((item[type] ? '启用' : '禁用') + '成功')
+  }).catch(err => {
+    ElMessage.error(err.msg)
+  })
 }
 
 const handleAction = (action) => {
@@ -1106,10 +1005,19 @@ const showPrivacy = () => {
   console.log('Show privacy')
 }
 
-onMounted(() => {
+const noReadCount = ref(0)
+const getCount = () => {
   getNoReadCount().then(res => {
-    console.log('Unread notifications count:', res)
+    let countList = (res.result || []).map(item => item.stateNumber)
+    noReadCount.value = eval(countList.join("+"))
+    notificationTabs.value = [
+      { messageType: 'all', messageTypeName: '全部', stateNumber: noReadCount.value },
+      ...res.result
+    ]
   })
+}
+onMounted(() => {
+  getCount()
 })
 </script>
 
@@ -1701,6 +1609,7 @@ onMounted(() => {
   position: sticky;
   top: 0;
   z-index: 10;
+  margin-bottom: 0;
 }
 
 .panel-title {
@@ -2364,6 +2273,14 @@ onMounted(() => {
   cursor: pointer;
 }
 
+.settings-item:first-child{
+  padding-top: 0;
+}
+
+.settings-item .el-switch{
+  --el-switch-on-color: #13ce66;
+}
+
 .item-icon {
   width: 40px;
   height: 40px;

+ 20 - 4
ui/smarttrade-platform/src/views/CustomersView.vue

@@ -69,7 +69,7 @@
     </div>
 
     <!-- Customer List -->
-    <div style="flex: 1; overflow-y: auto;">
+    <!-- <div style="flex: 1; overflow-y: auto;"> -->
       <div v-for="customer in filteredCustomers" :key="customer.id" 
            class="customer-card"
            @click="showCustomerDetail(customer)">
@@ -83,11 +83,11 @@
             </span> -->
           </div>
           <div class="customer-contact">
-            <i class="fas fa-user"></i> {{ customer.liaison.name }}<span v-if="customer.liaison.departmentPosition"> · </span>{{ customer.liaison.departmentPosition }}
+            <i class="fas fa-user"></i> {{ customer.liaison?.name }}<span v-if="customer.liaison?.departmentPosition"> · </span>{{ customer.liaison?.departmentPosition }}
           </div>
           <div class="customer-meta">
             <span><i class="fas fa-map-marker-alt"></i> {{ customer.continent }} · {{ customer.country }}</span>
-            <span><i class="fas fa-industry"></i> {{ customer.customerCompany.industry }}</span>
+            <span><i class="fas fa-industry"></i> {{ customer.customerCompany?.industry }}</span>
             <!-- <span><i class="fas fa-dollar-sign"></i> {{ customer.annualPurchase }}</span> -->
           </div>
         </div>
@@ -99,7 +99,18 @@
           <button class="btn btn-secondary btn-sm" @click="showCustomerDetail(customer)"><i class="fas fa-eye"></i>更多详情</button>
         </div>
       </div>
-    </div>
+      <div class="pagination-area">
+        <el-pagination
+          background
+          :total="customerStats.total"
+          style="text-align: right;"
+          :current-page="params.pageIndex"
+          :page-size="params.pageSize"
+          layout="total, prev, pager, next"
+          @current-change="pageCurrentChange">
+        </el-pagination>
+      </div>
+    <!-- </div> -->
 
     <!-- 引入添加客户弹窗组件 -->
     <!-- <AddCustomerModal 
@@ -279,6 +290,7 @@ const tabs = ref([
   // { key: 'silent', label: '沉默客户', count: 55 }
 ])
 const customers = ref([])
+const customerTotal = ref()
 const params = ref({
   pageIndex: 1, 
   pageSize: 10,
@@ -293,6 +305,10 @@ const getCustomer = () => {
   })
 }
 getCustomer()
+const pageCurrentChange = (page) => {
+    params.value.pageIndex = page
+    getCustomer()
+}
 
 const regionValue = ref([])
 const getRegion = (value) => {

+ 375 - 81
ui/smarttrade-platform/src/views/KnowledgeView.vue

@@ -6,9 +6,9 @@
         <p class="page-desc">AI 驱动的企业知识管理,智能问答与知识沉淀</p>
       </div>
       <div class="header-actions">
-        <button class="btn btn-secondary" @click="showImportModal = true">
+        <!-- <button class="btn btn-secondary" @click="showImportModal = true">
           <i class="fas fa-file-import"></i> 批量导入
-        </button>
+        </button> -->
         <button class="btn btn-primary" @click="addKnowledgeDocument()">
           <i class="fas fa-plus"></i> 添加知识
         </button>
@@ -109,9 +109,13 @@
         </div>
       </div>
       <div class="tab-actions">
+        <select class="form-select" v-if="activeTab=='all'" v-model="categoryId" @change="changeCategory" style="width: 250px;">
+          <option value="">选择分类</option>
+          <option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
+        </select>
         <div class="search-box">
           <i class="fas fa-search search-icon"></i>
-          <input type="text" v-model="searchQuery" class="search-input" placeholder="搜索知识...">
+          <input type="text" v-model="searchQuery" class="search-input" @change="searchData" placeholder="搜索知识...">
         </div>
       </div>
     </div>
@@ -153,11 +157,14 @@
       <div class="pagination-area">
         <el-pagination
           background
-          layout="prev, pager, next"
-          :total="1000">
+          :total="categoriesTotal"
+          style="text-align: right;"
+          :current-page="knowledgeParams.pageIndex"
+          :page-size="knowledgeParams.pageSize"
+          layout="total, prev, pager, next"
+          @current-change="knowledgeCurrentChange">
         </el-pagination>
       </div>
-      
     </div>
 
     <!-- 全部知识 Tab -->
@@ -165,7 +172,7 @@
       <div class="knowledge-list">
         <div v-for="item in knowledgeList" :key="item.id"
              class="knowledge-card"
-             @click="showKnowledgeDetail(item)">
+             @click="showDocumentDetail(item)">
           <!-- <div class="knowledge-icon" :style="{ background: item.iconBg }">{{ item.icon }}</div> -->
           <div class="knowledge-content">
             <div class="knowledge-header">
@@ -180,15 +187,31 @@
             </div> -->
           </div>
           <div class="knowledge-actions" @click.stop>
-            <button class="btn btn-secondary btn-sm" @click="editKnowledge(item)">
+            <button class="btn btn-secondary btn-sm" @click="editDocument(item)">
               <i class="fas fa-edit"></i>
             </button>
-            <button class="btn btn-secondary btn-sm" @click="deleteKnowledge(item)">
-              <i class="fas fa-trash"></i>
-            </button>
+            <el-popconfirm title="确认删除?" @confirm="deleteKnowledge(item)">
+              <template #reference>
+                <button class="btn btn-secondary btn-sm">
+                  <i class="fas fa-trash"></i>
+                </button>
+              </template>
+            </el-popconfirm>
+            
           </div>
         </div>
       </div>
+      <div class="pagination-area">
+          <el-pagination
+            background
+            :total="categoryTotal"
+            style="text-align: right;"
+            :current-page="documentsParams.pageIndex"
+            :page-size="documentsParams.pageSize"
+            layout="total, prev, pager, next"
+            @current-change="categoryPageCurrentChange">
+          </el-pagination>
+        </div>
     </div>
 
     <!-- 问答记录 Tab -->
@@ -237,6 +260,17 @@
             </div>
           </div>
         </div>
+        <div class="pagination-area">
+          <el-pagination
+            background
+            :total="qaRecordsTotal"
+            style="text-align: right;"
+            :current-page="chatParams.pageIndex"
+            :page-size="chatParams.pageSize"
+            layout="total, prev, pager, next"
+            @current-change="chatPageCurrentChange">
+          </el-pagination>
+        </div>
       </div>
     </div>
 
@@ -279,6 +313,18 @@
             </div>
           </div>
         </div>
+        <div class="pagination-area">
+          <el-pagination
+            background
+            :total="trainingTasksTotal"
+            style="text-align: right;"
+            :current-page="trainingParams.pageIndex"
+            :page-size="trainingParams.pageSize"
+            layout="total, prev, pager, next"
+            @current-change="tasksPageCurrentChange">
+          </el-pagination>
+        </div>
+        
       </div>
     </div>
 
@@ -354,28 +400,35 @@
       </div>
     </div>
 
-    <!-- Add Knowledge Modal -->
-    <div v-if="showAddModal" class="modal-overlay" @click.self="showAddModal = false">
+    <!-- 添加知识弹窗 -->
+    <div v-if="showAddModal" class="modal-overlay" style="z-index: 1002;" @click.self="showAddModal = false">
       <div class="modal add-modal">
         <div class="modal-header">
-          <div class="modal-title"><i class="fas fa-plus"></i> 添加知识</div>
+          <div class="modal-title"><i class="fas fa-plus"></i> {{chunkType=='add'?'添加':'编辑'}}知识</div>
           <button class="modal-close" @click="showAddModal = false"><i class="fas fa-times"></i></button>
         </div>
         <div class="modal-body">
           <div class="form-group">
             <label class="form-label">问题/关键词 <span class="required">*</span></label>
-            <input type="text" class="form-input" v-model="newKnowledge.name" placeholder="例如:MOQ最小起订量是多少?">
+            <input type="text" class="form-input" v-model="newKnowledge.content" placeholder="例如:MOQ最小起订量是多少?">
           </div>
           <div class="form-group">
             <label class="form-label">答案/内容 <span class="required">*</span></label>
-            <textarea class="form-textarea" v-model="newKnowledge.text" rows="4" placeholder="输入详细答案..."></textarea>
+            <textarea class="form-textarea" v-model="newKnowledge.answer" rows="4" placeholder="输入详细答案..."></textarea>
           </div>
           <div class="form-group">
             <label class="form-label">知识分类 <span class="required">*</span></label>
-            <select class="form-select" v-model="newKnowledge.dataset_id">
+            <select class="form-select" v-model="newKnowledge.dataset_id" @change='getDocument' :disabled='chunkType=="edit"'>
               <option value="">选择分类</option>
               <option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
             </select>
+          </div>
+          <div class="form-group">
+            <label class="form-label">知识文档 <span class="required">*</span></label>
+            <select class="form-select" v-model="newKnowledge.document_id" :disabled='chunkType=="edit"'>
+              <option value="">选择文档</option>
+              <option v-for="cat in documentList" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
+            </select>
           </div>
             <!-- <div class="form-group">
               <label class="form-label">关键词标签</label>
@@ -388,20 +441,78 @@
         </div>
         <div class="modal-footer">
           <button class="btn btn-secondary" @click="showAddModal = false">取消</button>
-          <button class="btn btn-primary" @click="addKnowledge"><i class="fas fa-plus"></i> 添加</button>
+          <button class="btn btn-primary" @click="addKnowledge"><i class="fas fa-plus"></i> {{chunkType=='add'?'添加':'确认'}}</button>
         </div>
       </div>
     </div>
 
-    <!-- Knowledge Detail Modal -->
+    <!-- 知识文本块列表弹窗 -->
+    <div v-if="showChunksModal" class="modal-overlay" @click.self="showChunksModal = false">
+      <div class="modal import-modal" style="min-width: 1000px;">
+        <div class="modal-header">
+          <div class="modal-title"><i class="fas fa-file-import"></i> {{ documentName }}</div>
+          <button class="modal-close" @click="showChunksModal = false"><i class="fas fa-times"></i></button>
+        </div>
+        <div class="modal-body">
+          <div class="knowledge-list" v-loading="chunkLoading" element-loading-background="rgba(0, 0, 0, 0.8)">
+            <div v-for="item in chunkList" :key="item.id"
+                class="knowledge-card"
+                @click="showKnowledgeDetail(item)">
+              <!-- <div class="knowledge-icon" :style="{ background: item.iconBg }">{{ item.icon }}</div> -->
+              <div class="knowledge-content">
+                <div class="knowledge-header">
+                  <div class="knowledge-question">{{ item.sign_content }}</div>
+                  <!-- <span class="knowledge-category" :style="{ background: item.iconBg }">{{ item.categoryName }}</span> -->
+                </div>
+                <div class="knowledge-answer">{{ item.answer }}</div>
+                <!-- <div class="knowledge-meta">
+                  <span><i class="fas fa-eye"></i> {{ item.views }}次查看</span>
+                  <span><i class="fas fa-thumbs-up"></i> {{ item.likes }}次好评</span>
+                  <span><i class="fas fa-clock"></i> {{ item.time }}</span>
+                </div> -->
+              </div>
+              <div class="knowledge-actions" @click.stop>
+                <button class="btn btn-secondary btn-sm" @click="editKnowledge(item)">
+                  <i class="fas fa-edit"></i>
+                </button>
+                <el-popconfirm title="确认删除?" @confirm="deletechunk(item)">
+                  <template #reference>
+                    <button class="btn btn-secondary btn-sm">
+                      <i class="fas fa-trash"></i>
+                    </button>
+                  </template>
+                </el-popconfirm>
+                
+              </div>
+            </div>
+          </div>
+          <div class="pagination-area">
+            <el-pagination
+              background
+              :total="chunksTotal"
+              style="text-align: right;padding: 0px;"
+              :current-page="chunkParams.pageIndex"
+              :page-size="chunkParams.pageSize"
+              layout="total, prev, pager, next"
+              @current-change="chunkCurrentChange">
+            </el-pagination>
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button class="btn btn-secondary" @click="showChunksModal = false">关闭</button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 知识块详情弹窗 -->
     <div v-if="showDetailModal && selectedKnowledge" class="modal-overlay" @click.self="showDetailModal = false">
       <div class="modal detail-modal">
         <div class="modal-header">
           <div class="modal-title">
-            <span class="detail-icon" :style="{ background: selectedKnowledge.iconBg }">{{ selectedKnowledge.icon }}</span>
+            <!-- <span class="detail-icon" :style="{ background: selectedKnowledge.iconBg }">{{ selectedKnowledge.icon }}</span> -->
             <div>
-              <h3>{{ selectedKnowledge.question }}</h3>
-              <p>{{ selectedKnowledge.categoryName }}</p>
+              <h3>{{ selectedKnowledge.sign_content }}</h3>
+              <!-- <p>{{ selectedKnowledge.categoryName }}</p> -->
             </div>
           </div>
           <button class="modal-close" @click="showDetailModal = false"><i class="fas fa-times"></i></button>
@@ -411,22 +522,22 @@
             <div class="detail-label"><i class="fas fa-lightbulb"></i> 答案内容</div>
             <div class="detail-content">{{ selectedKnowledge.answer }}</div>
           </div>
-          <div class="detail-section" v-if="selectedKnowledge.extendedAnswer">
+          <!-- <div class="detail-section" v-if="selectedKnowledge.extendedAnswer">
             <div class="detail-label"><i class="fas fa-file-alt"></i> 扩展说明</div>
             <div class="detail-content extended">{{ selectedKnowledge.extendedAnswer }}</div>
-          </div>
-          <div class="detail-section">
+          </div> -->
+          <!-- <div class="detail-section">
             <div class="detail-label"><i class="fas fa-tags"></i> 关键词标签</div>
             <div class="detail-tags">
               <span v-for="tag in selectedKnowledge.tags" :key="tag" class="tag">{{ tag }}</span>
             </div>
-          </div>
-          <div class="detail-section" v-if="selectedKnowledge.relatedQuestions && selectedKnowledge.relatedQuestions.length">
+          </div> -->
+          <!-- <div class="detail-section" v-if="selectedKnowledge.relatedQuestions && selectedKnowledge.relatedQuestions.length">
             <div class="detail-label"><i class="fas fa-link"></i> 相关问题</div>
             <div class="related-questions">
               <div v-for="(q, idx) in selectedKnowledge.relatedQuestions" :key="idx" class="related-item">{{ q }}</div>
             </div>
-          </div>
+          </div> -->
           <div class="detail-stats-row">
             <div class="detail-stat">
               <i class="fas fa-eye"></i>
@@ -515,6 +626,7 @@
           
           <div class="import-zone">
             <el-upload
+              ref="uploadRef"
               class="upload-demo"
               drag
               action=""
@@ -524,14 +636,14 @@
               multiple>
               <div class="import-icon"><i class="fas fa-cloud-upload-alt"></i></div>
               <div class="import-text">拖拽文件到此处,或点击上传</div>
-              <div class="import-formats">支持 Excel、CSV、JSON、PDF 格式</div>
+              <div class="import-formats">支持 Excel、CSV、JSON、PDF、TXT、DOC、MD 格式</div>
             </el-upload>
           </div>
           <div class="import-options">
             <div class="import-option" v-for="item in fileListRaw" :key="item.name">
               <i class="fas fa-file-excel"></i>
               <span>{{ item.name }}</span>
-              <button icon="el-icon-edit"></button>
+              <i class="fas fa-times" style="color: #fff;cursor: pointer;" @click="delFile(item)"></i>
             </div>
           </div>
         </div>
@@ -555,7 +667,7 @@
 
 <script setup>
 import { ref, computed } from 'vue'
-import { knowledgeDatasets,knowledgeDocuments,createKnowledge,chatMessages,baseTraining,creatTraining,baseStatistics } from '../api/knowledge'
+import { knowledgeDatasets,knowledgeDocuments,createKnowledge,createChunk,editChunk,chatMessages,baseTraining,creatTraining,baseStatistics,getAllChunks,getAllDocumnets,delDocuments,delChunk } from '../api/knowledge'
 import { formatTimeAgo } from '@/utils/time'
 import { ElMessage } from 'element-plus'
 
@@ -563,7 +675,10 @@ const showAddModal = ref(false)
 const showDetailModal = ref(false)
 const showImportModal = ref(false)
 const showTrainModal = ref(false)
+const showChunksModal = ref(false)
+const chunkLoading = ref(false)
 const selectedKnowledge = ref(null)
+const selectedDocument = ref(null)
 const activeTab = ref('categories')
 const searchQuery = ref('')
 const qaFilter = ref('all')
@@ -572,6 +687,7 @@ const confidenceThreshold = ref(70)
 const showToast = ref(false)
 const toastMessage = ref('')
 const toastType = ref('success')
+const chunkType = ref('add')
 
 
 const syncStatus = {
@@ -591,11 +707,9 @@ const tabs = ref([
 
 const newKnowledge = ref({
   dataset_id: '',
-  text: '',
-  name: '',
-  indexing_technique: "high_quality",
-  doc_form: "qa_model",
-  doc_language:"Chinese Simplified"
+  document_id: '',
+  content: '',
+  answer: '',
 })
 
 // const categories = ref([
@@ -643,51 +757,166 @@ const getBaseStatistics = () => {
     baseStatistics().then(res => {
     knowledgeStats.value = res.result
     tabs.value[1].count = res.result.totalDocuments
-    console.log('res :>> ', res);
   })
 }
 getBaseStatistics()
 
 
 const changeTab = (tab)=>{
+  searchQuery.value = ''
+  categoryId.value = ''
   activeTab.value = tab 
-  if(tab =='qa'){
-    getChatMessages()
+  searchData()
+}
+
+const searchData = () => {
+  switch (activeTab.value) {
+    case 'categories':
+      getKnowledgeDatasets()
+      break
+    case 'all':
+      getCategoryList()
+      break
+    case 'qa':
+      getChatMessages()
+      break
+    case 'training':
+      getTraining()
+      break
+    default:
+      return '未知状态'
   }
 }
+
+
 const categories = ref([])
+const categoriesTotal = ref()
+const knowledgeParams=ref({
+  pageIndex: 1, 
+  pageSize: 10,
+  keyword:'',
+})
 // 获取知识分类
 const getKnowledgeDatasets = () => {
-    knowledgeDatasets({ pageIndex: 1, pageSize: 10 }).then(res => {
+    knowledgeParams.value.keyword = searchQuery.value
+    knowledgeDatasets(knowledgeParams.value).then(res => {
     categories.value = res.result.data
     res.result.data.forEach(el => {
       el.timeip = formatTimeAgo(el.updated_at)
     });
-    tabs.value[0].count = res.result.total
-    console.log('res :>> ', res);
+    tabs.value[0].count = categoriesTotal.value =  res.result.total
   })
 }
 getKnowledgeDatasets()
+const knowledgeCurrentChange = (page) => {
+    knowledgeParams.value.pageIndex = page
+    getKnowledgeDatasets()
+}
+
+const changeCategory = () => {
+  getCategoryList(categoryId.value)
+}
 
 const knowledgeList = ref([])
-// 获取知识分类下的知识
-const documentsParams = ref({})
+const documentList = ref([])
+// 获取知识列表
+const categoryId = ref()
+const categoryTotal = ref()
+const documentsParams = ref({
+  pageIndex:1,
+  pageSize:10,
+  // dataset_id:'',
+  keyword:''
+})
 const openCategory = (category) => {
   activeTab.value = 'all'
-  searchQuery.value = category.name
   showToastMessage(`正在加载 ${category.name} 分类...`, 'success')
-  documentsParams.value = {
-    pageIndex:1,
-    pageSize:10,
-    dataset_id:category.id
+  getCategoryList(category.id)
+}
+const getCategoryList  = (id,type) => {
+  if(!type){
+    categoryId.value = id?id:''
+    documentsParams.value.dataset_id = id?id:''
+    documentsParams.value.keyword = searchQuery.value
+    documentsParams.value.pageSize = 10
+  }else{
+    documentsParams.value.pageSize = 9999
+  }
+  getAllDocumnets(documentsParams.value).then(res => {
+    console.log(res)
+    if(type=='add'){ //添加知识获取分类
+      documentList.value = res.result.data
+    }else{
+      knowledgeList.value = res.result.data
+      tabs.value[1].count = categoryTotal.value = res.result.total
+    }
+  })
+}
+getCategoryList()
+const categoryPageCurrentChange = (page) => {
+    documentsParams.value.pageIndex = page
+    getCategoryList(categoryId.value)
+}
+
+// 删除知识文档
+const deleteKnowledge = (item) => {
+  let params = {
+    dataset_id:item.dataset_id,
+    document_id:item.id,
+  }
+  delDocuments(params).then(res => {
+    if(res.success){
+      showToastMessage('知识文档已删除', 'success')
+      getCategoryList()
+    }else{
+      ElMessage.error(res.message)
+    }
+  })
+}
+// 获取知识列表下的知识块
+const chunkList = ref([])
+const chunksTotal = ref()
+const chunkParams=ref({
+  pageIndex: 1, 
+  pageSize: 10,
+  dataset_id:'',
+  document_id:'',
+})
+// 获取知识分类
+const getChunkDatasets = () => {
+    chunkLoading.value = true
+    getAllChunks(chunkParams.value).then(res => {
+      chunkLoading.value = false
+      chunkList.value = res.result.data
+      res.result.data.forEach(el => {
+        el.timeip = formatTimeAgo(el.updated_at)
+      });
+      chunksTotal.value =  res.result.total
+    })
+}
+const chunkCurrentChange = (page) => {
+    chunkParams.value.pageIndex = page
+    getChunkDatasets()
+}
+// 删除知识块
+const deletechunk = (item) => {
+  let params = {
+    dataset_id:item.dataset_id,
+    document_id:item.document_id,
+    segment_id:item.id,
   }
-  knowledgeDocuments(documentsParams.value).then(res => {
-    knowledgeList.value = res.result.data
-    tabs.value[1].count = res.result.total
+  delChunk(params).then(res => {
+    if(res.success){
+      showToastMessage('知识块已删除', 'success')
+      getChunkDatasets()
+    }else{
+      ElMessage.error(res.message)
+    }
   })
 }
 
 const qaRecords = ref([])
+const qaRecordsTotal = ref()
 // 获取问答记录
 const chatParams=ref({
   pageIndex: 1, 
@@ -695,18 +924,24 @@ const chatParams=ref({
   keyword:'',
 })
 const getChatMessages = () => {
-    chatMessages(chatParams.value).then(res => {
-    tabs.value[2].count = res.result.total
+  chatParams.value.keyword = searchQuery.value
+  chatMessages(chatParams.value).then(res => {
+    tabs.value[2].count = qaRecordsTotal.value = res.result.total
     res.result.records.forEach(el => {
       el.time = formatTimeAgo(el.createdAt)
     });
     qaRecords.value = res.result.records
-    console.log('res :>> ', res);
   })
 }
 getChatMessages()
+const chatPageCurrentChange = (page) => {
+    chatParams.value.pageIndex = page
+    getChatMessages()
+}
+
 
 const trainingTasks = ref([])
+const trainingTasksTotal = ref()
 //获取ai训练列表
 const trainingParams=ref({
   pageIndex: 1, 
@@ -714,18 +949,29 @@ const trainingParams=ref({
   keyword:'',
 })
 const getTraining = () => {
+    trainingParams.value.keyword = searchQuery.value
     baseTraining(trainingParams.value).then(res => {
-    tabs.value[3].count = res.result.total
+    tabs.value[3].count = trainingTasksTotal.value = res.result.total
     trainingTasks.value = res.result.records
-    console.log('res :>> ', res);
   })
 }
 getTraining()
+const tasksPageCurrentChange = (page) => {
+    trainingParams.value.pageIndex = page
+    getTraining()
+}
 
+const uploadRef = ref(null)
 const fileListRaw = ref([])
+const removeUidSet = new Set()
 const handleFileChange = (file, fileList) => {
   // 提取所有原生File对象
-  fileListRaw.value = fileList.map(item => item.raw)
+  console.log(fileList)
+  // fileListRaw.value = fileList.map(item => item.raw)
+  const exist = fileListRaw.value.some(f => f.uid === file.uid)
+  if (!exist) {
+    fileListRaw.value.push(file)
+  }
   console.log('当前选中所有文件:', fileListRaw.value)
 }
 const trainKnowledge = ref({
@@ -734,8 +980,11 @@ const trainKnowledge = ref({
   description: '',
 })
 const startTrain = async () => {
+  if(trainKnowledge.value.datasetId==''||trainKnowledge.value.name==''||trainKnowledge.value.description==''){
+    return ElMessage.warning('请填写必填项')
+  }
   if (fileListRaw.value.length === 0) {
-    return ElMessage.warning('请先选择文件')
+    return ElMessage.warning('请选择文件')
   }
   const formData = new FormData()
   const jsonBody = {
@@ -746,20 +995,48 @@ const startTrain = async () => {
   formData.append('data', JSON.stringify(jsonBody))
   
   fileListRaw.value.forEach(file => {
-    formData.append('file', file)
+    formData.append('file', file.raw)
   })
 
   try {
     await creatTraining(formData)
-    ElMessage.success('全部文件上传成功')
+    ElMessage.success('添加成功')
     // 清空上传列表、缓存文件
     uploadRef.value.clearFiles()
     fileListRaw.value = []
+    trainKnowledge.value = {
+      datasetId: '',
+      name: '',
+      description: '',
+    }
+    showTrainModal.value = false
+    getTraining()
   } catch (err) {
-    ElMessage.error(err?.message || '上传失败,请重试')
+    ElMessage.error(err?.message || '上传失败,请重试')
   }
 }
 
+const delFile = (targetItem) => {
+  fileListRaw.value = fileListRaw.value.filter(item => item.uid !== targetItem.uid)
+}
+
+const documentName = ref()
+const showDocumentDetail = (item) => {
+  selectedDocument.value = item
+  showChunksModal.value = true
+  documentName.value = item.name
+  chunkParams.value.dataset_id = item.dataset_id
+  chunkParams.value.document_id = item.id
+  getChunkDatasets()
+}
+
+const showKnowledgeDetail = (item) => {
+  selectedKnowledge.value = item
+  showDetailModal.value = true
+}
+
+
+
 // const filteredCategories = computed(() => {
 //   if (!searchQuery.value) return categories.value
 //   return categories.value.filter(c => c.name.includes(searchQuery.value) || c.description.includes(searchQuery.value))
@@ -790,37 +1067,54 @@ const formatNumber = (num) => num.toLocaleString()
 
 
 
-const showKnowledgeDetail = (item) => {
-  selectedKnowledge.value = item
-  showDetailModal.value = true
-}
-
 const editKnowledge = (item) => {
-  showDetailModal.value = false
-  showToastMessage('正在编辑知识...', 'success')
+  chunkType.value = 'edit'
+  newKnowledge.value.content = item.content
+  newKnowledge.value.answer=item.answer
+  newKnowledge.value.dataset_id=item.dataset_id
+  newKnowledge.value.segment_id=item.id
+  getCategoryList(item.dataset_id,'add')
+  newKnowledge.value.document_id=item.document_id
+  showAddModal.value = true
+}
+const editDocument = (item) => {
+  
 }
 
-const deleteKnowledge = (item) => {
-  showToastMessage('知识已删除', 'success')
+const getDocument = () =>{
+  getCategoryList(newKnowledge.value.dataset_id,'add')
 }
 
 const addKnowledgeDocument = ()=> {
-  newKnowledge.value.name = ''
-  newKnowledge.value.text=''
+  newKnowledge.value.content = ''
+  newKnowledge.value.answer=''
   newKnowledge.value.dataset_id=''
+  newKnowledge.value.document_id=''
+  newKnowledge.value.segment_id=''
+  chunkType.value = 'add'
   showAddModal.value = true
 }
 const addKnowledge = () => {
-  if(!newKnowledge.value.name||!newKnowledge.value.text||!newKnowledge.value.dataset_id){
+  if(!newKnowledge.value.answer||!newKnowledge.value.content||!newKnowledge.value.dataset_id||!newKnowledge.value.document_id){
     ElMessage.error('请填入必填项')
     return false
   }
-  createKnowledge(newKnowledge.value).then(res => {
-    if(res.success){
-      howToastMessage('知识添加成功,AI 已学习', 'success')
-      showAddModal.value = false
-    }
-  })
+  if(chunkType.value =='add'){
+    createChunk(newKnowledge.value).then(res => {
+      if(res.success){
+        howToastMessage('知识添加成功,AI 已学习', 'success')
+        showAddModal.value = false
+      }
+    })
+  }else{
+    editChunk(newKnowledge.value).then(res => {
+      if(res.success){
+        howToastMessage('知识编辑成功,AI 已学习', 'success')
+        showAddModal.value = false
+      }
+    })
+  }
+  
 }
 
 const optimizeAnswer = (item) => {
@@ -929,7 +1223,7 @@ const showToastMessage = (message, type = 'success') => {
 .knowledge-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0; }
 .knowledge-content { flex: 1; }
 .knowledge-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
-.knowledge-question { font-size: 14px; font-weight: 600; }
+.knowledge-question { font-size: 14px; font-weight: 600; word-break: break-all;}
 .knowledge-category { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
 .knowledge-answer { font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; line-height: 1.5; }
 .knowledge-meta { display: flex; gap: 16px; font-size: 11px; color: var(--text-muted); }