zhujing hai 1 mes
pai
achega
d6c3b85299

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

@@ -17,4 +17,18 @@ 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 = {}) => get('/router/rest/knowledge/document/getAllDocumnets', params)
+
+export const delDocuments = (params = {}) => {
+    const { dataset_id, document_id, ...query } = params
+    return remove(`/router/rest/knowledge/document/${dataset_id}/document/${document_id}`, params)
+}
+
+export const delChunk = (data = {}) => {
+    const { dataset_id, document_id,segment_id, ...query } = data
+    return  POST(`/router/rest/knowledge/chunk//${dataset_id}/${document_id}/${segment_id}`, data)
+}

+ 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) => {

+ 257 - 40
ui/smarttrade-platform/src/views/KnowledgeView.vue

@@ -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" @input="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">
@@ -189,6 +196,17 @@
           </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 +255,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 +308,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,7 +395,7 @@
       </div>
     </div>
 
-    <!-- Add Knowledge Modal -->
+    <!-- 添加知识弹窗 -->
     <div v-if="showAddModal" class="modal-overlay" @click.self="showAddModal = false">
       <div class="modal add-modal">
         <div class="modal-header">
@@ -393,6 +434,59 @@
       </div>
     </div>
 
+    <!-- 知识文本块列表弹窗 -->
+    <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>
+                <button class="btn btn-secondary btn-sm" @click="deleteKnowledge(item)">
+                  <i class="fas fa-trash"></i>
+                </button>
+              </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>
+
     <!-- Knowledge Detail Modal -->
     <div v-if="showDetailModal && selectedKnowledge" class="modal-overlay" @click.self="showDetailModal = false">
       <div class="modal detail-modal">
@@ -515,6 +609,7 @@
           
           <div class="import-zone">
             <el-upload
+              ref="uploadRef"
               class="upload-demo"
               drag
               action=""
@@ -524,14 +619,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 +650,7 @@
 
 <script setup>
 import { ref, computed } from 'vue'
-import { knowledgeDatasets,knowledgeDocuments,createKnowledge,chatMessages,baseTraining,creatTraining,baseStatistics } from '../api/knowledge'
+import { knowledgeDatasets,knowledgeDocuments,createKnowledge,chatMessages,baseTraining,creatTraining,baseStatistics,getAllChunks,getAllDocumnets,delDocuments,delChunk } from '../api/knowledge'
 import { formatTimeAgo } from '@/utils/time'
 import { ElMessage } from 'element-plus'
 
@@ -563,7 +658,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')
@@ -643,51 +741,126 @@ 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 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
-  }
-  knowledgeDocuments(documentsParams.value).then(res => {
+  getCategoryList(category.id)
+}
+const getCategoryList  = (id) => {
+  categoryId.value = id?id:''
+  documentsParams.value.dataset_id = id?id:''
+  documentsParams.value.keyword = searchQuery.value
+  getAllDocumnets(documentsParams.value).then(res => {
+    console.log(res)
     knowledgeList.value = res.result.data
-    tabs.value[1].count = res.result.total
+    tabs.value[1].count = categoryTotal.value = res.result.total
   })
 }
+getCategoryList()
+const categoryPageCurrentChange = (page) => {
+    documentsParams.value.pageIndex = page
+    getCategoryList(categoryId.value)
+}
+
+// 获取知识列表下的知识块
+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 qaRecords = ref([])
+const qaRecordsTotal = ref()
 // 获取问答记录
 const chatParams=ref({
   pageIndex: 1, 
@@ -695,18 +868,24 @@ const chatParams=ref({
   keyword:'',
 })
 const getChatMessages = () => {
+    chatParams.value.keyword = searchQuery.value
     chatMessages(chatParams.value).then(res => {
-    tabs.value[2].count = res.result.total
+    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 +893,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 +924,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 +939,49 @@ 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
+  console.log(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,11 +1012,6 @@ const formatNumber = (num) => num.toLocaleString()
 
 
 
-const showKnowledgeDetail = (item) => {
-  selectedKnowledge.value = item
-  showDetailModal.value = true
-}
-
 const editKnowledge = (item) => {
   showDetailModal.value = false
   showToastMessage('正在编辑知识...', 'success')
@@ -929,7 +1146,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); }