KnowledgeContent.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482
  1. <template>
  2. <div class="app-container">
  3. <!-- 顶部导航 -->
  4. <div class="app-header">
  5. <div class="header-content">
  6. <div class="header-main">
  7. <!-- 返回按钮 -->
  8. <div class="header-back">
  9. <el-button
  10. class="back-btn"
  11. @click="goBack"
  12. text
  13. >
  14. <span class="back-icon">←</span>
  15. <span style="color: white">返回知识库</span>
  16. </el-button>
  17. </div>
  18. <div class="header-titles">
  19. <h1 class="app-title">知识库内容管理</h1>
  20. <p class="app-subtitle">管理知识库中的文档和内容</p>
  21. </div>
  22. </div>
  23. <div class="header-stats">
  24. <div class="stat-item">
  25. <span class="stat-number">{{ totalItems }}</span>
  26. <span class="stat-label">总文档</span>
  27. </div>
  28. <div class="stat-item">
  29. <span class="stat-number">{{ currentPageItems.length }}</span>
  30. <span class="stat-label">本页</span>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. <div class="main-layout">
  36. <!-- 左侧文档列表 -->
  37. <div class="sidebar">
  38. <div class="sidebar-header">
  39. <div class="sidebar-title">
  40. <h3>📚 文档列表</h3>
  41. <p class="sidebar-subtitle">{{ datasetName }} - 共 {{ totalItems }} 篇文档</p>
  42. </div>
  43. <el-button
  44. class="add-btn"
  45. type="primary"
  46. @click="openDialog"
  47. >
  48. <span class="button-icon">➕</span>
  49. 添加文档
  50. </el-button>
  51. </div>
  52. <div class="knowledge-list">
  53. <div
  54. v-for="item in currentPageItems"
  55. :key="item.doc_id"
  56. :class="['document-card', { 'active': activeItem === String(item.doc_id) }]"
  57. @click="handleSelect(String(item.doc_id))"
  58. >
  59. <div class="card-content">
  60. <div class="document-icon">
  61. <span v-if="item.statusDesc === '可用'" class="status-available">✅</span>
  62. <span v-else class="status-unavailable">❌</span>
  63. </div>
  64. <div class="document-info">
  65. <span class="document-name">{{ item.title || item.text }}</span>
  66. </div>
  67. <div class="document-actions">
  68. <el-tooltip content="删除文档" placement="top">
  69. <el-button
  70. text
  71. class="delete-btn"
  72. @click.stop="confirmDelete(item.doc_id, item.title)"
  73. >
  74. 🗑️
  75. </el-button>
  76. </el-tooltip>
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. <!-- 分页 -->
  82. <div class="sidebar-footer">
  83. <div class="pagination-info">
  84. 共 {{ totalItems }} 篇文档
  85. </div>
  86. <el-pagination
  87. v-model:current-page="pageIndex"
  88. :page-size="pageSize"
  89. :total="totalItems"
  90. :pager-count="5"
  91. layout="prev, pager, next"
  92. @current-change="handlePageChange"
  93. class="custom-pagination"
  94. />
  95. </div>
  96. </div>
  97. <!-- 右侧内容区域 -->
  98. <div class="content-area">
  99. <div class="content-section">
  100. <div class="content-header">
  101. <div class="content-title">
  102. <h2>{{ selectedTitle || '选择文档查看内容' }}</h2>
  103. <span v-if="statusDesc" class="content-status"
  104. :class="statusDesc === '可用' ? 'available' : 'unavailable'">
  105. {{ statusDesc }}
  106. </span>
  107. </div>
  108. <el-button
  109. v-if="selectedContent"
  110. type="primary"
  111. class="chunk-btn"
  112. @click="openChunkDialog"
  113. >
  114. <span class="button-icon">📖</span>
  115. 查看分段
  116. </el-button>
  117. </div>
  118. <div class="content-body">
  119. <div v-if="selectedContent" class="content-text">
  120. <pre>{{ selectedContent }}</pre>
  121. </div>
  122. <div v-else class="empty-content">
  123. <div class="empty-icon">📄</div>
  124. <h3>选择左侧文档查看内容</h3>
  125. <p>点击左侧文档列表中的项目查看详细内容</p>
  126. </div>
  127. </div>
  128. </div>
  129. </div>
  130. </div>
  131. <!-- 添加文档弹窗 -->
  132. <el-dialog
  133. title="添加新文档"
  134. v-model="dialogVisible"
  135. width="600px"
  136. class="content-dialog"
  137. @close="resetForm"
  138. >
  139. <div class="dialog-content">
  140. <el-form :model="formData" ref="formRef" label-width="100px">
  141. <el-form-item label="📝 文档标题" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
  142. <el-input
  143. v-model="formData.title"
  144. placeholder="请输入文档标题"
  145. size="large"
  146. ></el-input>
  147. </el-form-item>
  148. <el-form-item label="📄 文档内容" :rules="[{ required: true, message: '请输入文本内容', trigger: 'blur' }]">
  149. <el-input
  150. type="textarea"
  151. v-model="formData.text"
  152. placeholder="请输入文档内容"
  153. :rows="8"
  154. resize="none"
  155. ></el-input>
  156. </el-form-item>
  157. <el-form-item label="🔗 是否分块">
  158. <el-switch
  159. v-model="formData.isChunk"
  160. active-text="启用分块"
  161. inactive-text="禁用分块"
  162. ></el-switch>
  163. </el-form-item>
  164. </el-form>
  165. </div>
  166. <template #footer>
  167. <div class="dialog-footer">
  168. <el-button
  169. class="action-btn secondary"
  170. @click="dialogVisible = false"
  171. >
  172. 取消
  173. </el-button>
  174. <el-button
  175. type="primary"
  176. class="action-btn primary"
  177. @click="submitData"
  178. >
  179. 💾 保存文档
  180. </el-button>
  181. </div>
  182. </template>
  183. </el-dialog>
  184. <!-- 分段展示弹窗 -->
  185. <el-dialog
  186. title="📑 文档分段"
  187. v-model="chunkDialogVisible"
  188. width="90%"
  189. class="content-dialog chunk-dialog"
  190. :destroy-on-close="true"
  191. >
  192. <div class="chunk-container">
  193. <div class="chunk-header">
  194. <div class="chunk-stats">
  195. <span class="stat-item">共 {{ chunkTotal }} 个分段</span>
  196. <span class="stat-item">当前显示 {{ chunkList.length }} 个</span>
  197. </div>
  198. </div>
  199. <div class="chunk-content">
  200. <el-table
  201. v-if="chunkDialogVisible && chunkList.length > 0"
  202. :data="chunkList"
  203. style="width:100%"
  204. class="chunk-table"
  205. :scrollbar-always-on="true"
  206. >
  207. <el-table-column
  208. prop="chunk_id"
  209. label="分段ID"
  210. width="100"
  211. header-align="center"
  212. align="center"
  213. fixed="left"
  214. >
  215. <template #default="scope">
  216. <span class="chunk-id">#{{ scope.row.chunk_id }}</span>
  217. </template>
  218. </el-table-column>
  219. <el-table-column
  220. prop="summary"
  221. label="内容摘要"
  222. width="300"
  223. header-align="center"
  224. min-width="300"
  225. >
  226. <template #default="scope">
  227. <div class="summary-content" :title="scope.row.summary">
  228. {{ scope.row.summary || '无摘要' }}
  229. </div>
  230. </template>
  231. </el-table-column>
  232. <el-table-column
  233. prop="text"
  234. label="完整内容"
  235. header-align="center"
  236. min-width="500"
  237. >
  238. <template #default="scope">
  239. <div class="full-content" :title="scope.row.text">
  240. {{ scope.row.text }}
  241. </div>
  242. </template>
  243. </el-table-column>
  244. <el-table-column
  245. prop="statusDesc"
  246. label="状态"
  247. width="120"
  248. header-align="center"
  249. align="center"
  250. fixed="right"
  251. >
  252. <template #default="scope">
  253. <el-tag
  254. :type="scope.row.statusDesc === '可用' ? 'success' : 'danger'"
  255. class="status-tag"
  256. >
  257. {{ scope.row.statusDesc }}
  258. </el-tag>
  259. </template>
  260. </el-table-column>
  261. </el-table>
  262. <div v-else class="empty-chunks">
  263. <div class="empty-icon">📝</div>
  264. <h3>暂无分段内容</h3>
  265. <p>该文档没有分块或分块内容为空</p>
  266. </div>
  267. </div>
  268. <!-- 分页器 -->
  269. <div v-if="chunkTotal > 0" class="pagination-section">
  270. <el-pagination
  271. v-model:current-page="chunkPage"
  272. :page-size="chunkPageSize"
  273. :total="chunkTotal"
  274. :page-sizes="[10, 20, 50, 100]"
  275. layout="total, sizes, prev, pager, next, jumper"
  276. @size-change="handleSizeChange"
  277. @current-change="fetchChunks"
  278. />
  279. </div>
  280. </div>
  281. </el-dialog>
  282. </div>
  283. </template>
  284. <script lang="ts">
  285. import {API_BASE_URL} from "@/config";
  286. import {defineComponent, onMounted, ref, computed} from 'vue';
  287. import {useRouter} from 'vue-router';
  288. import axios from 'axios';
  289. import {ElMessage, ElMessageBox} from 'element-plus';
  290. interface Item {
  291. doc_id: string;
  292. title: string | null;
  293. text: string;
  294. statusDesc: string;
  295. }
  296. export default defineComponent({
  297. name: 'KnowledgeContent',
  298. setup() {
  299. const router = useRouter();
  300. const datasetId = ref<number | null>(null);
  301. const datasetName = ref<string | null>('');
  302. const activeItem = ref('');
  303. const selectedTitle = ref('');
  304. const selectedContent = ref('');
  305. const statusDesc = ref('');
  306. // 页码控制
  307. const pageIndex = ref(1);
  308. const pageSize = ref(10);
  309. const totalItems = ref(0);
  310. const allItems = ref<Item[]>([]);
  311. // 新知识表单数据
  312. const formData = ref({
  313. title: '',
  314. text: '',
  315. isChunk: true
  316. });
  317. const dialogVisible = ref(false);
  318. const formRef = ref();
  319. // 分段弹窗
  320. const chunkDialogVisible = ref(false);
  321. const chunkList = ref<any[]>([]);
  322. const chunkPage = ref(1);
  323. const chunkPageSize = ref(10);
  324. const chunkTotal = ref(0);
  325. // 返回按钮功能
  326. const goBack = () => {
  327. router.push('/');
  328. };
  329. // 获取API数据并填充项
  330. const fetchData = async (datasetId: number) => {
  331. console.log(datasetId);
  332. try {
  333. const response = await axios.get(`${API_BASE_URL}/content/list`, {
  334. params: {
  335. page: pageIndex.value,
  336. pageSize: pageSize.value,
  337. datasetId: datasetId,
  338. }
  339. });
  340. const data = response.data.data;
  341. allItems.value = data.entities;
  342. totalItems.value = data.total_count;
  343. console.log(data);
  344. } catch (error) {
  345. console.error('Error fetching data:', error);
  346. ElMessage.error('获取文档列表失败');
  347. }
  348. };
  349. // 当前页面的项目
  350. const currentPageItems = computed(() => {
  351. return allItems.value;
  352. });
  353. // 处理菜单项选择
  354. const handleSelect = (doc_id: string) => {
  355. activeItem.value = doc_id;
  356. const selected = allItems.value.find(item => item.doc_id === doc_id);
  357. if (selected) {
  358. selectedTitle.value = selected.title || '';
  359. selectedContent.value = selected.text;
  360. statusDesc.value = selected.statusDesc;
  361. }
  362. };
  363. // 处理页码变更
  364. const handlePageChange = (newPage: number) => {
  365. pageIndex.value = newPage;
  366. if (datasetId.value !== null) {
  367. fetchData(datasetId.value);
  368. } else {
  369. console.error('datasetId is null');
  370. }
  371. };
  372. // 初始化数据
  373. onMounted(() => {
  374. const query = new URLSearchParams(window.location.search);
  375. datasetId.value = query.get('datasetId') ? parseInt(query.get('datasetId')!) : null;
  376. datasetName.value = query.get('datasetName');
  377. if (datasetId.value !== null) {
  378. fetchData(datasetId.value);
  379. } else {
  380. console.error('datasetId is null');
  381. }
  382. console.log(`知识库 ID: ${datasetId.value}`);
  383. });
  384. // 打开弹窗
  385. const openDialog = () => {
  386. dialogVisible.value = true;
  387. };
  388. // 重置表单
  389. const resetForm = () => {
  390. formData.value.title = '';
  391. formData.value.text = '';
  392. };
  393. // 提交表单数据
  394. const submitData = async () => {
  395. if (!formData.value.title || !formData.value.text) {
  396. ElMessage.error('标题和文本不能为空');
  397. return;
  398. }
  399. try {
  400. const response = await axios.post(`${API_BASE_URL}/chunk`, {
  401. dataset_id: datasetId.value,
  402. title: formData.value.title,
  403. text: formData.value.text,
  404. dont_chunk: !formData.value.isChunk,
  405. });
  406. console.log('提交成功:', response.data);
  407. dialogVisible.value = false;
  408. if (datasetId.value !== null) {
  409. fetchData(datasetId.value);
  410. } else {
  411. console.error('datasetId is null');
  412. }
  413. ElMessage.success('文档添加成功!');
  414. resetForm();
  415. } catch (error) {
  416. console.error('提交失败:', error);
  417. ElMessage.error('提交失败,请稍后再试!');
  418. }
  419. };
  420. // 确认删除
  421. const confirmDelete = (doc_id: string, title: string | null) => {
  422. ElMessageBox.confirm(
  423. `确定要删除文档 "${title || doc_id}" 吗?`,
  424. '删除确认',
  425. {
  426. confirmButtonText: '删除',
  427. cancelButtonText: '取消',
  428. type: 'warning',
  429. confirmButtonClass: 'delete-confirm-btn',
  430. cancelButtonClass: 'delete-cancel-btn'
  431. }
  432. ).then(() => {
  433. deleteDoc(doc_id);
  434. }).catch(() => {
  435. // 用户取消,不处理
  436. });
  437. };
  438. // 删除文档方法
  439. const deleteDoc = async (doc_id: string) => {
  440. try {
  441. await axios.post('http://192.168.100.31:8001/api/delete', {
  442. level: 'doc',
  443. params: {doc_id}
  444. });
  445. ElMessage.success('删除成功');
  446. if (datasetId.value !== null) {
  447. fetchData(datasetId.value);
  448. }
  449. } catch (error) {
  450. console.error(error);
  451. ElMessage.error('删除失败');
  452. }
  453. };
  454. // 打开分段弹窗
  455. const openChunkDialog = async () => {
  456. if (!activeItem.value) {
  457. ElMessage.warning("请先选择一个文档");
  458. return;
  459. }
  460. chunkDialogVisible.value = true;
  461. await fetchChunks(chunkPage.value);
  462. };
  463. // 获取分段数据
  464. const fetchChunks = async (page: number) => {
  465. if (!activeItem.value) return;
  466. try {
  467. const response = await axios.get(`${API_BASE_URL}/chunk/list`, {
  468. params: {
  469. page,
  470. pageSize: chunkPageSize.value,
  471. docId: activeItem.value,
  472. }
  473. });
  474. const data = response.data.data;
  475. chunkList.value = data.entities;
  476. chunkTotal.value = data.total_count;
  477. chunkPage.value = data.page;
  478. } catch (err) {
  479. console.error(err);
  480. ElMessage.error("获取分段失败");
  481. }
  482. };
  483. // 分页大小变化处理
  484. const handleSizeChange = (newSize: number) => {
  485. chunkPageSize.value = newSize;
  486. chunkPage.value = 1;
  487. fetchChunks(1);
  488. };
  489. return {
  490. activeItem,
  491. selectedTitle,
  492. selectedContent,
  493. pageIndex,
  494. pageSize,
  495. totalItems,
  496. currentPageItems,
  497. handleSelect,
  498. handlePageChange,
  499. datasetName,
  500. formData,
  501. dialogVisible,
  502. openDialog,
  503. submitData,
  504. resetForm,
  505. confirmDelete,
  506. deleteDoc,
  507. chunkDialogVisible,
  508. chunkList,
  509. chunkPage,
  510. chunkPageSize,
  511. chunkTotal,
  512. openChunkDialog,
  513. fetchChunks,
  514. statusDesc,
  515. formRef,
  516. goBack,
  517. handleSizeChange
  518. };
  519. },
  520. });
  521. </script>
  522. <style scoped>
  523. /* 基础样式 */
  524. .app-container {
  525. min-height: 100vh;
  526. background: linear-gradient(135deg, rgba(102, 126, 234, 0.81) 0%, rgba(118, 75, 162, 0.77) 100%);
  527. font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  528. }
  529. /* 顶部导航样式 */
  530. .app-header {
  531. background: rgba(255, 255, 255, 0.1);
  532. backdrop-filter: blur(20px);
  533. padding: 16px 0;
  534. border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  535. }
  536. .header-content {
  537. max-width: 1400px;
  538. margin: 0 auto;
  539. padding: 0 24px;
  540. display: flex;
  541. justify-content: space-between;
  542. align-items: center;
  543. }
  544. .header-main {
  545. flex: 1;
  546. display: flex;
  547. align-items: flex-start;
  548. gap: 20px;
  549. }
  550. .header-back {
  551. flex-shrink: 0;
  552. }
  553. .back-btn {
  554. color: white;
  555. font-weight: 500;
  556. padding: 8px 16px;
  557. border-radius: 12px;
  558. transition: all 0.3s ease;
  559. background: rgba(255, 255, 255, 0.1);
  560. border: 1px solid rgba(255, 255, 255, 0.2);
  561. }
  562. .back-btn:hover {
  563. background: rgb(255, 255, 255);
  564. transform: translateX(-2px);
  565. }
  566. .back-icon {
  567. color: white;
  568. margin-right: 6px;
  569. font-weight: bold;
  570. }
  571. .header-titles {
  572. flex: 1;
  573. }
  574. .app-title {
  575. color: white;
  576. font-size: 2.25rem;
  577. font-weight: 800;
  578. margin: 0;
  579. text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
  580. background: linear-gradient(135deg, #fff 0%, #e2e8f0 100%);
  581. -webkit-background-clip: text;
  582. -webkit-text-fill-color: transparent;
  583. background-clip: text;
  584. }
  585. .app-subtitle {
  586. color: rgba(255, 255, 255, 0.9);
  587. font-size: 1rem;
  588. margin: 8px 0 0 0;
  589. font-weight: 500;
  590. }
  591. .header-stats {
  592. display: flex;
  593. gap: 24px;
  594. }
  595. .stat-item {
  596. display: flex;
  597. flex-direction: column;
  598. align-items: center;
  599. }
  600. .stat-number {
  601. color: white;
  602. font-size: 1.5rem;
  603. font-weight: 700;
  604. }
  605. .stat-label {
  606. color: rgba(255, 255, 255, 0.7);
  607. font-size: 0.8rem;
  608. margin-top: 4px;
  609. }
  610. /* 主布局 */
  611. .main-layout {
  612. display: flex;
  613. max-width: 1400px;
  614. margin: 0 auto;
  615. padding: 24px;
  616. gap: 24px;
  617. min-height: calc(100vh - 120px);
  618. align-items: flex-start;
  619. }
  620. /* 侧边栏样式 */
  621. .sidebar {
  622. width: 400px;
  623. flex-shrink: 0;
  624. display: flex;
  625. flex-direction: column;
  626. position: sticky;
  627. top: 24px;
  628. max-height: calc(100vh - 120px);
  629. overflow-y: auto;
  630. }
  631. .sidebar-header {
  632. background: white;
  633. padding: 20px;
  634. border-radius: 20px;
  635. margin-bottom: 16px;
  636. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  637. display: flex;
  638. justify-content: space-between;
  639. align-items: flex-start;
  640. flex-shrink: 0;
  641. }
  642. .sidebar-title h3 {
  643. margin: 0 0 6px 0;
  644. color: #2d3748;
  645. font-size: 1.25rem;
  646. font-weight: 700;
  647. }
  648. .sidebar-subtitle {
  649. margin: 0;
  650. color: #718096;
  651. font-size: 0.85rem;
  652. }
  653. .add-btn {
  654. border-radius: 12px;
  655. padding: 8px 16px;
  656. font-weight: 500;
  657. }
  658. .button-icon {
  659. margin-right: 6px;
  660. }
  661. .knowledge-list {
  662. flex: 1;
  663. display: flex;
  664. flex-direction: column;
  665. gap: 12px;
  666. overflow-y: auto;
  667. min-height: 0;
  668. }
  669. .document-card {
  670. background: white;
  671. border-radius: 16px;
  672. cursor: pointer;
  673. transition: all 0.3s ease;
  674. border: 2px solid transparent;
  675. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  676. overflow: hidden;
  677. }
  678. .document-card:hover {
  679. transform: translateY(-2px);
  680. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  681. }
  682. .document-card.active {
  683. border-color: #4299e1;
  684. background: linear-gradient(135deg, #ebf8ff 0%, #ffffff 100%);
  685. }
  686. .card-content {
  687. padding: 5px;
  688. display: flex;
  689. align-items: center;
  690. gap: 12px;
  691. }
  692. .document-icon {
  693. font-size: 1.25rem;
  694. width: 40px;
  695. height: 40px;
  696. display: flex;
  697. align-items: center;
  698. justify-content: center;
  699. background: #f7fafc;
  700. border-radius: 10px;
  701. flex-shrink: 0;
  702. }
  703. .document-info {
  704. flex: 1;
  705. min-width: 0;
  706. }
  707. .document-name {
  708. display: block;
  709. font-weight: 600;
  710. color: #2d3748;
  711. margin-bottom: 4px;
  712. font-size: 0.95rem;
  713. line-height: 1.4;
  714. overflow: hidden;
  715. text-overflow: ellipsis;
  716. white-space: nowrap;
  717. }
  718. .document-status {
  719. font-size: 0.75rem;
  720. font-weight: 500;
  721. padding: 2px 8px;
  722. border-radius: 8px;
  723. }
  724. .document-status.available {
  725. background: #f0fff4;
  726. color: #2f855a;
  727. border: 1px solid #c6f6d5;
  728. }
  729. .document-status.unavailable {
  730. background: #fed7d7;
  731. color: #c53030;
  732. border: 1px solid #feb2b2;
  733. }
  734. .document-actions {
  735. display: flex;
  736. align-items: center;
  737. }
  738. .delete-btn {
  739. padding: 6px;
  740. border-radius: 8px;
  741. color: #e53e3e;
  742. transition: all 0.3s ease;
  743. }
  744. .delete-btn:hover {
  745. background: #fed7d7;
  746. transform: scale(1.1);
  747. }
  748. .sidebar-footer {
  749. background: white;
  750. padding: 16px 20px;
  751. border-radius: 16px;
  752. margin-top: 16px;
  753. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  754. display: flex;
  755. justify-content: space-between;
  756. align-items: center;
  757. flex-shrink: 0;
  758. }
  759. .pagination-info {
  760. color: #718096;
  761. font-size: 0.85rem;
  762. font-weight: 500;
  763. }
  764. .custom-pagination {
  765. margin: 0;
  766. }
  767. .custom-pagination :deep(.el-pagination) {
  768. justify-content: flex-end;
  769. }
  770. /* 主内容区样式 */
  771. .content-area {
  772. flex: 1;
  773. display: flex;
  774. flex-direction: column;
  775. min-height: 0;
  776. }
  777. .content-section {
  778. background: white;
  779. border-radius: 24px;
  780. padding: 32px;
  781. box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  782. flex: 1;
  783. display: flex;
  784. flex-direction: column;
  785. }
  786. .content-header {
  787. display: flex;
  788. justify-content: space-between;
  789. align-items: flex-start;
  790. margin-bottom: 24px;
  791. padding-bottom: 20px;
  792. border-bottom: 1px solid #e2e8f0;
  793. }
  794. .content-title {
  795. flex: 1;
  796. }
  797. .content-title h2 {
  798. margin: 0 0 8px 0;
  799. color: #2d3748;
  800. font-size: 1.75rem;
  801. font-weight: 700;
  802. line-height: 1.4;
  803. }
  804. .content-status {
  805. font-size: 0.85rem;
  806. font-weight: 500;
  807. padding: 4px 12px;
  808. border-radius: 12px;
  809. }
  810. .content-status.available {
  811. background: #f0fff4;
  812. color: #2f855a;
  813. border: 1px solid #c6f6d5;
  814. }
  815. .content-status.unavailable {
  816. background: #fed7d7;
  817. color: #c53030;
  818. border: 1px solid #feb2b2;
  819. }
  820. .chunk-btn {
  821. border-radius: 12px;
  822. padding: 10px 20px;
  823. font-weight: 500;
  824. }
  825. .content-body {
  826. flex: 1;
  827. display: flex;
  828. flex-direction: column;
  829. }
  830. .content-text {
  831. background: #f7fafc;
  832. border-radius: 12px;
  833. border: 1px solid #e2e8f0;
  834. padding: 24px;
  835. flex: 1;
  836. overflow-y: auto;
  837. }
  838. .content-text pre {
  839. margin: 0;
  840. color: #4a5568;
  841. line-height: 1.6;
  842. font-size: 0.95rem;
  843. white-space: pre-wrap;
  844. word-wrap: break-word;
  845. font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  846. }
  847. .empty-content {
  848. flex: 1;
  849. display: flex;
  850. flex-direction: column;
  851. align-items: center;
  852. justify-content: center;
  853. padding: 60px 20px;
  854. text-align: center;
  855. }
  856. .empty-icon {
  857. font-size: 4rem;
  858. margin-bottom: 20px;
  859. opacity: 0.6;
  860. }
  861. .empty-content h3 {
  862. margin: 0 0 12px 0;
  863. color: #2d3748;
  864. font-size: 1.5rem;
  865. font-weight: 600;
  866. }
  867. .empty-content p {
  868. margin: 0;
  869. color: #718096;
  870. font-size: 1rem;
  871. line-height: 1.5;
  872. }
  873. /* 弹窗样式 */
  874. .content-dialog :deep(.el-dialog) {
  875. border-radius: 24px;
  876. overflow: hidden;
  877. box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2);
  878. }
  879. .content-dialog :deep(.el-dialog__header) {
  880. padding: 0;
  881. margin: 0;
  882. border-bottom: 1px solid #e2e8f0;
  883. }
  884. .content-dialog :deep(.el-dialog__headerbtn) {
  885. top: 24px;
  886. right: 24px;
  887. width: 32px;
  888. height: 32px;
  889. border-radius: 8px;
  890. background: #f7fafc;
  891. }
  892. .content-dialog :deep(.el-dialog__headerbtn:hover) {
  893. background: #e2e8f0;
  894. }
  895. .content-dialog :deep(.el-dialog__title) {
  896. font-size: 1.5rem;
  897. font-weight: 700;
  898. color: #2d3748;
  899. }
  900. .dialog-content {
  901. padding: 0;
  902. }
  903. .content-dialog :deep(.el-form-item__label) {
  904. font-weight: 600;
  905. color: #2d3748;
  906. }
  907. .content-dialog :deep(.el-input__wrapper) {
  908. border-radius: 12px;
  909. padding: 12px 16px;
  910. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  911. border: 1px solid #e2e8f0;
  912. }
  913. .content-dialog :deep(.el-textarea__inner) {
  914. border-radius: 12px;
  915. padding: 12px 16px;
  916. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  917. border: 1px solid #e2e8f0;
  918. font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  919. }
  920. .content-dialog :deep(.el-switch) {
  921. --el-switch-on-color: #4299e1;
  922. }
  923. .dialog-footer {
  924. padding: 0;
  925. margin-top: 24px;
  926. display: flex;
  927. justify-content: flex-end;
  928. gap: 12px;
  929. }
  930. .action-btn {
  931. border-radius: 12px;
  932. padding: 10px 24px;
  933. font-weight: 500;
  934. transition: all 0.3s ease;
  935. }
  936. .action-btn.secondary {
  937. border: 1px solid #e2e8f0;
  938. background: white;
  939. color: #718096;
  940. }
  941. .action-btn.secondary:hover {
  942. background: #f7fafc;
  943. border-color: #cbd5e0;
  944. }
  945. .action-btn.primary {
  946. background: #4299e1;
  947. border: 1px solid #4299e1;
  948. color: white;
  949. }
  950. .action-btn.primary:hover {
  951. background: #3182ce;
  952. border-color: #3182ce;
  953. transform: translateY(-1px);
  954. box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
  955. }
  956. /* 分段弹窗样式 */
  957. .chunk-content {
  958. padding: 0;
  959. }
  960. .chunk-table :deep(.el-table) {
  961. border-radius: 12px;
  962. overflow: hidden;
  963. }
  964. .chunk-table :deep(.el-table__header) {
  965. background: #f7fafc;
  966. }
  967. .chunk-table :deep(.el-table th) {
  968. background: #f7fafc;
  969. color: #2d3748;
  970. font-weight: 600;
  971. }
  972. .status-available {
  973. color: #2f855a;
  974. font-weight: 500;
  975. }
  976. .status-unavailable {
  977. color: #c53030;
  978. font-weight: 500;
  979. }
  980. .empty-chunks {
  981. padding: 60px 20px;
  982. text-align: center;
  983. }
  984. .empty-chunks .empty-icon {
  985. font-size: 3rem;
  986. margin-bottom: 16px;
  987. }
  988. .empty-chunks h3 {
  989. margin: 0 0 8px 0;
  990. color: #2d3748;
  991. font-size: 1.25rem;
  992. font-weight: 600;
  993. }
  994. .empty-chunks p {
  995. margin: 0;
  996. color: #718096;
  997. font-size: 0.95rem;
  998. }
  999. .pagination-section {
  1000. margin-top: 20px;
  1001. text-align: center;
  1002. padding-top: 20px;
  1003. border-top: 1px solid #e2e8f0;
  1004. }
  1005. /* 响应式设计 */
  1006. @media (max-width: 1200px) {
  1007. .main-layout {
  1008. flex-direction: column;
  1009. }
  1010. .sidebar {
  1011. width: 100%;
  1012. position: static;
  1013. max-height: none;
  1014. margin-bottom: 20px;
  1015. }
  1016. .sidebar-footer {
  1017. position: static;
  1018. }
  1019. }
  1020. @media (max-width: 768px) {
  1021. .app-title {
  1022. font-size: 1.75rem;
  1023. }
  1024. .header-content {
  1025. flex-direction: column;
  1026. gap: 16px;
  1027. text-align: center;
  1028. }
  1029. .header-main {
  1030. flex-direction: column;
  1031. gap: 12px;
  1032. align-items: center;
  1033. }
  1034. .header-back {
  1035. align-self: flex-start;
  1036. }
  1037. .header-titles {
  1038. text-align: center;
  1039. }
  1040. .header-stats {
  1041. gap: 32px;
  1042. }
  1043. .main-layout {
  1044. padding: 16px;
  1045. }
  1046. .content-section {
  1047. padding: 24px;
  1048. }
  1049. .content-header {
  1050. flex-direction: column;
  1051. gap: 16px;
  1052. align-items: stretch;
  1053. }
  1054. .chunk-btn {
  1055. width: 100%;
  1056. }
  1057. .sidebar-header {
  1058. flex-direction: column;
  1059. gap: 12px;
  1060. align-items: stretch;
  1061. }
  1062. .add-btn {
  1063. width: 100%;
  1064. }
  1065. }
  1066. @media (max-width: 480px) {
  1067. .app-title {
  1068. font-size: 1.5rem;
  1069. }
  1070. .content-section {
  1071. padding: 20px;
  1072. }
  1073. .sidebar {
  1074. width: 100%;
  1075. }
  1076. }
  1077. /* 侧边栏滚动条样式 */
  1078. .sidebar::-webkit-scrollbar {
  1079. width: 6px;
  1080. }
  1081. .sidebar::-webkit-scrollbar-track {
  1082. background: #f1f1f1;
  1083. border-radius: 3px;
  1084. }
  1085. .sidebar::-webkit-scrollbar-thumb {
  1086. background: #c1c1c1;
  1087. border-radius: 3px;
  1088. }
  1089. .sidebar::-webkit-scrollbar-thumb:hover {
  1090. background: #a8a8a8;
  1091. }
  1092. .knowledge-list::-webkit-scrollbar {
  1093. width: 4px;
  1094. }
  1095. .knowledge-list::-webkit-scrollbar-track {
  1096. background: transparent;
  1097. }
  1098. .knowledge-list::-webkit-scrollbar-thumb {
  1099. background: #d1d1d1;
  1100. border-radius: 2px;
  1101. }
  1102. /* 删除确认按钮样式 */
  1103. :deep(.delete-confirm-btn) {
  1104. background: #e53e3e;
  1105. border-color: #e53e3e;
  1106. }
  1107. :deep(.delete-confirm-btn:hover) {
  1108. background: #c53030;
  1109. border-color: #c53030;
  1110. }
  1111. :deep(.delete-cancel-btn) {
  1112. color: #718096;
  1113. border-color: #e2e8f0;
  1114. }
  1115. /* 分段弹窗样式 */
  1116. .chunk-dialog :deep(.el-dialog) {
  1117. border-radius: 24px;
  1118. overflow: hidden;
  1119. box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2);
  1120. max-height: 85vh;
  1121. display: flex;
  1122. flex-direction: column;
  1123. }
  1124. .chunk-dialog :deep(.el-dialog__header) {
  1125. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  1126. margin: 0;
  1127. padding: 24px 32px;
  1128. }
  1129. .chunk-dialog :deep(.el-dialog__title) {
  1130. color: white;
  1131. font-size: 1.5rem;
  1132. font-weight: 700;
  1133. }
  1134. .chunk-dialog :deep(.el-dialog__headerbtn) {
  1135. top: 24px;
  1136. right: 24px;
  1137. }
  1138. .chunk-dialog :deep(.el-dialog__headerbtn .el-dialog__close) {
  1139. color: white;
  1140. font-size: 1.25rem;
  1141. }
  1142. .chunk-dialog :deep(.el-dialog__body) {
  1143. padding: 0;
  1144. flex: 1;
  1145. display: flex;
  1146. flex-direction: column;
  1147. overflow: hidden;
  1148. }
  1149. .chunk-container {
  1150. display: flex;
  1151. flex-direction: column;
  1152. height: 100%;
  1153. min-height: 400px;
  1154. }
  1155. .chunk-header {
  1156. padding: 20px 24px;
  1157. background: #f8fafc;
  1158. border-bottom: 1px solid #e2e8f0;
  1159. }
  1160. .chunk-stats {
  1161. display: flex;
  1162. gap: 20px;
  1163. }
  1164. .stat-item {
  1165. color: #64748b;
  1166. font-size: 0.9rem;
  1167. font-weight: 500;
  1168. }
  1169. .chunk-content {
  1170. flex: 1;
  1171. overflow: auto;
  1172. padding: 0;
  1173. }
  1174. .chunk-table {
  1175. height: 100%;
  1176. border: none;
  1177. }
  1178. .chunk-table :deep(.el-table__header-wrapper) {
  1179. background: #f7fafc;
  1180. }
  1181. .chunk-table :deep(.el-table__header th) {
  1182. background: #f7fafc;
  1183. color: #374151;
  1184. font-weight: 600;
  1185. border-bottom: 1px solid #e2e8f0;
  1186. }
  1187. .chunk-table :deep(.el-table__body tr:hover > td) {
  1188. background-color: #f0f9ff;
  1189. }
  1190. .chunk-table :deep(.el-table__body td) {
  1191. border-bottom: 1px solid #f1f5f9;
  1192. padding: 12px 16px;
  1193. }
  1194. .chunk-id {
  1195. background: #e2e8f0;
  1196. color: #475569;
  1197. padding: 4px 8px;
  1198. border-radius: 6px;
  1199. font-size: 0.8rem;
  1200. font-weight: 600;
  1201. }
  1202. .summary-content {
  1203. max-height: 60px;
  1204. overflow: hidden;
  1205. line-height: 1.4;
  1206. display: -webkit-box;
  1207. -webkit-line-clamp: 3;
  1208. -webkit-box-orient: vertical;
  1209. color: #475569;
  1210. font-size: 0.9rem;
  1211. }
  1212. .full-content {
  1213. max-height: 120px;
  1214. overflow-y: auto;
  1215. line-height: 1.5;
  1216. color: #475569;
  1217. font-size: 0.9rem;
  1218. padding-right: 8px;
  1219. }
  1220. /* 自定义滚动条 */
  1221. .full-content::-webkit-scrollbar {
  1222. width: 4px;
  1223. }
  1224. .full-content::-webkit-scrollbar-track {
  1225. background: #f1f5f9;
  1226. border-radius: 2px;
  1227. }
  1228. .full-content::-webkit-scrollbar-thumb {
  1229. background: #cbd5e1;
  1230. border-radius: 2px;
  1231. }
  1232. .full-content::-webkit-scrollbar-thumb:hover {
  1233. background: #94a3b8;
  1234. }
  1235. .status-tag {
  1236. border: none;
  1237. font-weight: 600;
  1238. padding: 4px 12px;
  1239. border-radius: 20px;
  1240. }
  1241. .empty-chunks {
  1242. display: flex;
  1243. flex-direction: column;
  1244. align-items: center;
  1245. justify-content: center;
  1246. height: 300px;
  1247. color: #64748b;
  1248. }
  1249. .empty-icon {
  1250. font-size: 4rem;
  1251. margin-bottom: 16px;
  1252. opacity: 0.5;
  1253. }
  1254. .empty-chunks h3 {
  1255. margin: 0 0 8px 0;
  1256. color: #475569;
  1257. font-size: 1.25rem;
  1258. font-weight: 600;
  1259. }
  1260. .empty-chunks p {
  1261. margin: 0;
  1262. font-size: 0.9rem;
  1263. }
  1264. .pagination-section {
  1265. padding: 20px 24px;
  1266. background: #f8fafc;
  1267. border-top: 1px solid #e2e8f0;
  1268. display: flex;
  1269. justify-content: center;
  1270. }
  1271. .pagination-section :deep(.el-pagination) {
  1272. justify-content: center;
  1273. }
  1274. .pagination-section :deep(.el-pagination .btn-prev),
  1275. .pagination-section :deep(.el-pagination .btn-next) {
  1276. border-radius: 8px;
  1277. border: 1px solid #d1d5db;
  1278. }
  1279. .pagination-section :deep(.el-pagination .number) {
  1280. border-radius: 8px;
  1281. border: 1px solid #d1d5db;
  1282. }
  1283. .pagination-section :deep(.el-pagination .number.active) {
  1284. background: #3b82f6;
  1285. border-color: #3b82f6;
  1286. color: white;
  1287. }
  1288. .pagination-section :deep(.el-pagination .el-select .el-input) {
  1289. border-radius: 8px;
  1290. }
  1291. </style>