2026-06-08 a6dd0c868784dbebe881a463b7962b94f952915a
src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -1,238 +1,409 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <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 label="合同特批" :value="'contract'" />
          <el-option label="审批案例" :value="'approval'" />
          <el-option label="解决方案" :value="'solution'" />
          <el-option label="经验总结" :value="'experience'" />
          <el-option label="操作指南" :value="'guide'" />
        </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
          搜索
        </el-button>
      </div>
      <div>
        <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>
    <!-- 新增/编辑知识弹窗 -->
    <el-dialog
      v-model="dialogVisible"
      :title="dialogTitle"
      width="800px"
      :close-on-click-modal="false"
    >
      <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 label="合同特批" value="contract" />
                <el-option label="审批案例" value="approval" />
                <el-option label="解决方案" value="solution" />
                <el-option label="经验总结" value="experience" />
                <el-option label="操作指南" value="guide" />
              </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="解决效率" prop="efficiency">
              <el-select v-model="form.efficiency" placeholder="请选择解决效率" 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="解决方案" 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-input v-model="form.creator" placeholder="请输入创建人" />
            </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>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitForm">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- 查看知识详情弹窗 -->
    <el-dialog
      v-model="viewDialogVisible"
      title="知识详情"
      width="900px"
      :close-on-click-modal="false"
    >
      <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="解决效率">
            <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>解决方案</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">
      <!-- 知识库管理Tab -->
      <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>
          </div>
        </div>
      </div>
            <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="解决效率" prop="efficiency">
                  <el-select v-model="form.efficiency" placeholder="请选择解决效率" 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="解决方案" 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>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="viewDialogVisible = false">关闭</el-button>
          <el-button type="primary" @click="copyKnowledge">复制知识</el-button>
          <el-button type="success" @click="markAsFavorite">收藏</el-button>
        </span>
      </template>
    </el-dialog>
        <!-- 查看知识详情弹窗 -->
        <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="解决效率">
                <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>解决方案</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、xlsx、xls、pdf、txt、md、json、csv 等格式,单文件不超过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>
      </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 } 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,
  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 = {
@@ -252,6 +423,7 @@
// 响应式数据
const data = reactive({
  activeTab: 'manage',
  searchForm: {
    title: "",
    type: "",
@@ -259,7 +431,7 @@
  tableLoading: false,
  page: {
    current: 1,
    size: 100,
    size: 20,
    total: 0,
  },
  tableData: [],
@@ -268,7 +440,7 @@
    title: "",
    type: "",
    scenario: "",
    efficiency: "medium",
    efficiency: "",
    problem: "",
    solution: "",
    keyPoints: "",
@@ -279,25 +451,58 @@
  dialogTitle: "",
  dialogType: "add",
  viewDialogVisible: false,
  currentKnowledge: {}
  currentKnowledge: {},
  // 文件管理相关
  selectedKnowledgeBaseId: null,
  fileList: [],
  fileLoading: false,
  uploadFileList: [],
  // 知识问答相关
  chatKnowledgeBaseId: null,
  chatMessages: [],
  questionInput: '',
  sending: false,
  streamingContent: '',
  memoryId: ''
});
const {
  searchForm,
  tableLoading,
  page,
  tableData,
const {
  activeTab,
  searchForm,
  tableLoading,
  page,
  tableData,
  selectedIds,
  form,
  dialogVisible,
  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([
@@ -311,24 +516,10 @@
    prop: "type",
    dataType: "tag",
    formatData: (params) => {
      const typeMap = {
        contract: "合同特批",
        approval: "审批案例",
        solution: "解决方案",
        experience: "经验总结",
        guide: "操作指南"
      };
      return typeMap[params] || params;
      return getKnowledgeTypeLabel(params);
    },
    formatType: (params) => {
      const typeMap = {
        contract: "success",
        approval: "warning",
        solution: "primary",
        experience: "info",
        guide: "danger"
      };
      return typeMap[params] || "info";
      return getKnowledgeTypeTagType(params);
    }
  },
  {
@@ -389,7 +580,7 @@
        }
      },
      {
        name: "查看",
        name: "详情",
        type: "text",
        clickFun: (row) => {
          viewKnowledge(row);
@@ -399,110 +590,14 @@
  }
]);
// 模拟数据
let mockData = [
  {
    id: "1",
    title: "特殊合同审批流程优化方案",
    type: "contract",
    scenario: "大额合同快速审批",
    efficiency: "high",
    problem: "大额合同审批流程复杂,审批时间长,影响业务进展",
    solution: "建立绿色通道,对符合条件的合同采用简化审批流程,由部门负责人直接审批,平均审批时间从3天缩短至1天",
    keyPoints: "绿色通道条件,简化流程,审批权限,时间控制",
    creator: "张经理",
    usageCount: 15,
    createTime: "2024-01-15 10:30:00"
  },
  {
    id: "2",
    title: "跨部门协作审批经验总结",
    type: "experience",
    scenario: "多部门协作项目",
    efficiency: "medium",
    problem: "跨部门项目审批时,各部门意见不统一,审批进度缓慢",
    solution: "建立项目协调机制,指定项目负责人,定期召开协调会议,统一各方意见后再进行审批",
    keyPoints: "项目协调,定期会议,统一意见,负责人制度",
    creator: "李主管",
    usageCount: 8,
    createTime: "2024-01-14 15:20:00"
  },
  {
    id: "3",
    title: "紧急采购审批操作指南",
    type: "guide",
    scenario: "紧急采购需求",
    efficiency: "high",
    problem: "紧急采购时审批流程复杂,无法满足紧急需求",
    solution: "制定紧急采购审批标准,明确紧急程度分级,不同级别采用不同审批流程,确保紧急需求得到及时处理",
    keyPoints: "紧急分级,标准制定,流程简化,及时处理",
    creator: "王专员",
    usageCount: 12,
    createTime: "2024-01-13 09:15:00"
// 监听对话框打开,获取用户列表
watch(dialogVisible, (newVal) => {
  if (newVal) {
    userListNoPageByTenantId().then((res) => {
      userList.value = res.data || [];
    });
  }
];
// 知识标题模板
const titleTemplates = [
  "{type}审批流程优化方案",
  "{scenario}处理经验总结",
  "{type}特殊情况处理指南",
  "{scenario}快速审批方案",
  "{type}标准化操作流程",
  "{scenario}问题解决方案",
  "{type}最佳实践总结",
  "{scenario}效率提升方案"
];
// 知识类型配置
const knowledgeTypes = [
  { type: "contract", label: "合同特批", efficiency: "high" },
  { type: "approval", label: "审批案例", efficiency: "medium" },
  { type: "solution", label: "解决方案", efficiency: "high" },
  { type: "experience", label: "经验总结", efficiency: "medium" },
  { type: "guide", label: "操作指南", efficiency: "low" }
];
// 场景列表
const scenarios = ["大额合同审批", "跨部门协作", "紧急采购", "特殊申请", "流程优化", "问题处理", "标准化建设", "效率提升"];
// 自动生成新数据
const generateNewData = () => {
  const newId = (mockData.length + 1).toString();
  const now = new Date();
  const randomType = knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)];
  const randomScenario = scenarios[Math.floor(Math.random() * scenarios.length)];
  // 生成随机标题
  let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)];
  title = title
    .replace('{type}', randomType.label)
    .replace('{scenario}', randomScenario);
  const newKnowledge = {
    id: newId,
    title: title,
    type: randomType.type,
    scenario: randomScenario,
    efficiency: randomType.efficiency,
    problem: `在${randomScenario}过程中遇到的问题描述...`,
    solution: `针对${randomScenario}的解决方案和操作步骤...`,
    keyPoints: "关键要点1,关键要点2,关键要点3,关键要点4",
    creator: ["张经理", "李主管", "王专员", "刘总监"][Math.floor(Math.random() * 4)],
    usageCount: Math.floor(Math.random() * 20) + 1,
    createTime: now.toLocaleString()
  };
  // 添加到数据开头
  mockData.unshift(newKnowledge);
  // 保持数据量在合理范围内(最多保留30条)
  if (mockData.length > 30) {
    mockData = mockData.slice(0, 30);
  }
  console.log(`[${new Date().toLocaleString()}] 自动生成新知识: ${title}`);
};
});
// 生命周期
onMounted(() => {
@@ -513,9 +608,8 @@
// 开始自动刷新
const startAutoRefresh = () => {
  setInterval(() => {
    generateNewData();
    getList();
  }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms)
  }, 600000);
};
// 查询数据
@@ -526,31 +620,30 @@
const getList = () => {
  tableLoading.value = true;
  setTimeout(() => {
    let filteredData = [...mockData];
    if (searchForm.value.title) {
      filteredData = filteredData.filter(item =>
        item.title.toLowerCase().includes(searchForm.value.title.toLowerCase())
      );
    }
    if (searchForm.value.type) {
      filteredData = filteredData.filter(item => item.type === searchForm.value.type);
    }
    tableData.value = filteredData;
    page.value.total = filteredData.length;
  listKnowledgeBase({...page.value, ...searchForm.value})
  .then(res => {
    tableLoading.value = false;
  }, 500);
    page.value.total = res.data.total;
    const maxPage = Math.ceil(res.data.total / page.value.size) || 1;
    if (page.value.current > maxPage && maxPage > 0) {
      page.value.current = 1;
      return getList();
    }
    tableData.value = res.data.records;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// 分页处理
const pagination = (obj) => {
  const oldSize = page.value.size;
  page.value.current = obj.page;
  page.value.size = obj.limit;
  handleQuery();
  if (oldSize !== obj.limit) {
    page.value.current = 1;
  }
  getList();
};
// 选择变化处理
@@ -563,21 +656,21 @@
  dialogType.value = type;
  if (type === "add") {
    dialogTitle.value = "新增知识";
    // 重置表单
    Object.assign(form.value, {
      title: "",
      type: "",
      scenario: "",
      efficiency: "medium",
      efficiency: "",
      problem: "",
      solution: "",
      keyPoints: "",
      creator: "",
      creator: userStore.nickName || "",
      usageCount: 0
    });
  } else if (type === "edit" && row) {
    dialogTitle.value = "编辑知识";
    Object.assign(form.value, {
      id: row.id,
      title: row.title,
      type: row.type,
      scenario: row.scenario,
@@ -612,14 +705,7 @@
// 获取类型标签文本
const getTypeLabel = (type) => {
  const typeMap = {
    contract: "合同特批",
    approval: "审批案例",
    solution: "解决方案",
    experience: "经验总结",
    guide: "操作指南"
  };
  return typeMap[type] || type;
  return getKnowledgeTypeLabel(type);
};
// 获取效率标签类型
@@ -665,16 +751,15 @@
// 复制知识
const copyKnowledge = () => {
  const knowledgeText = `
知识标题:${currentKnowledge.value.title}
知识类型:${getTypeLabel(currentKnowledge.value.type)}
适用场景:${currentKnowledge.value.scenario}
问题描述:${currentKnowledge.value.problem}
解决方案:${currentKnowledge.value.solution}
关键要点:${currentKnowledge.value.keyPoints}
创建人:${currentKnowledge.value.creator}
    知识标题:${currentKnowledge.value.title}
    知识类型:${getTypeLabel(currentKnowledge.value.type)}
    适用场景:${currentKnowledge.value.scenario}
    问题描述:${currentKnowledge.value.problem}
    解决方案:${currentKnowledge.value.solution}
    关键要点:${currentKnowledge.value.keyPoints}
    创建人:${currentKnowledge.value.creator}
  `.trim();
  // 复制到剪贴板
  navigator.clipboard.writeText(knowledgeText).then(() => {
    ElMessage.success("知识内容已复制到剪贴板");
  }).catch(() => {
@@ -682,62 +767,62 @@
  });
};
// 收藏知识
const markAsFavorite = () => {
  // 增加使用次数
  const index = mockData.findIndex(item => item.id === currentKnowledge.value.id);
  if (index !== -1) {
    mockData[index].usageCount += 1;
    currentKnowledge.value.usageCount += 1;
// 关闭知识表单对话框
const closeKnowledgeDialog = () => {
  Object.assign(form.value, {
    id: undefined,
    title: "",
    type: "",
    scenario: "",
    efficiency: "",
    problem: "",
    solution: "",
    keyPoints: "",
    creator: userStore.nickName || "",
    usageCount: 0
  });
  if (formRef.value) {
    formRef.value.clearValidate();
  }
  ElMessage.success("已收藏,使用次数+1");
  dialogVisible.value = false;
};
// 关闭查看详情对话框
const closeViewDialog = () => {
  viewDialogVisible.value = false;
};
// 处理查看详情对话框确认
const handleViewDialogConfirm = () => {
  copyKnowledge();
  closeViewDialog();
};
// 提交知识表单
const submitForm = async () => {
  try {
    await formRef.value.validate();
    if (dialogType.value === "add") {
      // 新增知识
      const newKnowledge = {
        id: (mockData.length + 1).toString(),
        title: form.value.title,
        type: form.value.type,
        scenario: form.value.scenario,
        efficiency: form.value.efficiency,
        problem: form.value.problem,
        solution: form.value.solution,
        keyPoints: form.value.keyPoints,
        creator: form.value.creator,
        usageCount: form.value.usageCount,
        createTime: new Date().toLocaleString()
      };
      mockData.unshift(newKnowledge);
      ElMessage.success("知识创建成功");
      addKnowledgeBase({...form.value}).then(res => {
        if(res.code == 200){
          ElMessage.success("添加成功");
          closeKnowledgeDialog();
          getList();
        }
      }).catch(err => {
        ElMessage.error(err.msg);
      })
    } else {
      // 编辑知识
      const index = mockData.findIndex(item => item.id === selectedIds.value[0]);
      if (index !== -1) {
        Object.assign(mockData[index], {
          title: form.value.title,
          type: form.value.type,
          scenario: form.value.scenario,
          efficiency: form.value.efficiency,
          problem: form.value.problem,
          solution: form.value.solution,
          keyPoints: form.value.keyPoints,
          creator: form.value.creator,
          usageCount: form.value.usageCount
        });
        ElMessage.success("知识更新成功");
      }
      updateKnowledgeBase({...form.value}).then(res => {
        if(res.code == 200){
          ElMessage.success("更新成功");
          closeKnowledgeDialog();
          getList();
        }
      }).catch(err => {
        ElMessage.error(err.msg);
      })
    }
    dialogVisible.value = false;
    getList();
  } catch (error) {
    console.error("表单验证失败:", error);
  }
@@ -749,42 +834,387 @@
    ElMessage.warning("请选择要删除的知识");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    // 从mockData中删除选中的项
    selectedIds.value.forEach(id => {
      const index = mockData.findIndex(item => item.id === id);
      if (index !== -1) {
        mockData.splice(index, 1);
    delKnowledgeBase(selectedIds.value).then(res => {
      if(res.code == 200){
        ElMessage.success("删除成功");
        selectedIds.value = [];
        getList();
      }
    })
  }).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))
  return item ? item.label : val
}
const getKnowledgeTypeTagType = (val) => {
  const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val))
  return item?.elTagType || "info"
}
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'
    });
    ElMessage.success("删除成功");
    selectedIds.value = [];
    getList();
  }).catch(() => {
    // 用户取消
    // 获取向量化状态
    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;
}
@@ -845,4 +1275,4 @@
  font-size: 14px;
  color: #909399;
}
</style>
</style>