knowledgeSet.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173
  1. <template>
  2. <div class="project-search">
  3. <div class="file-categories">
  4. <el-button type="primary" @click="createNewFolder">
  5. <i class="el-icon-plus"></i> 新建目录
  6. </el-button>
  7. <el-tree
  8. :data="folders"
  9. :props="defaultProps"
  10. @node-click="handleNodeClick"
  11. ref="folderTree"
  12. :default-expanded-keys="['0']"
  13. node-key="id"
  14. highlight-current
  15. >
  16. <span class="custom-tree-node" slot-scope="{ node, data }">
  17. <span>
  18. <i class="el-icon-folder"></i>
  19. {{ node.label }}
  20. </span>
  21. <span v-if="data.id !== '001'" :class="getFileCountClass(data.id)"
  22. >{{ data.document_count }} files</span
  23. >
  24. <span
  25. class="folder-actions"
  26. v-if="data.id !== 0 && data.id !== '001'"
  27. >
  28. <el-tooltip
  29. class="item"
  30. effect="dark"
  31. content="编辑"
  32. placement="top"
  33. >
  34. <i class="el-icon-edit" @click.stop="editFolder(data)"></i>
  35. </el-tooltip>
  36. <el-tooltip
  37. class="item"
  38. effect="dark"
  39. content="删除"
  40. placement="top"
  41. >
  42. <i class="el-icon-delete" @click.stop="deleteFolder(data.id)"></i>
  43. </el-tooltip>
  44. </span>
  45. </span>
  46. </el-tree>
  47. </div>
  48. <div class="right">
  49. <dataSearch @bindSetQuery="setQuery"></dataSearch>
  50. <!-- <dataList
  51. :queryForm="queryForm"
  52. :allowEdit="checkAuth('/document/update')"
  53. :allowDelete="checkAuth('/document/delete')"
  54. ></dataList> -->
  55. <div
  56. class="data-list"
  57. v-loading="loading"
  58. element-loading-text="加载中..."
  59. >
  60. <div class="batch-actions" style="margin: 10px 0">
  61. <el-button
  62. type="primary"
  63. @click="batchAnalysis"
  64. :disabled="!selectedRows.length"
  65. >批量解析</el-button
  66. >
  67. <el-button
  68. type="primary"
  69. @click="batchModifyFolder"
  70. :disabled="!selectedRows.length"
  71. >批量修改目录</el-button
  72. >
  73. </div>
  74. <el-table
  75. ref="dataTable"
  76. :data="dataList"
  77. style="width: 100%"
  78. header-row-class-name="headerBg"
  79. empty-text="暂无数据"
  80. @selection-change="handleSelectionChange"
  81. @sort-change="handleSortChange"
  82. >
  83. <el-table-column type="selection" width="55"></el-table-column>
  84. <el-table-column prop="id" label="ID" align="center" width="80" />
  85. <el-table-column
  86. prop="name"
  87. label="文件名称"
  88. align="center"
  89. sortable="custom"
  90. >
  91. <template #default="scope">
  92. <span>{{ scope.row.name }}</span>
  93. </template>
  94. </el-table-column>
  95. <el-table-column prop="type" label="文件类型" align="center" />
  96. <el-table-column prop="chunk_num" label="分块数" align="center" />
  97. <el-table-column
  98. prop="create_time"
  99. label="上传时间"
  100. align="center"
  101. sortable="custom"
  102. />
  103. <el-table-column prop="run" label="解析状态" align="center">
  104. <template #default="scope">
  105. <div v-if="scope.row.run == 0">未解析</div>
  106. <div v-if="scope.row.run == 1">解析中</div>
  107. <div v-if="scope.row.run == 3">成功</div>
  108. <div v-if="scope.row.run == 4">失败</div>
  109. <div v-if="scope.row.run == 5">待处理</div>
  110. <div v-if="scope.row.run == 6">文件异常</div>
  111. </template>
  112. </el-table-column>
  113. <el-table-column label="操作" align="center" width="330">
  114. <template #default="scope">
  115. <div class="btns">
  116. <el-tooltip
  117. class="item"
  118. effect="dark"
  119. content="预览"
  120. placement="top"
  121. >
  122. <el-button
  123. type="text"
  124. size="small"
  125. circle
  126. icon="el-icon-view"
  127. v-if="checkAuth('/document/update')"
  128. style="font-size: 20px"
  129. @click="getName(scope.row)"
  130. ></el-button
  131. ></el-tooltip>
  132. <el-tooltip
  133. class="item"
  134. effect="dark"
  135. :content="
  136. scope.row.run === 1 || scope.row.run === 5
  137. ? '解析中'
  138. : '解析'
  139. "
  140. placement="top"
  141. >
  142. <el-button
  143. v-if="scope.row.run !== 1 && scope.row.run !== 5"
  144. type="text"
  145. icon="el-icon-caret-right"
  146. circle
  147. @click="analysis(scope.row)"
  148. style="font-size: 20px"
  149. >
  150. </el-button>
  151. <el-button
  152. v-else
  153. type="text"
  154. icon="el-icon-loading"
  155. circle
  156. :disabled="true"
  157. style="font-size: 20px"
  158. >
  159. </el-button>
  160. </el-tooltip>
  161. <el-tooltip
  162. class="item"
  163. effect="dark"
  164. content="解析方法"
  165. placement="top"
  166. >
  167. <el-button
  168. type="text"
  169. size="small"
  170. circle
  171. icon="el-icon-tickets"
  172. v-if="checkAuth('/document/update')"
  173. style="font-size: 20px"
  174. @click="Analytical(scope.row)"
  175. ></el-button
  176. ></el-tooltip>
  177. <el-tooltip
  178. class="item"
  179. effect="dark"
  180. content="修改文件名"
  181. placement="top"
  182. >
  183. <el-button
  184. type="text"
  185. size="small"
  186. circle
  187. icon="el-icon-edit"
  188. v-if="checkAuth('/document/update')"
  189. style="font-size: 20px"
  190. @click="btnEdit(scope.row)"
  191. ></el-button
  192. ></el-tooltip>
  193. <el-tooltip
  194. class="item"
  195. effect="dark"
  196. content="下载"
  197. placement="top"
  198. >
  199. <el-button
  200. type="text"
  201. size="small"
  202. circle
  203. icon="el-icon-download"
  204. @click="btnDown(scope.row)"
  205. :loading="scope.row.downloading"
  206. style="font-size: 20px"
  207. ></el-button
  208. ></el-tooltip>
  209. <el-tooltip
  210. class="item"
  211. effect="dark"
  212. content="删除"
  213. placement="top"
  214. >
  215. <el-button
  216. type="text"
  217. size="small"
  218. circle
  219. icon="el-icon-delete"
  220. v-if="checkAuth('/document/delete')"
  221. @click="btnDelete(scope.row.id)"
  222. style="font-size: 20px"
  223. ></el-button
  224. ></el-tooltip>
  225. </div>
  226. </template>
  227. </el-table-column>
  228. </el-table>
  229. <div class="page-info">
  230. <el-pagination
  231. :currentPage="queryForm.page"
  232. :page-size="queryForm.pageSize"
  233. :total="recordCount"
  234. :page-count="pageTotal"
  235. :page-sizes="[10, 20, 50, 100]"
  236. background
  237. layout="total,sizes, prev, pager, next"
  238. @current-change="ChangePage"
  239. @size-change="handleSizeChange"
  240. >
  241. </el-pagination>
  242. </div>
  243. <el-dialog
  244. title="修改名称"
  245. :visible.sync="dialogVisible"
  246. width="30%"
  247. :before-close="handleClose"
  248. >
  249. <el-form :model="wordForm" ref="wordRef" :rules="wordRules">
  250. <el-form-item label="文件名称:" prop="new_name">
  251. <el-input
  252. v-model="wordForm.new_name"
  253. placeholder="请输入文件名称"
  254. ></el-input>
  255. </el-form-item>
  256. </el-form>
  257. <span slot="footer" class="dialog-footer">
  258. <el-button @click="handleClose">取 消</el-button>
  259. <el-button type="primary" @click="submitBuck">确 定</el-button>
  260. </span>
  261. </el-dialog>
  262. <!-- 解析方法 -->
  263. <el-dialog
  264. title="解析方法"
  265. :visible.sync="anaDialogVisible"
  266. width="30%"
  267. :before-close="handleAnalClose"
  268. >
  269. <el-form
  270. :model="analForm"
  271. ref="analRef"
  272. label-position="top"
  273. :rules="analRules"
  274. >
  275. <el-form-item label="页码范围:" prop="new_name">
  276. <el-input-number
  277. v-model="analForm.start_page"
  278. controls-position="right"
  279. :min="1"
  280. ></el-input-number>
  281. <el-input-number
  282. v-model="analForm.end_page"
  283. controls-position="right"
  284. :min="1"
  285. style="margin-left: 10px"
  286. ></el-input-number>
  287. </el-form-item>
  288. <el-form-item label="块token数">
  289. <div class="block">
  290. <el-slider v-model="analForm.token_num" :max="2048" show-input>
  291. </el-slider>
  292. </div>
  293. </el-form-item>
  294. </el-form>
  295. <span slot="footer" class="dialog-footer">
  296. <el-button @click="handleAnalClose">取 消</el-button>
  297. <el-button type="primary" @click="submitAnal">确 定</el-button>
  298. </span>
  299. </el-dialog>
  300. <el-dialog
  301. title="新建目录"
  302. :visible.sync="newFolderDialogVisible"
  303. width="30%"
  304. >
  305. <el-form
  306. :model="newFolderForm"
  307. :rules="newFolderRules"
  308. ref="newFolderFormRef"
  309. >
  310. <el-form-item label="文件夹名称" prop="name">
  311. <el-input v-model="newFolderForm.name"></el-input>
  312. </el-form-item>
  313. </el-form>
  314. <span slot="footer" class="dialog-footer">
  315. <el-button @click="newFolderDialogVisible = false">取 消</el-button>
  316. <el-button type="primary" @click="submitNewFolder">确 定</el-button>
  317. </span>
  318. </el-dialog>
  319. <!-- 批量修改目录 -->
  320. <el-dialog
  321. title="批量修改目录"
  322. :visible.sync="batchModifyFolderDialogVisible"
  323. width="30%"
  324. >
  325. <el-form
  326. :model="batchModifyFolderForm"
  327. :rules="batchModifyFolderRules"
  328. ref="batchModifyFolderFormRef"
  329. >
  330. <el-form-item label="选择目录" prop="folderId">
  331. <el-select
  332. v-model="batchModifyFolderForm.folderId"
  333. placeholder="请选择目录"
  334. >
  335. <el-option
  336. v-for="folder in folders.filter((f) => f.id !== 0)"
  337. :key="folder.id"
  338. :label="folder.name"
  339. :value="folder.id"
  340. >
  341. </el-option>
  342. </el-select>
  343. </el-form-item>
  344. </el-form>
  345. <span slot="footer" class="dialog-footer">
  346. <el-button @click="batchModifyFolderDialogVisible = false"
  347. >取 消</el-button
  348. >
  349. <el-button type="primary" @click="submitBatchModifyFolder"
  350. >确 定</el-button
  351. >
  352. </span>
  353. </el-dialog>
  354. </div>
  355. </div>
  356. </div>
  357. </template>
  358. <script>
  359. import { mapGetters } from "vuex";
  360. import { dataSearch, dataList } from "./components";
  361. import {
  362. getBucketContents,
  363. deleteFile,
  364. nameGetUrl,
  365. renameFile,
  366. analysis,
  367. batchAnalysis,
  368. getRunStatus,
  369. insertType,
  370. updateType,
  371. selectTypeList,
  372. deleteType,
  373. Info,
  374. batchMove,
  375. } from "@/api/knowledge";
  376. export default {
  377. components: {
  378. dataSearch,
  379. dataList,
  380. },
  381. computed: {
  382. ...mapGetters(["roleInfo", "authList"]),
  383. },
  384. data() {
  385. return {
  386. queryForm: {
  387. page: 1,
  388. pageSize: 10,
  389. bucket_id: "",
  390. doc_type_id: 0,
  391. },
  392. typeForm: {
  393. page: 1,
  394. pageSize: 9999,
  395. kb_id: "",
  396. },
  397. folders: [{ id: "001", name: "全部", document_count: 0 }],
  398. defaultProps: {
  399. children: "children",
  400. label: "name",
  401. },
  402. // dataList 组件的数据
  403. loading: false,
  404. dialogVisible: false,
  405. currentDataId: 0,
  406. recordCount: 0,
  407. pageTotal: 1,
  408. dataList: [],
  409. currentData: {},
  410. wordForm: {
  411. document_id: "",
  412. new_name: "",
  413. },
  414. wordRules: {
  415. new_name: [
  416. { required: true, message: "请输入文件名称", trigger: "blur" },
  417. ],
  418. },
  419. anaDialogVisible: false,
  420. analForm: {
  421. start_page: 1,
  422. end_page: 10000,
  423. token_num: 0,
  424. },
  425. analRules: {},
  426. selectedRows: [],
  427. sortColumn: "",
  428. sortOrder: "",
  429. analysisStatusCheckers: {},
  430. newFolderDialogVisible: false,
  431. newFolderForm: {
  432. name: "",
  433. kb_id: "",
  434. status: 5,
  435. },
  436. newFolderRules: {
  437. name: [
  438. { required: true, message: "请输入文件夹名称", trigger: "blur" },
  439. ],
  440. },
  441. /* 批量修改目录 */
  442. batchModifyFolderDialogVisible: false,
  443. batchModifyFolderForm: {
  444. folderId: null,
  445. },
  446. batchModifyFolderRules: {
  447. folderId: [
  448. { required: true, message: "请选择目标目录", trigger: "change" },
  449. ],
  450. },
  451. };
  452. },
  453. mounted() {
  454. this.queryForm.bucket_id = this.$route.query.id;
  455. this.typeForm.kb_id = this.$route.query.id;
  456. this.$nextTick(() => {
  457. this.initializeTree();
  458. });
  459. this.typeList();
  460. this.search();
  461. },
  462. beforeDestroy() {
  463. Object.values(this.analysisStatusCheckers).forEach(clearTimeout);
  464. },
  465. methods: {
  466. /* 判断类名 */
  467. getFileCountClass(id) {
  468. const isSpecialFolder = id === 0 || id === "001";
  469. console.log(
  470. `ID: ${id}, Class: ${isSpecialFolder ? "file-counts" : "file-count"}`
  471. );
  472. return isSpecialFolder ? "file-counts" : "file-count";
  473. },
  474. /* 批量修改目录 */
  475. batchModifyFolder() {
  476. if (this.selectedRows.length === 0) {
  477. this.$message.warning("请选择要修改目录的文件");
  478. return;
  479. }
  480. this.batchModifyFolderDialogVisible = true;
  481. },
  482. submitBatchModifyFolder() {
  483. this.$refs.batchModifyFolderFormRef.validate((valid) => {
  484. if (valid) {
  485. const selectedIds = this.selectedRows.map((row) => row.id);
  486. console.log(selectedIds);
  487. batchMove({
  488. ids: JSON.stringify(selectedIds),
  489. doc_type_id: this.batchModifyFolderForm.folderId,
  490. })
  491. .then((res) => {
  492. if (res.status === 200) {
  493. this.$message.success("批量修改目录成功");
  494. this.batchModifyFolderDialogVisible = false;
  495. this.search(); // Refresh the document list
  496. this.typeList(); // Refresh the folder list
  497. this.clearSelection();
  498. } else {
  499. this.$message.error("批量修改目录失败");
  500. }
  501. })
  502. .catch((error) => {
  503. console.error("批量修改目录时出错:", error);
  504. this.$message.error("批量修改目录时出错");
  505. });
  506. }
  507. });
  508. },
  509. editFolder(folder) {
  510. this.$prompt("请输入新的文件夹名称", "编辑文件夹", {
  511. confirmButtonText: "确定",
  512. cancelButtonText: "取消",
  513. inputValue: folder.name,
  514. inputValidator: (value) => {
  515. if (!value) {
  516. return "文件夹名称不能为空";
  517. }
  518. return true;
  519. },
  520. })
  521. .then(({ value }) => {
  522. if (value !== folder.name) {
  523. const updatedFolder = { ...folder, name: value };
  524. updateType(updatedFolder)
  525. .then((res) => {
  526. if (res.status === 200) {
  527. this.$message.success("更新文件夹成功");
  528. this.typeList(); // 刷新文件夹列表
  529. } else {
  530. this.$message.error("更新文件夹失败");
  531. }
  532. })
  533. .catch((error) => {
  534. console.error("更新文件夹时出错:", error);
  535. this.$message.error("更新文件夹时出错");
  536. });
  537. }
  538. })
  539. .catch(() => {
  540. // 用户取消操作,不做任何处理
  541. });
  542. },
  543. deleteFolder(folderId) {
  544. this.$confirm("确定要删除该文件夹吗?删除后无法恢复。", "警告", {
  545. confirmButtonText: "确定",
  546. cancelButtonText: "取消",
  547. type: "warning",
  548. })
  549. .then(() => {
  550. deleteType({ id: folderId })
  551. .then((res) => {
  552. if (res.status === 200) {
  553. this.$message.success("删除文件夹成功");
  554. this.typeList(); // 刷新文件夹列表
  555. } else {
  556. this.$message.error("删除文件夹失败");
  557. }
  558. })
  559. .catch((error) => {
  560. console.error("删除文件夹时出错:", error);
  561. this.$message.error("删除文件夹时出错");
  562. });
  563. })
  564. .catch(() => {
  565. // 用户取消删除操作,不做任何处理
  566. });
  567. },
  568. typeList() {
  569. this.loading = true;
  570. selectTypeList(this.typeForm)
  571. .then((res) => {
  572. if (res.status === 200) {
  573. const folderList = res.data.dataList.map((folder) => ({
  574. ...folder,
  575. id: folder.id === 0 ? "001" : folder.id.toString(),
  576. document_count: folder.document_count || 0,
  577. }));
  578. this.folders = [
  579. {
  580. id: "001",
  581. name: "全部",
  582. document_count: folderList.reduce(
  583. (sum, folder) => sum + folder.document_count,
  584. 0
  585. ),
  586. },
  587. ...folderList.filter((folder) => folder.id !== "001"),
  588. {
  589. id: 0, // 给"其他"文件夹一个不同的 id
  590. name: "其他",
  591. document_count: 0, // 这个值会在 search 方法中更新
  592. },
  593. ];
  594. this.$nextTick(() => {
  595. this.initializeTree();
  596. });
  597. } else {
  598. this.$message.error("获取文件夹列表失败");
  599. }
  600. })
  601. .catch((error) => {
  602. console.error("获取文件夹列表时出错:", error);
  603. this.$message.error("获取文件夹列表时出错");
  604. })
  605. .finally(() => {
  606. this.loading = false;
  607. });
  608. },
  609. initializeTree() {
  610. this.$nextTick(() => {
  611. if (this.$refs.folderTree) {
  612. this.$refs.folderTree.setCurrentKey(0);
  613. this.handleNodeClick(this.folders[0]);
  614. }
  615. });
  616. },
  617. createNewFolder() {
  618. this.newFolderDialogVisible = true;
  619. },
  620. /* 新增 */
  621. submitNewFolder() {
  622. this.$refs.newFolderFormRef.validate((valid) => {
  623. if (valid) {
  624. this.newFolderForm.kb_id = this.$route.query.id;
  625. insertType(this.newFolderForm).then((res) => {
  626. if (res.status === 200) {
  627. this.$message.success("创建文件夹成功");
  628. this.newFolderDialogVisible = false;
  629. this.$refs.newFolderFormRef.resetFields();
  630. this.typeList(); // Refresh folder list
  631. } else {
  632. this.$message.error("创建文件夹失败");
  633. }
  634. });
  635. }
  636. });
  637. },
  638. initializeTree() {
  639. this.$nextTick(() => {
  640. if (this.$refs.folderTree) {
  641. this.$refs.folderTree.setCurrentKey("001");
  642. this.handleNodeClick(this.folders[0]);
  643. }
  644. });
  645. },
  646. checkAuth(path) {
  647. if (this.roleInfo.is_admin === 1) {
  648. return true;
  649. }
  650. return this.authList.some(
  651. (auth) => auth.type === 999 && auth.path === path
  652. );
  653. },
  654. setQuery(e) {
  655. this.queryForm = { ...this.queryForm, ...e };
  656. this.search();
  657. },
  658. handleNodeClick(data) {
  659. this.queryForm.doc_type_id = data.id === "001" ? "" : data.id;
  660. this.queryForm.page = 1; // Reset to first page when changing folder
  661. this.search();
  662. },
  663. /*列表事件 */
  664. /* 排序 */
  665. handleSortChange({ prop, order }) {
  666. this.sortColumn = prop;
  667. this.sortOrder = order;
  668. this.sortData();
  669. },
  670. sortData() {
  671. const { sortColumn, sortOrder } = this;
  672. if (!sortColumn || !sortOrder) {
  673. this.search(); // Reset to original order
  674. return;
  675. }
  676. this.dataList.sort((a, b) => {
  677. let comparison = 0;
  678. if (sortColumn === "name") {
  679. comparison = a.name.localeCompare(b.name, "zh-CN");
  680. } else if (sortColumn === "create_time") {
  681. comparison = new Date(a.create_time) - new Date(b.create_time);
  682. }
  683. return sortOrder === "ascending" ? comparison : -comparison;
  684. });
  685. },
  686. /* 批量解析 */
  687. handleSelectionChange(val) {
  688. this.selectedRows = val;
  689. },
  690. batchAnalysis() {
  691. const selectedIds = this.selectedRows.map((row) => row.id);
  692. if (selectedIds.length === 0) {
  693. this.$message.warning("请选择要解析的文件");
  694. return;
  695. }
  696. this.$confirm("确定要批量解析选中的文件吗?", "提示", {
  697. confirmButtonText: "确定",
  698. cancelButtonText: "取消",
  699. type: "warning",
  700. })
  701. .then(() => {
  702. this.loading = true;
  703. batchAnalysis({ ids: JSON.stringify(selectedIds) })
  704. .then((res) => {
  705. if (res.status === 200) {
  706. this.$message.success("批量解析任务已提交");
  707. this.updateAnalysisStatus(selectedIds);
  708. this.clearSelection();
  709. } else {
  710. this.$message.error("批量解析请求失败");
  711. }
  712. })
  713. .catch((error) => {
  714. console.error("批量解析错误:", error);
  715. this.$message.error("批量解析过程中出现错误");
  716. })
  717. .finally(() => {
  718. this.loading = false;
  719. });
  720. })
  721. .catch(() => {
  722. // 取消操作
  723. });
  724. },
  725. updateAnalysisStatus(ids) {
  726. this.dataList.forEach((row) => {
  727. if (ids.includes(row.id)) {
  728. row.run = 1; // 设置状态为"解析中"
  729. this.startAnalysisStatusChecker(row);
  730. }
  731. });
  732. },
  733. clearSelection() {
  734. this.$refs.dataTable.clearSelection();
  735. this.selectedRows = [];
  736. },
  737. /* 跳转页面 */
  738. getName(e) {
  739. /* this.$router.push({
  740. path: "/knowledge/category/infoList",
  741. query: { id: e.id, type: e.type },
  742. }); */
  743. let _this = this;
  744. let a = document.createElement("a");
  745. a.href = `#/infoList?id=${e.id}&type=${e.type}`; // 使用 hash
  746. a.target = "_blank";
  747. a.click();
  748. },
  749. /* 解析 */
  750. analysis(row) {
  751. if (row.run === 1 || row.run === 5) return;
  752. this.$set(row, "run", 1);
  753. analysis({
  754. document_id: row.id,
  755. start_page: row.start_page || 0,
  756. end_page: row.end_page || 1000,
  757. max_tokens: row.token_num,
  758. })
  759. .then((res) => {
  760. if (res.status === 200) {
  761. this.startAnalysisStatusChecker(row);
  762. } else {
  763. this.$message.error("解析请求失败");
  764. this.$set(row, "run", 0);
  765. }
  766. })
  767. .catch((error) => {
  768. console.error("解析错误:", error);
  769. this.$message.error("解析过程中出现错误");
  770. this.$set(row, "run", 0);
  771. });
  772. },
  773. startAnalysisStatusChecker(row) {
  774. // 如果已经有一个检查器在运行,先停止它
  775. if (this.analysisStatusCheckers[row.id]) {
  776. clearTimeout(this.analysisStatusCheckers[row.id]);
  777. }
  778. const checkStatus = () => {
  779. getRunStatus({ document_id: row.id })
  780. .then((res) => {
  781. if (res.status === 200) {
  782. const newStatus = Number(res.data.run);
  783. this.$set(row, "run", newStatus);
  784. if (newStatus === 3) {
  785. this.$message.success("解析成功");
  786. delete this.analysisStatusCheckers[row.id];
  787. } else if (newStatus === 4) {
  788. this.$message.error("解析失败");
  789. delete this.analysisStatusCheckers[row.id];
  790. } else if (newStatus === 1 || newStatus === 5) {
  791. // 继续检查
  792. this.analysisStatusCheckers[row.id] = setTimeout(
  793. checkStatus,
  794. 5000 + Math.random() * 5000
  795. );
  796. }
  797. } else {
  798. this.$message.error("获取解析状态失败");
  799. this.$set(row, "run", 0);
  800. delete this.analysisStatusCheckers[row.id];
  801. }
  802. })
  803. .catch((error) => {
  804. console.error("获取解析状态错误:", error);
  805. this.$message.error("获取解析状态时出现错误");
  806. this.$set(row, "run", 0);
  807. delete this.analysisStatusCheckers[row.id];
  808. });
  809. };
  810. // 立即开始第一次检查
  811. checkStatus();
  812. },
  813. handleAnalClose() {
  814. this.anaDialogVisible = false;
  815. },
  816. /* 确认 */
  817. submitAnal() {
  818. this.dataList.map((el) => {
  819. if (this.analForm.id == el.id) {
  820. el.start_page = this.analForm.start_page;
  821. el.end_page = this.analForm.end_page;
  822. el.token_num = this.analForm.token_num;
  823. }
  824. });
  825. this.anaDialogVisible = false;
  826. },
  827. /* 解析方法 */
  828. Analytical(e) {
  829. this.analForm.id = e.id;
  830. this.analForm.token_num = e.token_num;
  831. this.anaDialogVisible = true;
  832. },
  833. editWidth() {
  834. if (this.allowDelete && this.allowEdit) {
  835. return 200;
  836. }
  837. if (this.allowDelete || this.allowEdit) {
  838. return 120;
  839. }
  840. return 100;
  841. },
  842. onFocusVal(e) {
  843. let _this = this;
  844. _this.currentDataId = e.target.dataset.id;
  845. },
  846. onChangeVal(e) {
  847. let _this = this;
  848. let par = {
  849. id: _this.currentDataId,
  850. val: e,
  851. };
  852. },
  853. btnDelete(e) {
  854. let _this = this;
  855. let par = {
  856. document_id: e,
  857. };
  858. _this
  859. .$confirm("您是否确认删除该记录?", "提示", {
  860. confirmButtonText: "确认",
  861. cancelButtonText: "取消",
  862. type: "warning",
  863. })
  864. .then((res) => {
  865. deleteFile(par).then((res) => {
  866. if (res.status !== 200) return;
  867. _this.$message.success(res.data);
  868. _this.search();
  869. });
  870. })
  871. .catch(() => {});
  872. },
  873. searchData() {
  874. let _this = this;
  875. _this.dialogVisible = false;
  876. _this.search();
  877. },
  878. /* 关闭 */
  879. handleClose(done) {
  880. done();
  881. },
  882. /* 修改文件名 */
  883. btnEdit(e) {
  884. this.wordForm.new_name = e.name;
  885. this.wordForm.document_id = e.id;
  886. this.dialogVisible = true;
  887. },
  888. /* 提交 */
  889. submitBuck() {
  890. this.$refs.wordRef.validate((valid) => {
  891. if (valid) {
  892. renameFile(this.wordForm).then((res) => {
  893. if (res.status !== 200) return;
  894. this.search();
  895. this.dialogVisible = false;
  896. this.wordForm = {
  897. new_name: "",
  898. document_id: "",
  899. };
  900. });
  901. } else {
  902. this.$message.error("请填写文档名称");
  903. }
  904. });
  905. },
  906. //下载
  907. btnDown(e) {
  908. // Set downloading flag for this specific row
  909. this.$set(e, "downloading", true);
  910. nameGetUrl({
  911. document_id: e.id,
  912. })
  913. .then((res) => {
  914. if (res.status !== 200) {
  915. this.$message.error("获取下载链接失败");
  916. return;
  917. }
  918. return fetch(res.data.url);
  919. })
  920. .then((response) => response.blob())
  921. .then((blob) => {
  922. const url = window.URL.createObjectURL(blob);
  923. const link = document.createElement("a");
  924. link.href = url;
  925. link.download = e.name;
  926. document.body.appendChild(link);
  927. link.click();
  928. setTimeout(() => {
  929. document.body.removeChild(link);
  930. window.URL.revokeObjectURL(url);
  931. }, 100);
  932. })
  933. .catch((error) => {
  934. console.error("下载出错:", error);
  935. this.$message.error("下载失败,请稍后重试");
  936. })
  937. .finally(() => {
  938. // Reset downloading flag when done
  939. this.$set(e, "downloading", false);
  940. });
  941. },
  942. handleClose() {
  943. let _this = this;
  944. _this.currentData = {};
  945. _this.dialogVisible = false;
  946. },
  947. //搜索
  948. search() {
  949. let _this = this;
  950. _this.loading = true;
  951. getBucketContents(_this.queryForm)
  952. .then((res) => {
  953. if (res.status !== 200) return;
  954. _this.dataList = res.data.documents.map((el) => ({
  955. ...el,
  956. token_num: 256,
  957. start_page: _this.analForm.start_page,
  958. end_page: _this.analForm.end_page,
  959. }));
  960. _this.pageTotal = res.data.pagination.total_count;
  961. _this.recordCount = res.data.pagination.total_count;
  962. _this.queryForm.pageSize = res.data.pagination.total_size;
  963. _this.loading = false;
  964. // Count documents with doc_type_id === 0 or null
  965. const unclassifiedCount = res.data.documents.filter(
  966. (doc) => !doc.doc_type_id || doc.doc_type_id === 0
  967. ).length;
  968. // Update "其他" folder count
  969. const otherFolder = _this.folders.find((f) => f.id === 0);
  970. if (otherFolder) {
  971. otherFolder.document_count = unclassifiedCount;
  972. }
  973. // Update the selected folder count if it's not "全部" or "其他"
  974. if (
  975. _this.queryForm.doc_type_id &&
  976. _this.queryForm.doc_type_id !== 0
  977. ) {
  978. const selectedFolder = _this.folders.find(
  979. (f) => f.id === _this.queryForm.doc_type_id
  980. );
  981. if (selectedFolder) {
  982. selectedFolder.document_count = _this.recordCount;
  983. }
  984. }
  985. // Initialize the analysis status for each row
  986. _this.dataList.forEach((row) => {
  987. if (row.run === 1) {
  988. _this.startAnalysisStatusChecker(row);
  989. }
  990. });
  991. })
  992. .catch(() => {
  993. _this.loading = false;
  994. });
  995. },
  996. //修改分页
  997. ChangePage(e) {
  998. let _this = this;
  999. _this.queryForm.page = e;
  1000. _this.search();
  1001. },
  1002. handleSizeChange(val) {
  1003. this.queryForm.pageSize = val;
  1004. this.queryForm.page = 1; // Reset to first page when changing page size
  1005. this.search();
  1006. },
  1007. },
  1008. };
  1009. </script>
  1010. <style lang="scss" scoped>
  1011. .project-search {
  1012. display: flex;
  1013. height: calc(100vh - 100px); // 假设顶部导航栏高度为60px
  1014. background-color: #f9fafb;
  1015. .custom-tree-node {
  1016. width: 100%;
  1017. display: flex;
  1018. align-items: center;
  1019. justify-content: space-between;
  1020. padding: 8px 0;
  1021. position: relative;
  1022. .el-icon-folder {
  1023. color: #409eff;
  1024. margin-right: 8px;
  1025. }
  1026. .file-count {
  1027. color: #909399;
  1028. font-size: 12px;
  1029. padding: 2px 6px;
  1030. border-radius: 10px;
  1031. transition: opacity 0.3s;
  1032. }
  1033. .file-counts {
  1034. color: #909399;
  1035. font-size: 12px;
  1036. padding: 2px 6px;
  1037. border-radius: 10px;
  1038. transition: opacity 0.3s;
  1039. }
  1040. .folder-actions {
  1041. position: absolute;
  1042. right: 0;
  1043. top: 50%;
  1044. transform: translateY(-50%);
  1045. display: none;
  1046. padding-left: 10px;
  1047. i {
  1048. font-size: 16px;
  1049. margin-left: 8px;
  1050. color: #606266;
  1051. cursor: pointer;
  1052. &:hover {
  1053. color: #409eff;
  1054. }
  1055. }
  1056. }
  1057. &:hover {
  1058. .file-count {
  1059. opacity: 0;
  1060. }
  1061. .folder-actions {
  1062. display: inline-block;
  1063. }
  1064. }
  1065. }
  1066. .el-tree-node__content:hover {
  1067. background-color: #f5f7fa;
  1068. }
  1069. .el-tree-node__content:hover {
  1070. background-color: #f5f7fa;
  1071. }
  1072. .file-categories {
  1073. flex: 0 0 280px;
  1074. padding: 20px;
  1075. background-color: #ffffff;
  1076. box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
  1077. overflow-y: auto;
  1078. .el-button {
  1079. width: 100%;
  1080. margin-bottom: 20px;
  1081. }
  1082. .el-tree {
  1083. background-color: transparent;
  1084. }
  1085. }
  1086. .right {
  1087. flex: 1;
  1088. padding: 20px;
  1089. overflow-y: auto;
  1090. }
  1091. .custom-tree-node {
  1092. width: 100%;
  1093. display: flex;
  1094. align-items: center;
  1095. justify-content: space-between;
  1096. padding: 8px 0;
  1097. .el-icon-folder {
  1098. color: #409eff;
  1099. margin-right: 8px;
  1100. }
  1101. .file-count {
  1102. color: #909399;
  1103. font-size: 12px;
  1104. background-color: #f0f2f5;
  1105. padding: 2px 6px;
  1106. border-radius: 10px;
  1107. }
  1108. }
  1109. }
  1110. @import "./components/dataList.scss";
  1111. </style>