From a6dd0c868784dbebe881a463b7962b94f952915a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 08 六月 2026 16:00:54 +0800
Subject: [PATCH] feat(knowledgeBase): 添加知识库文件管理和问答功能

---
 src/views/collaborativeApproval/knowledgeBase/index.vue | 1004 ++++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 762 insertions(+), 242 deletions(-)

diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue
index 43fee33..669b260 100644
--- a/src/views/collaborativeApproval/knowledgeBase/index.vue
+++ b/src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -1,242 +1,409 @@
 <template>
   <div class="app-container">
-    <div class="search_form" style="margin-bottom: 20px;">
-      <div>
-        <span class="search_title">鐭ヨ瘑鏍囬锛�</span>
-        <el-input
-          v-model="searchForm.title"
-          style="width: 240px"
-          placeholder="璇疯緭鍏ョ煡璇嗘爣棰樻悳绱�"
-          @change="handleQuery"
-          clearable
-          :prefix-icon="Search"
-        />
-        <span class="search_title ml10">鐭ヨ瘑绫诲瀷锛�</span>
-        <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px">
-          <el-option
-              v-for="item in knowledgeTypeOptions"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-          />
-        </el-select>
-        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
-          鎼滅储
-        </el-button>
-      </div>
-      <div>
-        <el-button @click="handleExport" style="margin-right: 10px">瀵煎嚭</el-button>
-        <el-button type="primary" @click="openForm('add')">鏂板鐭ヨ瘑</el-button>
-        <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
-      </div>
-    </div>
-
-    <div class="table_list">
-      <PIMTable
-        rowKey="id"
-        :column="tableColumn"
-        :tableData="tableData"
-        :page="page"
-        :isSelection="true"
-        @selection-change="handleSelectionChange"
-        :tableLoading="tableLoading"
-        @pagination="pagination"
-        :total="page.total"
-      ></PIMTable>
-    </div>
-
-    <!-- 鏂板/缂栬緫鐭ヨ瘑寮圭獥 -->
-    <FormDialog
-      v-model="dialogVisible"
-      :title="dialogTitle"
-      :width="'800px'"
-      @close="closeKnowledgeDialog"
-      @confirm="submitForm"
-      @cancel="closeKnowledgeDialog"
-    >
-      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="鐭ヨ瘑鏍囬" prop="title">
-              <el-input v-model="form.title" placeholder="璇疯緭鍏ョ煡璇嗘爣棰�" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="鐭ヨ瘑绫诲瀷" prop="type">
-              <el-select v-model="form.type" placeholder="璇烽�夋嫨鐭ヨ瘑绫诲瀷" style="width: 100%">
-                <el-option
-                    v-for="item in knowledgeTypeOptions"
-                    :key="item.value"
-                    :label="item.label"
-                    :value="item.value"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="閫傜敤鍦烘櫙" prop="scenario">
-              <el-input v-model="form.scenario" placeholder="璇疯緭鍏ラ�傜敤鍦烘櫙" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="瑙e喅鏁堢巼" prop="efficiency">
-              <el-select v-model="form.efficiency" placeholder="璇烽�夋嫨瑙e喅鏁堢巼" style="width: 100%">
-                <el-option label="鏄捐憲鎻愬崌" value="high" />
-                <el-option label="涓�鑸彁鍗�" value="medium" />
-                <el-option label="杞诲井鎻愬崌" value="low" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-form-item label="闂鎻忚堪" prop="problem">
-          <el-input
-            v-model="form.problem"
-            type="textarea"
-            :rows="3"
-            placeholder="璇锋弿杩伴亣鍒扮殑闂"
-          />
-        </el-form-item>
-        <el-form-item label="瑙e喅鏂规" prop="solution">
-          <el-input
-            v-model="form.solution"
-            type="textarea"
-            :rows="4"
-            placeholder="璇疯缁嗘弿杩拌В鍐虫柟妗�"
-          />
-        </el-form-item>
-        <el-form-item label="鍏抽敭瑕佺偣" prop="keyPoints">
-          <el-input
-            v-model="form.keyPoints"
-            type="textarea"
-            :rows="3"
-            placeholder="璇疯緭鍏ュ叧閿鐐癸紝鐢ㄩ�楀彿鍒嗛殧"
-          />
-        </el-form-item>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="鍒涘缓浜�" prop="creator">
-              <el-select v-model="form.creator" placeholder="璇烽�夋嫨鍒涘缓浜�" style="width: 100%" filterable>
-                <el-option
-                  v-for="user in userList"
-                  :key="user.userId"
-                  :label="user.nickName"
-                  :value="user.nickName"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="浣跨敤娆℃暟" prop="usageCount">
-              <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-    </FormDialog>
-
-    <!-- 鏌ョ湅鐭ヨ瘑璇︽儏寮圭獥 -->
-    <FormDialog
-      v-model="viewDialogVisible"
-      title="鐭ヨ瘑璇︽儏"
-      :width="'900px'"
-      @close="closeViewDialog"
-      @confirm="handleViewDialogConfirm"
-      @cancel="closeViewDialog"
-    >
-      <div class="knowledge-detail">
-        <el-descriptions :column="2" border>
-          <el-descriptions-item label="鐭ヨ瘑鏍囬" :span="2">
-            <span class="detail-title">{{ currentKnowledge.title }}</span>
-          </el-descriptions-item>
-          <el-descriptions-item label="鐭ヨ瘑绫诲瀷">
-            <el-tag :type="getTypeTagType(currentKnowledge.type)">
-              {{ getTypeLabel(currentKnowledge.type) }}
-            </el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="閫傜敤鍦烘櫙">
-            {{ currentKnowledge.scenario }}
-          </el-descriptions-item>
-          <el-descriptions-item label="瑙e喅鏁堢巼">
-            <el-tag :type="getEfficiencyTagType(currentKnowledge.efficiency)">
-              {{ getEfficiencyLabel(currentKnowledge.efficiency) }}
-            </el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="浣跨敤娆℃暟">
-            <el-tag type="info">{{ currentKnowledge.usageCount }} 娆�</el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="鍒涘缓浜�">
-            {{ currentKnowledge.creator }}
-          </el-descriptions-item>
-          <el-descriptions-item label="鍒涘缓鏃堕棿">
-            {{ currentKnowledge.createTime }}
-          </el-descriptions-item>
-        </el-descriptions>
-
-        <div class="detail-section">
-          <h4>闂鎻忚堪</h4>
-          <div class="detail-content">{{ currentKnowledge.problem }}</div>
-        </div>
-
-        <div class="detail-section">
-          <h4>瑙e喅鏂规</h4>
-          <div class="detail-content">{{ currentKnowledge.solution }}</div>
-        </div>
-
-        <div class="detail-section">
-          <h4>鍏抽敭瑕佺偣</h4>
-          <div class="key-points">
-            <el-tag
-              v-for="(point, index) in currentKnowledge.keyPoints?.split(',') || []"
-              :key="index"
-              type="success"
-              style="margin-right: 8px; margin-bottom: 8px;"
-            >
-              {{ point.trim() }}
-            </el-tag>
+    <!-- Tab椤电鍒囨崲 -->
+    <el-tabs v-model="activeTab" class="knowledge-tabs">
+      <!-- 鐭ヨ瘑搴撶鐞員ab -->
+      <el-tab-pane label="鐭ヨ瘑搴撶鐞�" name="manage">
+        <div class="search_form" style="margin-bottom: 20px;">
+          <div>
+            <span class="search_title">鐭ヨ瘑鏍囬锛�</span>
+            <el-input
+              v-model="searchForm.title"
+              style="width: 240px"
+              placeholder="璇疯緭鍏ョ煡璇嗘爣棰樻悳绱�"
+              @change="handleQuery"
+              clearable
+              :prefix-icon="Search"
+            />
+            <span class="search_title ml10">鐭ヨ瘑绫诲瀷锛�</span>
+            <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px">
+              <el-option
+                  v-for="item in knowledgeTypeOptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+              />
+            </el-select>
+            <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
+              鎼滅储
+            </el-button>
+          </div>
+          <div>
+            <el-button @click="handleExport" style="margin-right: 10px">瀵煎嚭</el-button>
+            <el-button type="primary" @click="openForm('add')">鏂板鐭ヨ瘑</el-button>
+            <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
           </div>
         </div>
 
-        <div class="detail-section">
-          <h4>浣跨敤缁熻</h4>
-          <div class="usage-stats">
+        <div class="table_list">
+          <PIMTable
+            rowKey="id"
+            :column="tableColumn"
+            :tableData="tableData"
+            :page="page"
+            :isSelection="true"
+            @selection-change="handleSelectionChange"
+            :tableLoading="tableLoading"
+            @pagination="pagination"
+            :total="page.total"
+          ></PIMTable>
+        </div>
+
+        <!-- 鏂板/缂栬緫鐭ヨ瘑寮圭獥 -->
+        <FormDialog
+          v-model="dialogVisible"
+          :title="dialogTitle"
+          :width="'800px'"
+          @close="closeKnowledgeDialog"
+          @confirm="submitForm"
+          @cancel="closeKnowledgeDialog"
+        >
+          <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
             <el-row :gutter="20">
-              <el-col :span="8">
-                <div class="stat-item">
-                  <div class="stat-number">{{ currentKnowledge.usageCount }}</div>
-                  <div class="stat-label">浣跨敤娆℃暟</div>
-                </div>
+              <el-col :span="12">
+                <el-form-item label="鐭ヨ瘑鏍囬" prop="title">
+                  <el-input v-model="form.title" placeholder="璇疯緭鍏ョ煡璇嗘爣棰�" />
+                </el-form-item>
               </el-col>
-              <el-col :span="8">
-                <div class="stat-item">
-                  <div class="stat-number">{{ getEfficiencyScore(currentKnowledge.efficiency) }}%</div>
-                  <div class="stat-label">鏁堢巼鎻愬崌</div>
-                </div>
-              </el-col>
-              <el-col :span="8">
-                <div class="stat-item">
-                  <div class="stat-number">{{ getTimeSaved(currentKnowledge.efficiency) }}</div>
-                  <div class="stat-label">骞冲潎鑺傜渷鏃堕棿</div>
-                </div>
+              <el-col :span="12">
+                <el-form-item label="鐭ヨ瘑绫诲瀷" prop="type">
+                  <el-select v-model="form.type" placeholder="璇烽�夋嫨鐭ヨ瘑绫诲瀷" style="width: 100%">
+                    <el-option
+                        v-for="item in knowledgeTypeOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                    />
+                  </el-select>
+                </el-form-item>
               </el-col>
             </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="閫傜敤鍦烘櫙" prop="scenario">
+                  <el-input v-model="form.scenario" placeholder="璇疯緭鍏ラ�傜敤鍦烘櫙" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="瑙e喅鏁堢巼" prop="efficiency">
+                  <el-select v-model="form.efficiency" placeholder="璇烽�夋嫨瑙e喅鏁堢巼" style="width: 100%">
+                    <el-option label="鏄捐憲鎻愬崌" value="high" />
+                    <el-option label="涓�鑸彁鍗�" value="medium" />
+                    <el-option label="杞诲井鎻愬崌" value="low" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="闂鎻忚堪" prop="problem">
+              <el-input
+                v-model="form.problem"
+                type="textarea"
+                :rows="3"
+                placeholder="璇锋弿杩伴亣鍒扮殑闂"
+              />
+            </el-form-item>
+            <el-form-item label="瑙e喅鏂规" prop="solution">
+              <el-input
+                v-model="form.solution"
+                type="textarea"
+                :rows="4"
+                placeholder="璇疯缁嗘弿杩拌В鍐虫柟妗�"
+              />
+            </el-form-item>
+            <el-form-item label="鍏抽敭瑕佺偣" prop="keyPoints">
+              <el-input
+                v-model="form.keyPoints"
+                type="textarea"
+                :rows="3"
+                placeholder="璇疯緭鍏ュ叧閿鐐癸紝鐢ㄩ�楀彿鍒嗛殧"
+              />
+            </el-form-item>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="鍒涘缓浜�" prop="creator">
+                  <el-select v-model="form.creator" placeholder="璇烽�夋嫨鍒涘缓浜�" style="width: 100%" filterable>
+                    <el-option
+                      v-for="user in userList"
+                      :key="user.userId"
+                      :label="user.nickName"
+                      :value="user.nickName"
+                    />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="浣跨敤娆℃暟" prop="usageCount">
+                  <el-input-number v-model="form.usageCount" :min="0" style="width: 100%" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </FormDialog>
+
+        <!-- 鏌ョ湅鐭ヨ瘑璇︽儏寮圭獥 -->
+        <FormDialog
+          v-model="viewDialogVisible"
+          title="鐭ヨ瘑璇︽儏"
+          :width="'900px'"
+          @close="closeViewDialog"
+          @confirm="handleViewDialogConfirm"
+          @cancel="closeViewDialog"
+        >
+          <div class="knowledge-detail">
+            <el-descriptions :column="2" border>
+              <el-descriptions-item label="鐭ヨ瘑鏍囬" :span="2">
+                <span class="detail-title">{{ currentKnowledge.title }}</span>
+              </el-descriptions-item>
+              <el-descriptions-item label="鐭ヨ瘑绫诲瀷">
+                <el-tag :type="getTypeTagType(currentKnowledge.type)">
+                  {{ getTypeLabel(currentKnowledge.type) }}
+                </el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="閫傜敤鍦烘櫙">
+                {{ currentKnowledge.scenario }}
+              </el-descriptions-item>
+              <el-descriptions-item label="瑙e喅鏁堢巼">
+                <el-tag :type="getEfficiencyTagType(currentKnowledge.efficiency)">
+                  {{ getEfficiencyLabel(currentKnowledge.efficiency) }}
+                </el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="浣跨敤娆℃暟">
+                <el-tag type="info">{{ currentKnowledge.usageCount }} 娆�</el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="鍒涘缓浜�">
+                {{ currentKnowledge.creator }}
+              </el-descriptions-item>
+              <el-descriptions-item label="鍒涘缓鏃堕棿">
+                {{ currentKnowledge.createTime }}
+              </el-descriptions-item>
+            </el-descriptions>
+
+            <div class="detail-section">
+              <h4>闂鎻忚堪</h4>
+              <div class="detail-content">{{ currentKnowledge.problem }}</div>
+            </div>
+
+            <div class="detail-section">
+              <h4>瑙e喅鏂规</h4>
+              <div class="detail-content">{{ currentKnowledge.solution }}</div>
+            </div>
+
+            <div class="detail-section">
+              <h4>鍏抽敭瑕佺偣</h4>
+              <div class="key-points">
+                <el-tag
+                  v-for="(point, index) in currentKnowledge.keyPoints?.split(',') || []"
+                  :key="index"
+                  type="success"
+                  style="margin-right: 8px; margin-bottom: 8px;"
+                >
+                  {{ point.trim() }}
+                </el-tag>
+              </div>
+            </div>
+
+            <div class="detail-section">
+              <h4>浣跨敤缁熻</h4>
+              <div class="usage-stats">
+                <el-row :gutter="20">
+                  <el-col :span="8">
+                    <div class="stat-item">
+                      <div class="stat-number">{{ currentKnowledge.usageCount }}</div>
+                      <div class="stat-label">浣跨敤娆℃暟</div>
+                    </div>
+                  </el-col>
+                  <el-col :span="8">
+                    <div class="stat-item">
+                      <div class="stat-number">{{ getEfficiencyScore(currentKnowledge.efficiency) }}%</div>
+                      <div class="stat-label">鏁堢巼鎻愬崌</div>
+                    </div>
+                  </el-col>
+                  <el-col :span="8">
+                    <div class="stat-item">
+                      <div class="stat-number">{{ getTimeSaved(currentKnowledge.efficiency) }}</div>
+                      <div class="stat-label">骞冲潎鑺傜渷鏃堕棿</div>
+                    </div>
+                  </el-col>
+                </el-row>
+              </div>
+            </div>
           </div>
+        </FormDialog>
+      </el-tab-pane>
+
+      <!-- 鏂囦欢绠$悊Tab -->
+      <el-tab-pane label="鏂囦欢绠$悊" name="files">
+        <div class="file-manage-container">
+          <!-- 鐭ヨ瘑搴撻�夋嫨 -->
+          <div class="kb-selector" style="margin-bottom: 20px;">
+            <span class="search_title">閫夋嫨鐭ヨ瘑搴擄細</span>
+            <el-select v-model="selectedKnowledgeBaseId" placeholder="璇烽�夋嫨鐭ヨ瘑搴�" style="width: 300px" @change="handleKnowledgeBaseChange">
+              <el-option
+                v-for="kb in tableData"
+                :key="kb.id"
+                :label="kb.title"
+                :value="kb.id"
+              />
+            </el-select>
+            <el-button type="primary" style="margin-left: 10px" @click="refreshFileList" :disabled="!selectedKnowledgeBaseId">
+              鍒锋柊鐘舵��
+            </el-button>
+          </div>
+
+          <!-- 鏂囦欢涓婁紶鍖哄煙 -->
+          <div v-if="selectedKnowledgeBaseId" class="upload-section">
+            <el-upload
+              :action="uploadUrl"
+              :headers="uploadHeaders"
+              :on-success="handleUploadSuccess"
+              :before-upload="beforeUpload"
+              :accept="acceptTypes"
+              :file-list="uploadFileList"
+              :show-file-list="false"
+              name="files"
+              multiple
+            >
+              <el-button type="primary">
+                <el-icon><Upload /></el-icon>
+                涓婁紶鏂囦欢
+              </el-button>
+            </el-upload>
+            <div class="upload-tip">
+              鏀寔 docx銆亁lsx銆亁ls銆乸df銆乼xt銆乵d銆乯son銆乧sv 绛夋牸寮忥紝鍗曟枃浠朵笉瓒呰繃10MB
+            </div>
+          </div>
+
+          <!-- 鏂囦欢鍒楄〃 -->
+          <div v-if="selectedKnowledgeBaseId" class="file-list-section">
+            <el-table :data="fileList" v-loading="fileLoading" border>
+              <el-table-column prop="name" label="鏂囦欢鍚�" min-width="200" show-overflow-tooltip />
+              <el-table-column prop="fileType" label="绫诲瀷" width="80">
+                <template #default="{ row }">
+                  {{ getFileType(row.name) }}
+                </template>
+              </el-table-column>
+              <el-table-column prop="vectorStatus" label="鍚戦噺鍖栫姸鎬�" width="120">
+                <template #default="{ row }">
+                  <el-tag :type="getVectorStatusType(row.vectorStatus)">
+                    {{ getVectorStatusText(row.vectorStatus) }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column prop="chunkCount" label="鍒囩墖鏁�" width="80" align="center">
+                <template #default="{ row }">
+                  {{ row.chunkCount || 0 }}
+                </template>
+              </el-table-column>
+              <el-table-column prop="createTime" label="涓婁紶鏃堕棿" width="160" />
+              <el-table-column label="鎿嶄綔" width="200" fixed="right">
+                <template #default="{ row }">
+                  <el-button type="primary" size="small" link @click="previewFile(row)">棰勮</el-button>
+                  <el-button type="primary" size="small" link @click="downloadFile(row)">涓嬭浇</el-button>
+                  <el-button
+                    v-if="row.vectorStatus === 3"
+                    type="warning"
+                    size="small"
+                    link
+                    @click="revectorFile(row)"
+                  >閲嶆柊澶勭悊</el-button>
+                  <el-button type="danger" size="small" link @click="deleteFile(row)">鍒犻櫎</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+
+          <!-- 鏈�夋嫨鐭ヨ瘑搴撴彁绀� -->
+          <el-empty v-if="!selectedKnowledgeBaseId" description="璇峰厛閫夋嫨鐭ヨ瘑搴�" />
         </div>
-      </div>
-    </FormDialog>
+      </el-tab-pane>
+
+      <!-- 鐭ヨ瘑闂瓟Tab -->
+      <el-tab-pane label="鐭ヨ瘑闂瓟" name="chat">
+        <div class="knowledge-chat-container">
+          <!-- 鐭ヨ瘑搴撻�夋嫨 -->
+          <div class="kb-selector">
+            <span class="search_title">閫夋嫨鐭ヨ瘑搴擄細</span>
+            <el-select v-model="chatKnowledgeBaseId" placeholder="璇烽�夋嫨鐭ヨ瘑搴�" style="width: 300px" @change="handleChatKnowledgeBaseChange">
+              <el-option
+                v-for="kb in tableData"
+                :key="kb.id"
+                :label="kb.title"
+                :value="kb.id"
+              />
+            </el-select>
+            <el-button type="primary" plain style="margin-left: 10px" @click="clearChatHistory" :disabled="!chatKnowledgeBaseId">
+              娓呯┖瀵硅瘽
+            </el-button>
+          </div>
+
+          <!-- 鑱婂ぉ鍖哄煙 -->
+          <div class="chat-container">
+            <div class="message-list" ref="messageListRef">
+              <div v-for="msg in chatMessages" :key="msg.id" :class="['message', msg.role]">
+                <div class="message-role">
+                  <el-avatar v-if="msg.role === 'user'" :size="28" style="background: #409eff;">
+                    <el-icon><User /></el-icon>
+                  </el-avatar>
+                  <el-avatar v-else :size="28" style="background: #67c23a;">
+                    <el-icon><ChatDotRound /></el-icon>
+                  </el-avatar>
+                  <span class="role-text">{{ msg.role === 'user' ? '鎴�' : 'AI鍔╂墜' }}</span>
+                </div>
+                <div class="message-content">{{ msg.content }}</div>
+                <div class="message-time">{{ msg.createTime }}</div>
+              </div>
+              <div v-if="streamingContent" class="message assistant">
+                <div class="message-role">
+                  <el-avatar :size="28" style="background: #67c23a;">
+                    <el-icon><ChatDotRound /></el-icon>
+                  </el-avatar>
+                  <span class="role-text">AI鍔╂墜</span>
+                </div>
+                <div class="message-content">{{ streamingContent }}</div>
+              </div>
+            </div>
+
+            <!-- 杈撳叆鍖哄煙 -->
+            <div class="input-area">
+              <el-input
+                v-model="questionInput"
+                placeholder="杈撳叆闂锛屽熀浜庣煡璇嗗簱鍐呭鍥炵瓟..."
+                @keyup.enter="sendQuestion"
+                :disabled="!chatKnowledgeBaseId || sending"
+              />
+              <el-button type="primary" @click="sendQuestion" :loading="sending" :disabled="!chatKnowledgeBaseId">
+                鍙戦��
+              </el-button>
+            </div>
+          </div>
+
+          <!-- 鏈�夋嫨鐭ヨ瘑搴撴彁绀� -->
+          <el-empty v-if="!chatKnowledgeBaseId" description="璇峰厛閫夋嫨鐭ヨ瘑搴撳紑濮嬮棶绛�" />
+        </div>
+      </el-tab-pane>
+    </el-tabs>
   </div>
 </template>
 
 <script setup>
-import { Search } from "@element-plus/icons-vue";
-import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch } from "vue";
+import { Search, Upload, User, ChatDotRound } from "@element-plus/icons-vue";
+import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch, nextTick } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import PIMTable from "@/components/PIMTable/PIMTable.vue";
 import FormDialog from '@/components/Dialog/FormDialog.vue';
-import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js";
+import {
+  listKnowledgeBase,
+  delKnowledgeBase,
+  addKnowledgeBase,
+  updateKnowledgeBase,
+  getVectorStatus,
+  reprocessVector,
+  knowledgeChat
+} from "@/api/collaborativeApproval/knowledgeBase.js";
+import { attachmentList, createAttachment, deleteAttachment } from "@/api/basicData/storageAttachment.js";
 import useUserStore from '@/store/modules/user';
 import { userListNoPageByTenantId } from '@/api/system/user.js';
+import { getToken } from '@/utils/auth';
 
 // 琛ㄥ崟楠岃瘉瑙勫垯
 const rules = {
@@ -256,6 +423,7 @@
 
 // 鍝嶅簲寮忔暟鎹�
 const data = reactive({
+  activeTab: 'manage',
   searchForm: {
     title: "",
     type: "",
@@ -283,10 +451,23 @@
   dialogTitle: "",
   dialogType: "add",
   viewDialogVisible: false,
-  currentKnowledge: {}
+  currentKnowledge: {},
+  // 鏂囦欢绠$悊鐩稿叧
+  selectedKnowledgeBaseId: null,
+  fileList: [],
+  fileLoading: false,
+  uploadFileList: [],
+  // 鐭ヨ瘑闂瓟鐩稿叧
+  chatKnowledgeBaseId: null,
+  chatMessages: [],
+  questionInput: '',
+  sending: false,
+  streamingContent: '',
+  memoryId: ''
 });
 
 const {
+  activeTab,
   searchForm,
   tableLoading,
   page,
@@ -297,14 +478,31 @@
   dialogTitle,
   dialogType,
   viewDialogVisible,
-  currentKnowledge
+  currentKnowledge,
+  selectedKnowledgeBaseId,
+  fileList,
+  fileLoading,
+  uploadFileList,
+  chatKnowledgeBaseId,
+  chatMessages,
+  questionInput,
+  sending,
+  streamingContent,
+  memoryId
 } = toRefs(data);
 
 // 琛ㄥ崟寮曠敤
 const formRef = ref();
+const messageListRef = ref();
 // 鐢ㄦ埛鐩稿叧
 const userStore = useUserStore();
 const userList = ref([]);
+
+// 鏂囦欢涓婁紶鐩稿叧
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload';
+const uploadHeaders = { Authorization: 'Bearer ' + getToken() };
+const acceptTypes = '.docx,.xlsx,.xls,.pdf,.txt,.md,.json,.csv';
+const uploadedBlobs = ref([]);
 
 // 琛ㄦ牸鍒楅厤缃�
 const tableColumn = ref([
@@ -411,7 +609,7 @@
 const startAutoRefresh = () => {
   setInterval(() => {
     getList();
-  }, 600000); // 10鍒嗛挓鍒锋柊涓�娆� (10 * 60 * 1000 = 600000ms)
+  }, 600000);
 };
 
 // 鏌ヨ鏁版嵁
@@ -426,11 +624,9 @@
   .then(res => {
     tableLoading.value = false;
     page.value.total = res.data.total;
-    // 濡傛灉褰撳墠椤垫暟瓒呰繃鎬婚〉鏁帮紝閲嶇疆鍒扮1椤靛苟閲嶆柊鏌ヨ
     const maxPage = Math.ceil(res.data.total / page.value.size) || 1;
     if (page.value.current > maxPage && maxPage > 0) {
       page.value.current = 1;
-      // 閲嶆柊鏌ヨ绗�1椤垫暟鎹�
       return getList();
     }
     tableData.value = res.data.records;
@@ -444,7 +640,6 @@
   const oldSize = page.value.size;
   page.value.current = obj.page;
   page.value.size = obj.limit;
-  // 濡傛灉 size 鏀瑰彉浜嗭紝閲嶇疆鍒扮1椤碉紝閬垮厤褰撳墠椤佃秴鍑鸿寖鍥�
   if (oldSize !== obj.limit) {
     page.value.current = 1;
   }
@@ -461,7 +656,6 @@
   dialogType.value = type;
   if (type === "add") {
     dialogTitle.value = "鏂板鐭ヨ瘑";
-    // 閲嶇疆琛ㄥ崟锛岄粯璁ゅ垱寤轰汉涓哄綋鍓嶇敤鎴�
     Object.assign(form.value, {
       title: "",
       type: "",
@@ -566,7 +760,6 @@
     鍒涘缓浜猴細${currentKnowledge.value.creator}
   `.trim();
 
-  // 澶嶅埗鍒板壀璐存澘
   navigator.clipboard.writeText(knowledgeText).then(() => {
     ElMessage.success("鐭ヨ瘑鍐呭宸插鍒跺埌鍓创鏉�");
   }).catch(() => {
@@ -576,7 +769,6 @@
 
 // 鍏抽棴鐭ヨ瘑琛ㄥ崟瀵硅瘽妗�
 const closeKnowledgeDialog = () => {
-  // 娓呯┖琛ㄥ崟鏁版嵁锛岄粯璁ゅ垱寤轰汉涓哄綋鍓嶇敤鎴�
   Object.assign(form.value, {
     id: undefined,
     title: "",
@@ -589,7 +781,6 @@
     creator: userStore.nickName || "",
     usageCount: 0
   });
-  // 娓呴櫎琛ㄥ崟楠岃瘉鐘舵��
   if (formRef.value) {
     formRef.value.clearValidate();
   }
@@ -601,7 +792,7 @@
   viewDialogVisible.value = false;
 };
 
-// 澶勭悊鏌ョ湅璇︽儏瀵硅瘽妗嗙‘璁わ紙鎵ц澶嶅埗鎿嶄綔锛�
+// 澶勭悊鏌ョ湅璇︽儏瀵硅瘽妗嗙‘璁�
 const handleViewDialogConfirm = () => {
   copyKnowledge();
   closeViewDialog();
@@ -612,7 +803,6 @@
   try {
     await formRef.value.validate();
     if (dialogType.value === "add") {
-      // 鏂板鐭ヨ瘑
       addKnowledgeBase({...form.value}).then(res => {
         if(res.code == 200){
           ElMessage.success("娣诲姞鎴愬姛");
@@ -650,7 +840,6 @@
     cancelButtonText: "鍙栨秷",
     type: "warning",
   }).then(() => {
-    // console.log(selectedIds.value);
     delKnowledgeBase(selectedIds.value).then(res => {
       if(res.code == 200){
         ElMessage.success("鍒犻櫎鎴愬姛");
@@ -658,16 +847,13 @@
         getList();
       }
     })
-  }).catch(() => {
-    // 鐢ㄦ埛鍙栨秷
-  });
+  }).catch(() => {});
 };
 
 // 瀵煎嚭
 const { proxy } = getCurrentInstance()
 const { knowledge_type } = proxy.useDict("knowledge_type")
 
-// 瀛楀吀宸ュ叿
 const knowledgeTypeOptions = computed(() => knowledge_type?.value || [])
 const getKnowledgeTypeLabel = (val) => {
   const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val))
@@ -680,21 +866,355 @@
 const handleExport = () => {
   proxy.download('/knowledgeBase/export', { ...searchForm.value }, '鐭ヨ瘑搴�.xlsx')
 }
+
+// ========== 鏂囦欢绠$悊鐩稿叧鏂规硶 ==========
+
+// 鐭ヨ瘑搴撻�夋嫨鍙樺寲
+const handleKnowledgeBaseChange = () => {
+  refreshFileList();
+};
+
+// 鍒锋柊鏂囦欢鍒楄〃
+const refreshFileList = async () => {
+  if (!selectedKnowledgeBaseId.value) return;
+
+  fileLoading.value = true;
+  try {
+    // 鑾峰彇闄勪欢鍒楄〃
+    const attachmentRes = await attachmentList({
+      recordType: 'knowledge_base',
+      recordId: selectedKnowledgeBaseId.value,
+      application: 'rag_file'
+    });
+
+    // 鑾峰彇鍚戦噺鍖栫姸鎬�
+    const vectorRes = await getVectorStatus(selectedKnowledgeBaseId.value);
+
+    // 鍚堝苟鏁版嵁
+    const vectorMap = {};
+    (vectorRes.data || []).forEach(v => {
+      vectorMap[v.storageBlobId] = v;
+    });
+
+    fileList.value = (attachmentRes.data || []).map(file => ({
+      ...file,
+      vectorStatus: vectorMap[file.storageBlobId]?.vectorStatus ?? 0,
+      chunkCount: vectorMap[file.storageBlobId]?.chunkCount ?? 0,
+      vectorId: vectorMap[file.storageBlobId]?.id
+    }));
+  } catch (error) {
+    console.error('鑾峰彇鏂囦欢鍒楄〃澶辫触:', error);
+    ElMessage.error('鑾峰彇鏂囦欢鍒楄〃澶辫触');
+  } finally {
+    fileLoading.value = false;
+  }
+};
+
+// 涓婁紶鍓嶆牎楠�
+const beforeUpload = (file) => {
+  const maxSize = 10 * 1024 * 1024;
+  if (file.size > maxSize) {
+    ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃10MB');
+    return false;
+  }
+  return true;
+};
+
+// 涓婁紶鎴愬姛澶勭悊
+const handleUploadSuccess = async (response, file) => {
+  if (response.code === 200) {
+    uploadedBlobs.value.push(...response.data);
+    await saveAttachment();
+    ElMessage.success('鏂囦欢涓婁紶鎴愬姛锛屾鍦ㄥ鐞嗗悜閲忓寲...');
+    uploadedBlobs.value = [];
+    refreshFileList();
+  } else {
+    ElMessage.error(response.msg || '涓婁紶澶辫触');
+  }
+};
+
+// 淇濆瓨闄勪欢鍏宠仈鍒扮煡璇嗗簱
+const saveAttachment = async () => {
+  await createAttachment({
+    recordType: 'knowledge_base',
+    recordId: selectedKnowledgeBaseId.value,
+    application: 'rag_file',
+    storageBlobDTOs: uploadedBlobs.value.map(b => b.id)
+  });
+};
+
+// 鑾峰彇鏂囦欢绫诲瀷
+const getFileType = (name) => {
+  return name?.split('.').pop()?.toLowerCase() || '';
+};
+
+// 鍚戦噺鍖栫姸鎬佹枃鏈�
+const getVectorStatusText = (status) => {
+  const statusMap = { 0: '寰呭鐞�', 1: '澶勭悊涓�', 2: '宸插畬鎴�', 3: '澶辫触' };
+  return statusMap[status] || '鏈煡';
+};
+
+// 鍚戦噺鍖栫姸鎬佹爣绛剧被鍨�
+const getVectorStatusType = (status) => {
+  const typeMap = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' };
+  return typeMap[status] || 'info';
+};
+
+// 棰勮鏂囦欢
+const previewFile = (row) => {
+  if (row.previewURL) {
+    window.open(row.previewURL, '_blank');
+  } else {
+    ElMessage.warning('鏆傛棤棰勮閾炬帴');
+  }
+};
+
+// 涓嬭浇鏂囦欢
+const downloadFile = (row) => {
+  if (row.downloadURL) {
+    window.open(row.downloadURL, '_blank');
+  } else {
+    ElMessage.warning('鏆傛棤涓嬭浇閾炬帴');
+  }
+};
+
+// 鍒犻櫎鏂囦欢
+const deleteFile = async (row) => {
+  try {
+    await ElMessageBox.confirm('纭畾鍒犻櫎璇ユ枃浠跺悧锛熷垹闄ゅ悗灏嗗悓姝ュ垹闄ゅ悜閲忓簱涓殑鐩稿叧鏁版嵁', '鍒犻櫎纭', {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
+    });
+
+    await deleteAttachment([row.id]);
+    ElMessage.success('鍒犻櫎鎴愬姛');
+    refreshFileList();
+  } catch (error) {
+    if (error !== 'cancel') {
+      ElMessage.error('鍒犻櫎澶辫触');
+    }
+  }
+};
+
+// 閲嶆柊鍚戦噺鍖栨枃浠�
+const revectorFile = async (row) => {
+  try {
+    await reprocessVector(row.vectorId);
+    ElMessage.success('宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟');
+    refreshFileList();
+  } catch (error) {
+    ElMessage.error('閲嶆柊澶勭悊澶辫触');
+  }
+};
+
+// ========== 鐭ヨ瘑闂瓟鐩稿叧鏂规硶 ==========
+
+// 鐭ヨ瘑搴撻�夋嫨鍙樺寲
+const handleChatKnowledgeBaseChange = () => {
+  // 閲嶇疆浼氳瘽
+  memoryId.value = 'kb-chat-' + Date.now();
+  chatMessages.value = [];
+  questionInput.value = '';
+  streamingContent.value = '';
+};
+
+// 娓呯┖瀵硅瘽鍘嗗彶
+const clearChatHistory = () => {
+  memoryId.value = 'kb-chat-' + Date.now();
+  chatMessages.value = [];
+  streamingContent.value = '';
+  ElMessage.success('瀵硅瘽宸叉竻绌�');
+};
+
+// 鍙戦�侀棶棰�
+const sendQuestion = async () => {
+  if (!questionInput.value.trim() || !chatKnowledgeBaseId.value || sending.value) return;
+
+  sending.value = true;
+  streamingContent.value = '';
+
+  // 娣诲姞鐢ㄦ埛娑堟伅
+  const userMsg = {
+    id: Date.now(),
+    role: 'user',
+    content: questionInput.value,
+    createTime: new Date().toLocaleString()
+  };
+  chatMessages.value.push(userMsg);
+
+  const currentQuestion = questionInput.value;
+  questionInput.value = '';
+
+  try {
+    const response = await knowledgeChat({
+      knowledgeBaseId: chatKnowledgeBaseId.value,
+      memoryId: memoryId.value,
+      question: currentQuestion
+    });
+
+    if (!response.ok) {
+      throw new Error('璇锋眰澶辫触');
+    }
+
+    // 娴佸紡璇诲彇鍝嶅簲
+    const reader = response.body.getReader();
+    const decoder = new TextDecoder();
+    let accumulatedContent = '';
+
+    while (true) {
+      const { done, value } = await reader.read();
+      if (done) break;
+
+      accumulatedContent += decoder.decode(value, { stream: true });
+      streamingContent.value = accumulatedContent;
+
+      // 婊氬姩鍒板簳閮�
+      scrollToBottom();
+    }
+
+    // 娣诲姞AI鍥炲娑堟伅
+    if (accumulatedContent) {
+      chatMessages.value.push({
+        id: Date.now(),
+        role: 'assistant',
+        content: accumulatedContent,
+        createTime: new Date().toLocaleString()
+      });
+    }
+
+    streamingContent.value = '';
+  } catch (error) {
+    console.error('闂瓟璇锋眰澶辫触:', error);
+    ElMessage.error('闂瓟璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯');
+    // 绉婚櫎鐢ㄦ埛娑堟伅
+    chatMessages.value.pop();
+  } finally {
+    sending.value = false;
+  }
+};
+
+// 婊氬姩鍒板簳閮�
+const scrollToBottom = () => {
+  nextTick(() => {
+    if (messageListRef.value) {
+      messageListRef.value.scrollTop = messageListRef.value.scrollHeight;
+    }
+  });
+};
 </script>
 
 <style scoped>
-.auto-refresh-info {
-  margin-bottom: 15px;
+.knowledge-tabs {
+  margin-bottom: 20px;
 }
 
-.auto-refresh-info .el-alert {
+/* 鏂囦欢绠$悊鏍峰紡 */
+.file-manage-container {
+  padding: 20px;
+}
+
+.upload-section {
+  margin-bottom: 20px;
+  padding: 20px;
+  background: #f8f9fa;
   border-radius: 8px;
 }
 
-.dialog-footer {
-  text-align: right;
+.upload-tip {
+  margin-top: 10px;
+  color: #909399;
+  font-size: 12px;
 }
 
+.file-list-section {
+  margin-top: 20px;
+}
+
+/* 鐭ヨ瘑闂瓟鏍峰紡 */
+.knowledge-chat-container {
+  height: calc(100vh - 200px);
+  display: flex;
+  flex-direction: column;
+}
+
+.kb-selector {
+  padding: 10px 0;
+  border-bottom: 1px solid #eee;
+}
+
+.chat-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 400px;
+}
+
+.message-list {
+  flex: 1;
+  overflow-y: auto;
+  padding: 20px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin: 10px 0;
+}
+
+.message {
+  margin-bottom: 20px;
+  padding: 16px;
+  border-radius: 12px;
+  background: #fff;
+  box-shadow: 0 1px 2px rgba(0,0,0,0.1);
+}
+
+.message.user {
+  background: #e6f7ff;
+  border-left: 3px solid #409eff;
+}
+
+.message.assistant {
+  background: #f0f9eb;
+  border-left: 3px solid #67c23a;
+}
+
+.message-role {
+  display: flex;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.role-text {
+  margin-left: 8px;
+  font-weight: 500;
+  color: #606266;
+}
+
+.message-content {
+  line-height: 1.6;
+  color: #303133;
+  white-space: pre-wrap;
+}
+
+.message-time {
+  margin-top: 8px;
+  font-size: 12px;
+  color: #909399;
+}
+
+.input-area {
+  padding: 15px;
+  display: flex;
+  gap: 10px;
+  background: #fff;
+  border-radius: 8px;
+  border: 1px solid #eee;
+}
+
+.input-area .el-input {
+  flex: 1;
+}
+
+/* 鐭ヨ瘑璇︽儏鏍峰紡 */
 .knowledge-detail {
   padding: 20px 0;
 }
@@ -755,4 +1275,4 @@
   font-size: 14px;
   color: #909399;
 }
-</style>
+</style>
\ No newline at end of file

--
Gitblit v1.9.3