2 天以前 16697df76d7b27ff65d229937b3f3ac4cc0e56a0
src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -1,6 +1,6 @@
<template>
  <div class="app-container">
    <div class="search_form">
    <div class="search_form" style="margin-bottom: 20px;">
      <div>
        <span class="search_title">知识标题:</span>
        <el-input
@@ -13,22 +13,24 @@
        />
        <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-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"
@@ -44,11 +46,13 @@
    </div>
    <!-- 新增/编辑知识弹窗 -->
    <el-dialog
    <FormDialog
      v-model="dialogVisible"
      :title="dialogTitle"
      width="800px"
      :close-on-click-modal="false"
      :width="'800px'"
      @close="closeKnowledgeDialog"
      @confirm="submitForm"
      @cancel="closeKnowledgeDialog"
    >
      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
        <el-row :gutter="20">
@@ -60,11 +64,12 @@
          <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-option
                    v-for="item in knowledgeTypeOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
@@ -112,7 +117,14 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="创建人" prop="creator">
              <el-input v-model="form.creator" placeholder="请输入创建人" />
              <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">
@@ -122,20 +134,16 @@
          </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>
    </FormDialog>
    <!-- 查看知识详情弹窗 -->
    <el-dialog
    <FormDialog
      v-model="viewDialogVisible"
      title="知识详情"
      width="900px"
      :close-on-click-modal="false"
      :width="'900px'"
      @close="closeViewDialog"
      @confirm="handleViewDialogConfirm"
      @cancel="closeViewDialog"
    >
      <div class="knowledge-detail">
        <el-descriptions :column="2" border>
@@ -180,7 +188,7 @@
          <h4>关键要点</h4>
          <div class="key-points">
            <el-tag
              v-for="(point, index) in currentKnowledge.keyPoints.split(',')"
              v-for="(point, index) in currentKnowledge.keyPoints?.split(',') || []"
              :key="index"
              type="success"
              style="margin-right: 8px; margin-bottom: 8px;"
@@ -216,23 +224,168 @@
          </div>
        </div>
      </div>
    </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="filesDialogVisible"
      title="文件管理"
      :width="'900px'"
      @close="closeFilesDialog"
      @confirm="closeFilesDialog"
      @cancel="closeFilesDialog"
    >
      <div class="file-manager">
        <!-- 文件上传 -->
        <div class="upload-section">
          <el-upload
            :action="uploadUrl"
            :headers="uploadHeaders"
            :on-success="handleUploadSuccess"
            :on-error="handleUploadError"
            :before-upload="beforeUpload"
            name="files"
            multiple
            :show-file-list="false"
            accept=".txt,.md,.docx,.xlsx,.xls,.pdf"
          >
            <el-button type="primary">上传文件</el-button>
          </el-upload>
          <el-button
            type="success"
            @click="saveFiles"
            :disabled="uploadedBlobIds.length === 0"
            :loading="savingFiles"
            style="margin-left: 10px"
          >
            保存文件关联
          </el-button>
          <el-button
            v-if="uploadedBlobIds.length > 0"
            type="text"
            @click="clearUploadedFiles"
            style="margin-left: 10px"
          >
            清空待保存列表
          </el-button>
        </div>
        <!-- 待保存的文件列表 -->
        <div v-if="uploadedBlobIds.length > 0" class="uploaded-list">
          <div class="uploaded-tip">
            <el-icon style="color: #409eff"><InfoFilled /></el-icon>
            <span>已上传 {{ uploadedBlobIds.length }} 个文件,请点击"保存文件关联"按钮触发向量化处理</span>
          </div>
        </div>
        <!-- 文件列表与向量化状态 -->
        <el-table :data="fileList" style="margin-top: 20px" border>
          <el-table-column prop="fileName" label="文件名" show-overflow-tooltip />
          <el-table-column prop="fileType" label="文件类型" width="100" />
          <el-table-column label="向量化状态" width="120">
            <template #default="{ row }">
              <el-tag :type="getStatusType(row.vectorStatus)">
                {{ getStatusText(row.vectorStatus) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="chunkCount" label="切片数" width="100" align="center" />
          <el-table-column label="错误信息" width="200" show-overflow-tooltip>
            <template #default="{ row }">
              <span v-if="row.vectorError" style="color: #f56c6c">{{ row.vectorError }}</span>
              <span v-else style="color: #909399">-</span>
            </template>
          </el-table-column>
          <el-table-column prop="createTime" label="上传时间" width="180" />
          <el-table-column label="操作" width="150" align="center">
            <template #default="{ row }">
              <el-button
                v-if="row.vectorStatus === 3"
                type="text"
                @click="reprocessFile(row)"
              >
                重新处理
              </el-button>
              <el-button type="text" @click="deleteFile(row)" style="color: #f56c6c">
                删除
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </FormDialog>
    <!-- 知识库问答弹窗 -->
    <FormDialog
      v-model="chatDialogVisible"
      title="知识库问答"
      :width="'800px'"
      @close="closeChatDialog"
      @confirm="closeChatDialog"
      @cancel="closeChatDialog"
    >
      <div class="knowledge-chat">
        <div class="chat-header">
          <el-tag type="success">当前知识库: {{ currentKnowledgeBase?.title }}</el-tag>
        </div>
        <!-- 对话区域 -->
        <div class="chat-messages" ref="chatMessagesRef">
          <div
            v-for="(msg, index) in messages"
            :key="index"
            :class="['message', msg.role]"
          >
            <div class="message-role">{{ msg.role === 'user' ? '我' : 'AI助手' }}</div>
            <div class="message-content">{{ msg.content }}</div>
          </div>
          <div v-if="chatLoading" class="message assistant">
            <div class="message-role">AI助手</div>
            <div class="message-content typing">正在思考中...</div>
          </div>
        </div>
        <!-- 输入框 -->
        <div class="chat-input">
          <el-input
            v-model="inputQuestion"
            placeholder="请输入问题,按回车发送(Ctrl+Enter快捷发送)"
            @keyup.enter="sendMessage"
            :disabled="chatLoading"
          >
            <template #append>
              <el-button @click="sendMessage" :loading="chatLoading">发送</el-button>
            </template>
          </el-input>
          <div class="chat-actions">
            <el-button type="text" size="small" @click="clearMessages">清空对话</el-button>
          </div>
        </div>
      </div>
    </FormDialog>
  </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, toRefs } from "vue";
import { Search, InfoFilled } 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,
  saveKnowledgeBaseFiles,
  deleteKnowledgeBaseFile,
  knowledgeChat
} from "@/api/collaborativeApproval/knowledgeBase.js";
import useUserStore from '@/store/modules/user';
import { userListNoPageByTenantId } from '@/api/system/user.js';
import { getToken } from "@/utils/auth";
// 表单验证规则
const rules = {
@@ -259,7 +412,7 @@
  tableLoading: false,
  page: {
    current: 1,
    size: 100,
    size: 20,
    total: 0,
  },
  tableData: [],
@@ -268,7 +421,7 @@
    title: "",
    type: "",
    scenario: "",
    efficiency: "medium",
    efficiency: "",
    problem: "",
    solution: "",
    keyPoints: "",
@@ -279,25 +432,56 @@
  dialogTitle: "",
  dialogType: "add",
  viewDialogVisible: false,
  currentKnowledge: {}
  currentKnowledge: {},
  filesDialogVisible: false,
  currentKnowledgeBase: null,
  fileList: [],
  uploadedBlobIds: [],
  savingFiles: false,
  vectorStatusTimer: null, // 向量化状态轮询定时器
  chatDialogVisible: false,
  messages: [],
  inputQuestion: "",
  chatLoading: false,
  memoryId: ""
});
const {
  searchForm,
  tableLoading,
  page,
  tableData,
const {
  searchForm,
  tableLoading,
  page,
  tableData,
  selectedIds,
  form,
  dialogVisible,
  dialogTitle,
  dialogType,
  viewDialogVisible,
  currentKnowledge
  currentKnowledge,
  filesDialogVisible,
  currentKnowledgeBase,
  fileList,
  uploadedBlobIds,
  savingFiles,
  vectorStatusTimer,
  chatDialogVisible,
  messages,
  inputQuestion,
  chatLoading,
  memoryId
} = toRefs(data);
// 表单引用
const formRef = ref();
// 用户相关
const userStore = useUserStore();
const userList = ref([]);
// 聊天消息容器引用
const chatMessagesRef = ref();
// 文件上传相关
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
const uploadHeaders = { Authorization: "Bearer " + getToken() };
// 表格列配置
const tableColumn = ref([
@@ -311,24 +495,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);
    }
  },
  {
@@ -357,6 +527,18 @@
      };
      return typeMap[params] || "info";
    }
  },
  {
    label: "文件数量",
    prop: "fileCount",
    width: 100,
    align: "center"
  },
  {
    label: "切片数量",
    prop: "totalChunkCount",
    width: 100,
    align: "center"
  },
  {
    label: "使用次数",
@@ -389,7 +571,21 @@
        }
      },
      {
        name: "查看",
        name: "文件",
        type: "text",
        clickFun: (row) => {
          openFilesDialog(row);
        }
      },
      {
        name: "问答",
        type: "text",
        clickFun: (row) => {
          openChatDialog(row);
        }
      },
      {
        name: "详情",
        type: "text",
        clickFun: (row) => {
          viewKnowledge(row);
@@ -399,110 +595,14 @@
  }
]);
// 模拟数据
let mockData = [
  {
    id: "1",
    title: "特殊合同审批流程优化方案",
    type: "contract",
    scenario: "大额合同快速审批",
    efficiency: "high",
    problem: "大额合同审批流程复杂,审批时间长,影响业务进展",
    solution: "建立绿色通道,对符合条件的合同采用简化审批流程,由部门负责人直接审批,平均审批时间从3天缩短至1天",
    keyPoints: "绿色通道条件,简化流程,审批权限,时间控制",
    creator: "陈志强",
    usageCount: 15,
    createTime: "2025-01-15 10:30:00"
  },
  {
    id: "2",
    title: "跨部门协作审批经验总结",
    type: "experience",
    scenario: "多部门协作项目",
    efficiency: "medium",
    problem: "跨部门项目审批时,各部门意见不统一,审批进度缓慢",
    solution: "建立项目协调机制,指定项目负责人,定期召开协调会议,统一各方意见后再进行审批",
    keyPoints: "项目协调,定期会议,统一意见,负责人制度",
    creator: "李主管",
    usageCount: 8,
    createTime: "2025-01-14 15:20:00"
  },
  {
    id: "3",
    title: "紧急采购审批操作指南",
    type: "guide",
    scenario: "紧急采购需求",
    efficiency: "high",
    problem: "紧急采购时审批流程复杂,无法满足紧急需求",
    solution: "制定紧急采购审批标准,明确紧急程度分级,不同级别采用不同审批流程,确保紧急需求得到及时处理",
    keyPoints: "紧急分级,标准制定,流程简化,及时处理",
    creator: "王专员",
    usageCount: 12,
    createTime: "2025-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,7 +613,6 @@
// 开始自动刷新
const startAutoRefresh = () => {
  setInterval(() => {
    generateNewData();
    getList();
  }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms)
};
@@ -526,31 +625,43 @@
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;
  // ✅ GET请求使用params传参
  listKnowledgeBase({
    current: page.value.current,
    size: page.value.size,
    title: searchForm.value.title,
    type: searchForm.value.type
  })
  .then(res => {
    tableLoading.value = false;
  }, 500);
    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;
      return getList();
    }
    tableData.value = res.data.records;
  })
  .catch(err => {
    tableLoading.value = false;
    console.error("查询知识库列表失败:", err);
  });
};
// 分页处理
const pagination = (obj) => {
  const oldSize = page.value.size;
  page.value.current = obj.page;
  page.value.size = obj.limit;
  handleQuery();
  // 如果 size 改变了,重置到第1页,避免当前页超出范围
  if (oldSize !== obj.limit) {
    page.value.current = 1;
  }
  getList();
};
// 选择变化处理
@@ -563,21 +674,22 @@
  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 +724,7 @@
// 获取类型标签文本
const getTypeLabel = (type) => {
  const typeMap = {
    contract: "合同特批",
    approval: "审批案例",
    solution: "解决方案",
    experience: "经验总结",
    guide: "操作指南"
  };
  return typeMap[type] || type;
  return getKnowledgeTypeLabel(type);
};
// 获取效率标签类型
@@ -665,15 +770,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("知识内容已复制到剪贴板");
@@ -682,62 +787,85 @@
  });
};
// 收藏知识
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();
    // ✅ POST请求使用data传参,明确参数结构
    const formData = {
      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 || 0
    };
    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(formData).then(res => {
        if(res.code == 200){
          ElMessage.success("添加成功");
          closeKnowledgeDialog();
          getList();
        }
      }).catch(err => {
        console.error("添加知识库失败:", 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("知识更新成功");
      }
      // 更新知识 - 添加id参数
      updateKnowledgeBase({
        id: form.value.id,
        ...formData
      }).then(res => {
        if(res.code == 200){
          ElMessage.success("更新成功");
          closeKnowledgeDialog();
          getList();
        }
      }).catch(err => {
        console.error("更新知识库失败:", err);
        ElMessage.error(err.msg || "更新失败");
      });
    }
    dialogVisible.value = false;
    getList();
  } catch (error) {
    console.error("表单验证失败:", error);
  }
@@ -749,26 +877,389 @@
    ElMessage.warning("请选择要删除的知识");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
  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);
    // ✅ DELETE请求使用data传递ID数组
    delKnowledgeBase(selectedIds.value).then(res => {
      if(res.code == 200){
        ElMessage.success("删除成功");
        selectedIds.value = [];
        getList();
      }
    }).catch(err => {
      console.error("删除知识库失败:", err);
      ElMessage.error(err.msg || "删除失败");
    });
    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 openFilesDialog = (row) => {
  currentKnowledgeBase.value = row;
  filesDialogVisible.value = true;
  loadFileList();
};
// 加载文件列表
const loadFileList = async () => {
  if (!currentKnowledgeBase.value?.id) return;
  try {
    const res = await getVectorStatus(currentKnowledgeBase.value.id);
    fileList.value = res.data || [];
    // 检查是否有处理中的文件,如果有则启动轮询
    const hasProcessing = res.data.some(item => item.vectorStatus === 1);
    if (hasProcessing && !vectorStatusTimer.value) {
      startVectorStatusPolling();
    } else if (!hasProcessing && vectorStatusTimer.value) {
      stopVectorStatusPolling();
    }
  } catch (error) {
    console.error("加载文件列表失败:", error);
    ElMessage.error("加载文件列表失败");
  }
};
// 开始轮询向量化状态
const startVectorStatusPolling = () => {
  vectorStatusTimer.value = setInterval(async () => {
    try {
      const res = await getVectorStatus(currentKnowledgeBase.value.id);
      fileList.value = res.data || [];
      // 检查是否还有处理中的文件
      const hasProcessing = res.data.some(item => item.vectorStatus === 1);
      if (!hasProcessing) {
        stopVectorStatusPolling();
        ElMessage.success("所有文件向量化处理完成");
      }
    } catch (error) {
      console.error("轮询向量化状态失败:", error);
      stopVectorStatusPolling();
    }
  }, 3000); // 每3秒轮询一次
};
// 停止轮询向量化状态
const stopVectorStatusPolling = () => {
  if (vectorStatusTimer.value) {
    clearInterval(vectorStatusTimer.value);
    vectorStatusTimer.value = null;
  }
};
// 上传前校验
const beforeUpload = (file) => {
  const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf'];
  const fileName = file.name.toLowerCase();
  const isAllowed = allowedTypes.some(type => fileName.endsWith(type));
  if (!isAllowed) {
    ElMessage.error('只支持 txt、md、docx、xlsx、xls、pdf 格式的文件');
    return false;
  }
  const isLt50M = file.size / 1024 / 1024 < 50;
  if (!isLt50M) {
    ElMessage.error('文件大小不能超过 50MB');
    return false;
  }
  return true;
};
// 上传成功
const handleUploadSuccess = (response, file) => {
  console.log("上传响应:", response);  // 调试日志
  if (response.code === 200) {
    // ✅ 后端返回的是 List<StorageBlobVO>,所以data是数组
    if (Array.isArray(response.data) && response.data.length > 0) {
      // 取数组第一个元素的id
      const blobId = response.data[0].id;
      if (blobId) {
        uploadedBlobIds.value.push(blobId);
        ElMessage.success(`文件 ${file.name} 上传成功`);
      } else {
        console.error("上传响应中未找到id:", response.data[0]);
        ElMessage.error("上传失败: 未获取到文件ID");
      }
    } else {
      console.error("上传响应格式错误:", response);
      ElMessage.error("上传失败: 响应格式错误");
    }
  } else {
    ElMessage.error(response.msg || "上传失败");
  }
};
// 上传失败
const handleUploadError = (error, file) => {
  ElMessage.error(`文件 ${file.name} 上传失败`);
};
// 保存文件关联
const saveFiles = async () => {
  // 参数校验
  if (!currentKnowledgeBase.value?.id) {
    ElMessage.error("知识库信息异常");
    return;
  }
  if (uploadedBlobIds.value.length === 0) {
    ElMessage.warning("请先上传文件");
    return;
  }
  savingFiles.value = true;
  try {
    // ✅ POST请求使用data传参,明确参数结构
    await saveKnowledgeBaseFiles({
      knowledgeBaseId: currentKnowledgeBase.value.id,  // 知识库ID
      storageBlobIds: uploadedBlobIds.value             // 文件blob ID数组
    });
    ElMessage.success("文件关联保存成功,正在后台处理向量化");
    uploadedBlobIds.value = [];
    // 延迟刷新文件列表,给后台处理时间
    setTimeout(() => {
      loadFileList();
    }, 1000);
  } catch (error) {
    console.error("保存文件关联失败:", error);
    ElMessage.error("保存文件关联失败");
  } finally {
    savingFiles.value = false;
  }
};
// 重新处理向量化的文件
const reprocessFile = async (row) => {
  try {
    await reprocessVector(row.id);
    ElMessage.success("已重新提交向量化任务");
    // 延迟刷新
    setTimeout(() => {
      loadFileList();
    }, 1000);
  } catch (error) {
    console.error("重新处理失败:", error);
    ElMessage.error("重新处理失败");
  }
};
// 清空待保存的文件列表
const clearUploadedFiles = () => {
  uploadedBlobIds.value = [];
  ElMessage.success("已清空待保存文件列表");
};
// 删除文件
const deleteFile = async (row) => {
  try {
    await ElMessageBox.confirm(
      "确定要删除该文件吗?删除后将无法恢复向量数据",
      "删除确认",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }
    );
    // ✅ DELETE请求使用data传递ID数组
    await deleteKnowledgeBaseFile([row.id]);  // 注意: row.id是向量记录ID,不是storageBlobId
    ElMessage.success("删除成功");
    loadFileList();
  } catch (error) {
    if (error !== 'cancel') {
      console.error("删除文件失败:", error);
      ElMessage.error("删除文件失败");
    }
  }
};
// 状态文本映射
const getStatusText = (status) => {
  const map = {
    0: '待处理',
    1: '处理中',
    2: '已完成',
    3: '失败'
  };
  return map[status] || '未知';
};
// 状态标签类型映射
const getStatusType = (status) => {
  const map = {
    0: 'info',
    1: 'warning',
    2: 'success',
    3: 'danger'
  };
  return map[status] || 'info';
};
// 关闭文件管理弹窗
const closeFilesDialog = () => {
  filesDialogVisible.value = false;
  currentKnowledgeBase.value = null;
  fileList.value = [];
  uploadedBlobIds.value = [];
  stopVectorStatusPolling(); // 停止轮询
  getList(); // 刷新主列表,更新文件数量
};
// ============ 知识库问答相关 ============
// 打开问答弹窗
const openChatDialog = (row) => {
  currentKnowledgeBase.value = row;
  chatDialogVisible.value = true;
  memoryId.value = crypto.randomUUID();
  messages.value = [];
  inputQuestion.value = "";
};
// 发送消息
const sendMessage = async () => {
  // 参数校验
  if (!inputQuestion.value.trim()) {
    ElMessage.warning("请输入问题");
    return;
  }
  if (!currentKnowledgeBase.value?.id) {
    ElMessage.error("知识库信息异常");
    return;
  }
  const question = inputQuestion.value.trim();
  // 添加用户消息
  messages.value.push({
    role: 'user',
    content: question
  });
  inputQuestion.value = "";
  chatLoading.value = true;
  // 滚动到底部
  await nextTick();
  scrollToBottom();
  try {
    // ✅ 流式请求使用Fetch API
    const response = await knowledgeChat({
      knowledgeBaseId: currentKnowledgeBase.value.id,  // 知识库ID
      memoryId: memoryId.value,                         // 会话ID
      question: question                                // 用户问题
    });
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(errorText || '请求失败');
    }
    // ✅ 后端返回 text/stream;charset=utf-8
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let aiContent = '';
    messages.value.push({ role: 'assistant', content: '' });
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      const text = decoder.decode(value, { stream: true });  // ✅ 添加stream选项
      aiContent += text;
      messages.value[messages.value.length - 1].content = aiContent;
      // 滚动到底部
      await nextTick();
      scrollToBottom();
    }
    // 如果AI返回空内容,显示提示
    if (!aiContent.trim()) {
      messages.value[messages.value.length - 1].content = '抱歉,知识库中未找到相关内容,请尝试其他问题。';
    }
  } catch (error) {
    console.error("问答请求失败:", error);
    ElMessage.error("问答请求失败,请稍后重试");
    messages.value.push({
      role: 'assistant',
      content: '抱歉,发生了错误,请稍后重试'
    });
  } finally {
    chatLoading.value = false;
  }
};
// 清空对话
const clearMessages = () => {
  ElMessageBox.confirm(
    "确定要清空所有对话记录吗?",
    "清空确认",
    {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning"
    }
  ).then(() => {
    messages.value = [];
    memoryId.value = crypto.randomUUID(); // 重新生成会话ID
    ElMessage.success("对话已清空");
  }).catch(() => {
    // 用户取消
  });
};
// 滚动到底部
const scrollToBottom = () => {
  if (chatMessagesRef.value) {
    chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
  }
};
// 关闭问答弹窗
const closeChatDialog = () => {
  chatDialogVisible.value = false;
  currentKnowledgeBase.value = null;
  messages.value = [];
  inputQuestion.value = "";
};
</script>
@@ -845,4 +1336,113 @@
  font-size: 14px;
  color: #909399;
}
/* 文件管理样式 */
.file-manager {
  padding: 20px 0;
}
.upload-section {
  display: flex;
  align-items: center;
}
.uploaded-list {
  margin-top: 16px;
  padding: 12px;
  background: #f0f9ff;
  border-radius: 6px;
  border: 1px solid #b3d8ff;
}
.uploaded-tip {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #409eff;
  font-size: 14px;
}
/* 知识库问答样式 */
.knowledge-chat {
  display: flex;
  flex-direction: column;
  height: 500px;
}
.chat-header {
  margin-bottom: 16px;
}
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  background: #f5f7fa;
  border-radius: 8px;
  margin-bottom: 16px;
}
.message {
  margin-bottom: 16px;
  max-width: 80%;
}
.message.user {
  margin-left: auto;
  text-align: right;
}
.message.assistant {
  margin-right: auto;
}
.message-role {
  font-size: 12px;
  color: #909399;
  margin-bottom: 4px;
}
.message-content {
  display: inline-block;
  padding: 10px 14px;
  border-radius: 8px;
  line-height: 1.6;
  word-wrap: break-word;
  white-space: pre-wrap;
}
.message.user .message-content {
  background: #409eff;
  color: white;
}
.message.assistant .message-content {
  background: white;
  color: #303133;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.typing {
  animation: typing 1.5s infinite;
}
@keyframes typing {
  0%, 50%, 100% {
    opacity: 1;
  }
  25%, 75% {
    opacity: 0.5;
  }
}
.chat-input {
  margin-top: auto;
}
.chat-actions {
  margin-top: 8px;
  text-align: right;
}
</style>