list.vue 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. <template>
  2. <div class="gpu-list">
  3. <!-- 顶部筛选区域 -->
  4. <div class="filter-section">
  5. <el-select
  6. v-model="selectedBucket"
  7. placeholder="请选择知识库"
  8. @change="handleBucketChange"
  9. >
  10. <el-option
  11. v-for="item in buckets"
  12. :key="item.id"
  13. :label="item.name"
  14. :value="item.id"
  15. />
  16. </el-select>
  17. <el-select
  18. v-model="selectedType"
  19. placeholder="请选择目录"
  20. :disabled="!selectedBucket"
  21. class="ml-15"
  22. >
  23. <el-option
  24. v-for="item in types"
  25. :key="item.id"
  26. :label="item.name"
  27. :value="item.id"
  28. />
  29. </el-select>
  30. <el-button
  31. type="primary"
  32. :loading="isSyncing"
  33. :disabled="!selectedType"
  34. class="ml-15"
  35. @click="handleSyncData"
  36. >
  37. {{ isSyncing ? "同步中..." : "同步数据" }}
  38. </el-button>
  39. </div>
  40. <el-tabs v-model="activeName" @tab-click="handleClick">
  41. <!-- 原有的文献列表 tab -->
  42. <el-tab-pane label="文献列表" name="first">
  43. <!-- 添加搜索表单 -->
  44. <div class="search-form">
  45. <el-form
  46. :inline="true"
  47. :model="searchForm"
  48. class="demo-form-inline"
  49. label-width="80px"
  50. ><el-form-item label="内容:">
  51. <el-input
  52. style="width: 260px"
  53. v-model="searchForm.name"
  54. clearable
  55. placeholder="请输入名称\文献编号\ID"
  56. ></el-input>
  57. </el-form-item>
  58. <el-form-item label="创建时间:">
  59. <el-date-picker
  60. v-model="searchForm.createTimeRange"
  61. type="daterange"
  62. range-separator="至"
  63. start-placeholder="开始日期"
  64. end-placeholder="结束日期"
  65. value-format="yyyy-MM-dd"
  66. />
  67. </el-form-item>
  68. <el-form-item label="更新时间:">
  69. <el-date-picker
  70. v-model="searchForm.updateTimeRange"
  71. type="daterange"
  72. range-separator="至"
  73. start-placeholder="开始日期"
  74. end-placeholder="结束日期"
  75. value-format="yyyy-MM-dd"
  76. />
  77. </el-form-item>
  78. <!-- <el-form-item label="文档类型:">
  79. <el-select
  80. v-model="searchForm.type"
  81. clearable
  82. placeholder="请选择文档类型"
  83. >
  84. <el-option label="PDF" value="pdf" />
  85. <el-option label="DOCX" value="docx" />
  86. <el-option label="DOC" value="doc" />
  87. <el-option label="TXT" value="txt" />
  88. </el-select>
  89. </el-form-item> -->
  90. <el-form-item label="处理状态:">
  91. <el-select
  92. v-model="searchForm.processing_status"
  93. clearable
  94. placeholder="请选择处理状态"
  95. >
  96. <el-option
  97. v-for="(value, key) in STATUS_MAP"
  98. :key="key"
  99. :label="value.text"
  100. :value="key"
  101. />
  102. </el-select>
  103. </el-form-item>
  104. <!-- <el-form-item label="文本时间:">
  105. <el-date-picker
  106. v-model="searchForm.textTimeRange"
  107. type="daterange"
  108. range-separator="至"
  109. start-placeholder="开始日期"
  110. end-placeholder="结束日期"
  111. value-format="yyyy-MM-dd"
  112. />
  113. </el-form-item> -->
  114. <el-form-item label="标签:">
  115. <el-select
  116. v-model="searchForm.id"
  117. multiple
  118. filterable
  119. remote
  120. reserve-keyword
  121. :remote-method="searchFormTags"
  122. :loading="searchTagsLoading"
  123. placeholder="请输入标签关键词"
  124. clearable
  125. >
  126. <el-option
  127. v-for="tag in searchTagOptions"
  128. :key="tag.id"
  129. :label="tag.name"
  130. :value="tag.name"
  131. />
  132. </el-select>
  133. </el-form-item>
  134. <el-form-item>
  135. <el-button type="primary" @click="handleSearch">搜索</el-button>
  136. <el-button @click="resetSearch" type="danger">重置</el-button>
  137. </el-form-item>
  138. </el-form>
  139. </div>
  140. <div class="batch-operations">
  141. <el-button
  142. type="primary"
  143. @click="handleBatchProcess"
  144. :loading="isProcessing"
  145. :disabled="selectedRows.length === 0"
  146. class="ml-15"
  147. >
  148. {{ isProcessing ? "处理中..." : "批量处理文献" }}
  149. </el-button>
  150. <el-button
  151. type="primary"
  152. @click="handleBatchTag"
  153. :disabled="selectedRows.length == 0"
  154. >批量绑定标签</el-button
  155. >
  156. <el-button
  157. type="primary"
  158. @click="handleExport"
  159. :disabled="selectedRows.length == 0"
  160. >文件导出</el-button
  161. >
  162. </div>
  163. <!-- 添加选择提示区域 :title="selectionText"-->
  164. <div class="selection-control" v-if="selectedRows.length > 0">
  165. <el-alert
  166. type="info"
  167. show-icon
  168. :closable="false"
  169. style="margin-bottom: 10px"
  170. >
  171. <template v-if="!isAllDataSelected">
  172. <span
  173. type="text"
  174. style="cursor: pointer; color: #409eff"
  175. @click="handleSelectAllData"
  176. >
  177. 选择全部{{ total }}条数据
  178. </span>
  179. </template>
  180. <template v-else>
  181. <span
  182. type="text"
  183. style="cursor: pointer; color: #409eff"
  184. @click="handleCancelAllSelection"
  185. >
  186. 取消全选
  187. </span>
  188. </template>
  189. </el-alert>
  190. </div>
  191. <!-- 数据表格区域 -->
  192. <el-table
  193. v-loading="isLoading"
  194. :data="gpuList"
  195. style="width: 100%"
  196. @selection-change="handleSelectionChange"
  197. @select-all="handleSelectAll"
  198. ref="gpuTable"
  199. :row-key="getRowKey"
  200. >
  201. <el-table-column
  202. type="selection"
  203. width="55"
  204. :selectable="checkSelectable"
  205. :reserve-selection="true"
  206. />
  207. <el-table-column prop="id" label="ID" width="80" />
  208. <el-table-column prop="identifier" label="文献编号"></el-table-column>
  209. <el-table-column prop="name" label="名称" sortable>
  210. <!-- <template #default="{ row }">
  211. <el-link @click="handleNameClick(row)">{{ row.name }}</el-link>
  212. </template> -->
  213. </el-table-column>
  214. <el-table-column prop="tags" label="已绑定标签">
  215. <template #default="{ row }">
  216. <el-tag
  217. v-for="(item, index) in row.tags"
  218. :key="index"
  219. type=""
  220. style="margin-right: 5px;margin-bottom:5px;"
  221. >
  222. {{ item.name }}
  223. </el-tag>
  224. </template>
  225. </el-table-column>
  226. <el-table-column prop="type" label="文档类型" width="100" />
  227. <el-table-column
  228. prop="create_time"
  229. label="创建时间"
  230. width="200"
  231. sortable
  232. />
  233. <el-table-column prop="status" label="处理状态" width="100">
  234. <template #default="{ row }">
  235. <el-tag :type="getStatusType(row.processing_status)">
  236. {{ getStatusText(row.processing_status) }}
  237. </el-tag>
  238. </template>
  239. </el-table-column>
  240. <el-table-column label="操作" width="100">
  241. <template #default="{ row }">
  242. <el-dropdown trigger="click">
  243. <el-button
  244. type="text"
  245. size="small"
  246. circle
  247. class="operation-button"
  248. style="font-size: 15px; margin-left: 10px"
  249. icon="el-icon-more"
  250. ></el-button>
  251. <template #dropdown>
  252. <el-dropdown-menu>
  253. <el-dropdown-item @click.native="handleNameClick(row)">
  254. <i class="el-icon-view"></i> 查看
  255. </el-dropdown-item>
  256. <el-dropdown-item @click.native="handlePreview(row)">
  257. <i class="el-icon-tickets"></i> 预览
  258. </el-dropdown-item>
  259. <el-dropdown-item
  260. @click.native="handleSingleProcess(row)"
  261. v-if="row.processing_status !== 2"
  262. >
  263. <i class="el-icon-caret-right"></i> 处理文献
  264. </el-dropdown-item>
  265. <el-dropdown-item
  266. @click.native="handleReprocess(row)"
  267. v-if="row.processing_status === 2"
  268. >
  269. <i class="el-icon-refresh"></i> 重新处理
  270. </el-dropdown-item>
  271. <!-- <el-dropdown-item
  272. @click.native="firstHandleUpload(scope.$index, scope.row)"
  273. >
  274. <i class="el-icon-refresh"></i> 批量处理
  275. </el-dropdown-item> -->
  276. <!-- <el-dropdown-item
  277. @click.native="firstHandleDelete(scope.$index, scope.row)"
  278. >
  279. <i class="el-icon-delete"></i> 删除
  280. </el-dropdown-item> -->
  281. </el-dropdown-menu>
  282. </template>
  283. </el-dropdown>
  284. </template>
  285. </el-table-column>
  286. </el-table>
  287. <!-- 分页器 -->
  288. <el-pagination
  289. class="pagination"
  290. @size-change="handleSizeChange"
  291. @current-change="handleCurrentChange"
  292. :current-page="currentPage"
  293. :page-sizes="[10, 20, 50, 100]"
  294. :page-size="pageSize"
  295. layout="total, sizes, prev, pager, next, jumper"
  296. :total="total"
  297. />
  298. </el-tab-pane>
  299. <!-- 添加 SOTA详情 tab -->
  300. <el-tab-pane label="SOTA详情" name="second">
  301. <el-table :data="sotaDetailsList" style="width: 100%">
  302. <el-table-column label="Identifier" prop="identifier" />
  303. <el-table-column label="Device" prop="device" />
  304. <el-table-column label="Outcomes" prop="outcomes" />
  305. <el-table-column label="Unit for analysis" prop="unit" />
  306. <el-table-column label="Mean" prop="mean" />
  307. <!-- 操作列与原有类似 -->
  308. </el-table>
  309. </el-tab-pane>
  310. <!-- 添加 SOTA汇总表 tab -->
  311. <el-tab-pane label="SOTA汇总表" name="third">
  312. <el-table :data="sotaSummaryList" style="width: 100%">
  313. <el-table-column label="Summary" prop="summary" />
  314. <el-table-column label="EqMD" prop="eqmd" />
  315. <el-table-column label="Pain:VAS" prop="painVas" />
  316. <!-- 操作列与原有类似 -->
  317. </el-table>
  318. </el-tab-pane>
  319. </el-tabs>
  320. <!-- 添加标签对话框 -->
  321. <el-dialog
  322. title="批量绑定标签"
  323. :visible.sync="tagDialogVisible"
  324. width="800px"
  325. >
  326. <el-form :model="tagForm" label-width="150px">
  327. <el-form-item label="标签分类:">
  328. <el-select
  329. v-model="tagForm.category_id"
  330. placeholder="请选择分类"
  331. clearable
  332. style="width: 350px"
  333. @change="handleCategoryChange"
  334. >
  335. <el-option label="全部分类" value="" />
  336. <el-option
  337. v-for="item in categoriesList"
  338. :key="item.id"
  339. :label="item.name"
  340. :value="item.id"
  341. />
  342. </el-select>
  343. </el-form-item>
  344. <el-form-item label="标签:">
  345. <el-select
  346. style="width: 350px"
  347. v-model="tagForm.tags"
  348. multiple
  349. filterable
  350. remote
  351. reserve-keyword
  352. :remote-method="handleTagSearch"
  353. :loading="tagsLoading"
  354. placeholder="请输入关键词搜索标签"
  355. >
  356. <el-option
  357. v-for="tag in tagOptions"
  358. :key="tag.id"
  359. :label="tag.name"
  360. :value="tag.id"
  361. />
  362. </el-select>
  363. </el-form-item>
  364. </el-form>
  365. <template #footer>
  366. <span class="dialog-footer">
  367. <el-button @click="tagDialogVisible = false">取消</el-button>
  368. <el-button type="primary" @click="submitBatchTag">确定</el-button>
  369. </span>
  370. </template>
  371. </el-dialog>
  372. </div>
  373. </template>
  374. <script>
  375. import { debounce } from "lodash-es";
  376. import {
  377. GET_list,
  378. GET_sync,
  379. listBuckets,
  380. selectTypeList,
  381. getDetail,
  382. search_tags,
  383. edit_tags,
  384. search,
  385. process_model_generation,
  386. process_model_regeneration,
  387. categories,
  388. } from "@/api/knowledge";
  389. import axios from "axios";
  390. // 状态映射表
  391. const STATUS_MAP = {
  392. 0: { text: "未处理", type: "info" },
  393. 1: { text: "处理中", type: "warning" },
  394. 2: { text: "处理完成", type: "success" },
  395. 3: { text: "处理失败", type: "danger" },
  396. };
  397. export default {
  398. name: "GpuList",
  399. data() {
  400. return {
  401. gpuList: [],
  402. currentPage: 1,
  403. pageSize: 10,
  404. total: 0,
  405. isLoading: false,
  406. isSyncing: false,
  407. buckets: [],
  408. types: [],
  409. selectedBucket: null,
  410. selectedType: null,
  411. activeName: "first", // 当前激活的tab
  412. sotaDetailsList: [], // SOTA详情列表数据
  413. sotaSummaryList: [], // SOTA汇总表数据
  414. searchForm: {
  415. name: "",
  416. createTimeRange: [],
  417. textTimeRange: [],
  418. id: [],
  419. type: "", // 新增文档类型
  420. processing_status: "", // 新增处理状态
  421. updateTimeRange: [], // 新增更新时间范围
  422. },
  423. selectedRows: [], // 选中的行
  424. tagDialogVisible: false, // 标签对话框显示状态
  425. categoriesList: [], // 添加分类列表
  426. tagForm: {
  427. tags: [], // 选中的标签ID数组
  428. category_id: null, // 添加分类ID
  429. },
  430. tagOptions: [], // 标签选项
  431. tagsLoading: false, // 标签加载状态
  432. searchTagOptions: [], // 搜索表单的标签选项
  433. searchTagsLoading: false, // 搜索表单的标签加载状态
  434. isProcessing: false, // 批量处理的加载状态
  435. pollingTimers: {}, // 新增:存储每个文献的轮询定时器
  436. STATUS_MAP: {
  437. 0: { text: "未处理", type: "info" },
  438. 1: { text: "处理中", type: "warning" },
  439. 2: { text: "处理完成", type: "success" },
  440. 3: { text: "处理失败", type: "danger" },
  441. },
  442. multipleSelection: [], // 存储跨页选中的数据
  443. selectedRows: [], // 当前页选中的行
  444. allSelectedRows: new Map(), // 存储所有选中的行
  445. isAllDataSelected: false, // 是否选中所有数据
  446. };
  447. },
  448. created() {
  449. // 使用防抖处理数据加载
  450. this.debouncedFetchGpuList = debounce(this.fetchGpuList, 300);
  451. },
  452. mounted() {
  453. this.initialize();
  454. },
  455. computed: {
  456. // 添加选择提示文本计算属性
  457. selectionText() {
  458. const selectedCount = this.isAllDataSelected
  459. ? this.total
  460. : this.allSelectedRows.size;
  461. return `已选择 ${selectedCount} 项`;
  462. },
  463. },
  464. methods: {
  465. // 添加获取分类列表方法
  466. async fetchCategories() {
  467. try {
  468. const res = await categories();
  469. if (res.status === 200) {
  470. this.categoriesList = res.data.data.items;
  471. }
  472. } catch (error) {
  473. console.error("获取分类列表失败:", error);
  474. this.$message.error("获取分类列表失败");
  475. }
  476. },
  477. // 添加导出处理方法
  478. async handleExport() {
  479. try {
  480. const ids = this.selectedRows.map((row) => row.id);
  481. // 添加确认对话框
  482. await this.$confirm("确认要导出选中的文献吗?", "提示", {
  483. confirmButtonText: "确定",
  484. cancelButtonText: "取消",
  485. type: "warning",
  486. });
  487. // 调用导出接口
  488. const response = await axios({
  489. url: `${process.env.VUE_APP_BASE_API}/literature-data/export/`,
  490. method: "POST",
  491. data: { ids },
  492. responseType: "blob", // 指定响应类型为 blob
  493. });
  494. // 创建下载链接
  495. const blob = new Blob([response.data], {
  496. type: response.headers["content-type"],
  497. });
  498. const url = window.URL.createObjectURL(blob);
  499. const link = document.createElement("a");
  500. link.href = url;
  501. // 从响应头获取文件名,如果没有则使用默认名称
  502. const filename = response.headers["content-disposition"]
  503. ? decodeURIComponent(
  504. response.headers["content-disposition"].split("filename=")[1]
  505. )
  506. : "文献导出.xlsx";
  507. link.setAttribute("download", filename);
  508. document.body.appendChild(link);
  509. link.click();
  510. document.body.removeChild(link);
  511. window.URL.revokeObjectURL(url);
  512. this.$message.success("导出成功");
  513. } catch (error) {
  514. if (error === "cancel") return;
  515. this.$message.error(error.message || "导出失败");
  516. console.error("导出错误:", error);
  517. }
  518. },
  519. /* 重复处理 */
  520. async handleReprocess(row) {
  521. try {
  522. await this.$confirm("确认要重新处理该文献吗?", "提示", {
  523. confirmButtonText: "确定",
  524. cancelButtonText: "取消",
  525. type: "warning",
  526. });
  527. // 调用重新处理的接口
  528. const res = await process_model_regeneration([row.id]); // 添加第二个参数表示重新处理
  529. if (res.status === 200) {
  530. this.$message.success("重新处理任务已提交");
  531. await this.fetchGpuList(); // 刷新列表
  532. } else {
  533. throw new Error(res.message || "重新处理失败");
  534. }
  535. } catch (error) {
  536. if (error === "cancel") {
  537. return;
  538. }
  539. this.$message.error(error.message || "重新处理提交失败");
  540. console.error("重新处理错误:", error);
  541. }
  542. },
  543. // 在中添加单个处理方法
  544. async handleSingleProcess(row) {
  545. try {
  546. await this.$confirm("确认要处理该文献吗?", "提示", {
  547. confirmButtonText: "确定",
  548. cancelButtonText: "取消",
  549. type: "warning",
  550. });
  551. const res = await process_model_generation([row.id]);
  552. if (res.status === 200) {
  553. this.$message.success("处��任务已提交");
  554. await this.fetchGpuList(); // 刷新列表
  555. } else {
  556. throw new Error(res.message || "处理失败");
  557. }
  558. } catch (error) {
  559. if (error === "cancel") {
  560. return;
  561. }
  562. this.$message.error(error.message || "处理提交失败");
  563. console.error("处理错误:", error);
  564. }
  565. },
  566. /* 搜索栏模糊查找 */
  567. async searchFormTags(query) {
  568. if (query !== "") {
  569. this.searchTagsLoading = true;
  570. try {
  571. const { data } = await search_tags({ keyword: query });
  572. this.searchTagOptions = data.data.items;
  573. } catch (error) {
  574. this.$message.error("搜索标签失败");
  575. } finally {
  576. this.searchTagsLoading = false;
  577. }
  578. } else {
  579. this.searchTagOptions = [];
  580. }
  581. },
  582. // 获取行的唯一键
  583. getRowKey(row) {
  584. return row.id;
  585. },
  586. // 处理表格选择变化
  587. handleSelectionChange(selection) {
  588. this.selectedRows = selection;
  589. if (!this.isAllDataSelected) {
  590. // 更新选中数据集合
  591. selection.forEach((row) => {
  592. this.allSelectedRows.set(row.id, row);
  593. });
  594. // 当前页取消选中的数据从总选中集合中移除
  595. this.gpuList.forEach((row) => {
  596. if (!selection.find((item) => item.id === row.id)) {
  597. this.allSelectedRows.delete(row.id);
  598. }
  599. });
  600. }
  601. },
  602. // 处理全选按钮
  603. handleSelectAll(selection) {
  604. if (selection.length > 0) {
  605. this.$message.success(`已全选当前页${selection.length}条数据`);
  606. } else {
  607. // 取消当前页所有选择
  608. this.gpuList.forEach((row) => {
  609. this.allSelectedRows.delete(row.id);
  610. });
  611. this.$message.info("已取消选中当前页全部数据");
  612. }
  613. },
  614. // 选择全部数据
  615. async handleSelectAllData() {
  616. try {
  617. // 获取所有数据的查询参数
  618. const params = {
  619. page: 1,
  620. page_size: this.total,
  621. keyword: this.searchForm.name,
  622. tag_names: this.searchForm.id,
  623. create_time_start: this.searchForm.createTimeRange?.[0] || "",
  624. create_time_end: this.searchForm.createTimeRange?.[1] || "",
  625. update_time_start: this.searchForm.updateTimeRange?.[0] || "",
  626. update_time_end: this.searchForm.updateTimeRange?.[1] || "",
  627. processing_status: this.searchForm.processing_status || null,
  628. };
  629. const response = await axios.post(
  630. `${process.env.VUE_APP_BASE_API}/literature-data/search/`,
  631. params
  632. );
  633. if (response.status === 200) {
  634. const allData = response.data.data.list;
  635. // 清空之前的选择
  636. this.allSelectedRows.clear();
  637. // 将所有数据添加到选中集合
  638. allData.forEach((item) => {
  639. this.allSelectedRows.set(item.id, item);
  640. });
  641. this.isAllDataSelected = true;
  642. // 选中当前页显示的所有行
  643. this.$nextTick(() => {
  644. if (this.$refs.gpuTable) {
  645. this.gpuList.forEach((row) => {
  646. this.$refs.gpuTable.toggleRowSelection(row, true);
  647. });
  648. }
  649. });
  650. this.$message.success(`已选中全部${this.total}条数据`);
  651. }
  652. } catch (error) {
  653. console.error("获取全部数据失败:", error);
  654. this.$message.error("选择全部数据失败");
  655. }
  656. },
  657. // 取消全选
  658. handleCancelAllSelection() {
  659. this.clearSelection();
  660. this.$message.info("已取消全部选择");
  661. },
  662. // 检查行是否可选
  663. checkSelectable(row) {
  664. // 可以根据需要添加选择限制条件
  665. return true;
  666. },
  667. // 修改分页方法
  668. async handleCurrentChange(val) {
  669. this.currentPage = val;
  670. await this.fetchGpuList();
  671. // 恢复选中状态
  672. this.$nextTick(() => {
  673. if (this.$refs.gpuTable) {
  674. this.gpuList.forEach((row) => {
  675. if (this.allSelectedRows.has(row.id) || this.isAllDataSelected) {
  676. this.$refs.gpuTable.toggleRowSelection(row, true);
  677. }
  678. });
  679. }
  680. });
  681. },
  682. // 修改每页条数切换方法
  683. async handleSizeChange(val) {
  684. this.pageSize = val;
  685. this.currentPage = 1;
  686. await this.fetchGpuList();
  687. // 恢复选中状态
  688. this.$nextTick(() => {
  689. if (this.$refs.gpuTable) {
  690. this.gpuList.forEach((row) => {
  691. if (this.allSelectedRows.has(row.id) || this.isAllDataSelected) {
  692. this.$refs.gpuTable.toggleRowSelection(row, true);
  693. }
  694. });
  695. }
  696. });
  697. },
  698. // 清空选择
  699. clearSelection() {
  700. if (this.$refs.gpuTable) {
  701. this.$refs.gpuTable.clearSelection();
  702. }
  703. this.selectedRows = [];
  704. this.allSelectedRows.clear();
  705. this.isAllDataSelected = false;
  706. },
  707. // 打开标签对话框时重置表单
  708. handleBatchTag() {
  709. this.tagDialogVisible = true;
  710. this.tagForm = {
  711. tags: [],
  712. category_id: "",
  713. };
  714. this.tagOptions = [];
  715. this.fetchCategories(); // 获取分类列表
  716. this.handleCategoryChange(""); // 获取所有标签
  717. },
  718. // 修改分类变更处理方法
  719. async handleCategoryChange(categoryId) {
  720. this.tagForm.tags = []; // 清空已选标签
  721. this.tagOptions = []; // 清空标签选项
  722. try {
  723. this.tagsLoading = true;
  724. const { data } = await search_tags({
  725. page: 1,
  726. pagesize: 999,
  727. keyword: "",
  728. category_id: categoryId === "" ? undefined : categoryId, // 修改判断条件
  729. });
  730. if (data?.data?.items) {
  731. this.tagOptions = data.data.items;
  732. }
  733. } catch (error) {
  734. console.error("获取标签列表失败:", error);
  735. this.$message.error("获取标签列表失败");
  736. } finally {
  737. this.tagsLoading = false;
  738. }
  739. },
  740. // 修改标签搜索方法
  741. async handleTagSearch(query) {
  742. this.tagsLoading = true;
  743. try {
  744. const { data } = await search_tags({
  745. keyword: query,
  746. category_id:
  747. this.tagForm.category_id === ""
  748. ? undefined
  749. : this.tagForm.category_id, // 修改判断条件
  750. });
  751. if (data?.data?.items) {
  752. this.tagOptions = data.data.items;
  753. }
  754. } catch (error) {
  755. console.error("搜索标签失败:", error);
  756. this.$message.error("搜索标签失败");
  757. } finally {
  758. this.tagsLoading = false;
  759. }
  760. },
  761. /* 批量处理文献 */
  762. async handleBatchProcess() {
  763. const selectedIds = this.isAllDataSelected
  764. ? await this.getAllDataIds()
  765. : Array.from(this.allSelectedRows.keys());
  766. if (selectedIds.length === 0) {
  767. this.$message.warning("请选择需要处理的文献");
  768. return;
  769. }
  770. try {
  771. this.isProcessing = true;
  772. const ids = this.selectedRows.map((row) => row.id);
  773. // 添加确认对话框
  774. await this.$confirm("确认要批量处理选中的文献吗?", "提示", {
  775. confirmButtonText: "确定",
  776. cancelButtonText: "取消",
  777. type: "warning",
  778. });
  779. const res = await process_model_regeneration(ids);//process_model_regeneration
  780. if (res.status === 200) {
  781. this.$message.success("批量处理已提交");
  782. this.selectedRows = []; // 清空选中
  783. this.clearSelection()
  784. this.allSelectedRows.clear();
  785. await this.fetchGpuList(); // 刷新列表
  786. } else {
  787. throw new Error(res.message || "处理失败");
  788. }
  789. } catch (error) {
  790. if (error === "cancel") {
  791. return;
  792. }
  793. this.$message.error(error.message || "批量处理提交失败");
  794. console.error("批量处理错误:", error);
  795. } finally {
  796. this.isProcessing = false;
  797. }
  798. },
  799. // 获取所有数据的ID
  800. async getAllDataIds() {
  801. try {
  802. const params = {
  803. page: 1,
  804. page_size: this.total,
  805. keyword: this.searchForm.name,
  806. tag_names: this.searchForm.id,
  807. create_time_start: this.searchForm.createTimeRange?.[0] || "",
  808. create_time_end: this.searchForm.createTimeRange?.[1] || "",
  809. update_time_start: this.searchForm.updateTimeRange?.[0] || "",
  810. update_time_end: this.searchForm.updateTimeRange?.[1] || "",
  811. processing_status: this.searchForm.processing_status || null,
  812. };
  813. const response = await axios.post(
  814. `${process.env.VUE_APP_BASE_API}/literature-data/search/`,
  815. params
  816. );
  817. if (response.status === 200) {
  818. return response.data.data.list.map((item) => item.id);
  819. }
  820. return [];
  821. } catch (error) {
  822. console.error("获取所有数据ID失败:", error);
  823. return [];
  824. }
  825. },
  826. /* 模糊搜索 */
  827. async searchTags(query) {
  828. if (query !== "") {
  829. this.tagsLoading = true;
  830. try {
  831. const { data } = await search_tags({
  832. keyword: query,
  833. });
  834. this.tagOptions = data.data.items;
  835. } catch (error) {
  836. this.$message.error("搜索标签失败");
  837. } finally {
  838. this.tagsLoading = false;
  839. }
  840. } else {
  841. this.tagOptions = [];
  842. }
  843. },
  844. // 修改提交方法
  845. async submitBatchTag() {
  846. try {
  847. const ids = this.selectedRows.map((row) => row.id);
  848. // 这里需要添加实际的API调用
  849. /* await edit_tags({
  850. ids,
  851. tagIds: this.tagForm.tags, // 现在传递的是标签ID数组
  852. }); */
  853. const idObject = { literature_ids: ids, tag_ids: this.tagForm.tags };
  854. const response = await axios.post(
  855. `${process.env.VUE_APP_BASE_API}/literature-tags/relation_tags/`,
  856. idObject,
  857. {
  858. headers: {
  859. "Content-Type": "application/json",
  860. },
  861. }
  862. );
  863. if (response.status !== 200) return;
  864. console.log(this.tagForm.tags);
  865. this.$message.success("标签更新成功");
  866. this.tagDialogVisible = false;
  867. this.fetchGpuList(); // 刷新列表
  868. } catch (error) {
  869. this.$message.error("标签更新失败");
  870. }
  871. },
  872. // 处理搜索
  873. handleSearch() {
  874. this.currentPage = 1; // 重置页码
  875. this.fetchGpuList();
  876. },
  877. // 修改重置搜索方法
  878. resetSearch() {
  879. this.searchForm = {
  880. name: "",
  881. createTimeRange: [],
  882. textTimeRange: [],
  883. id: [],
  884. type: "",
  885. processing_status: "",
  886. };
  887. this.clearSelection(); // 清空选择
  888. this.handleSearch();
  889. },
  890. // 初始化数据
  891. async initialize() {
  892. try {
  893. await this.fetchBuckets();
  894. await this.fetchGpuList();
  895. } catch (error) {
  896. this.$message.error("初始化数据失败");
  897. console.error("初始化失败:", error);
  898. }
  899. },
  900. // 获取状态文本
  901. getStatusText(status) {
  902. return STATUS_MAP[status]?.text || "未知状态";
  903. },
  904. // 获取状态样式类型
  905. getStatusType(status) {
  906. return STATUS_MAP[status]?.type || "info";
  907. },
  908. // 获取知识库列表
  909. async fetchBuckets() {
  910. try {
  911. const { data } = await listBuckets();
  912. this.buckets = data;
  913. } catch (error) {
  914. this.$message.error("获取知识库列表失败");
  915. console.error("获取知识库失败:", error);
  916. }
  917. },
  918. // 处理知识库变更
  919. async handleBucketChange(val) {
  920. this.selectedType = null;
  921. this.types = [];
  922. if (!val) return;
  923. try {
  924. const { data } = await selectTypeList({ kb_id: val });
  925. this.types = data.dataList;
  926. this.debouncedFetchGpuList();
  927. } catch (error) {
  928. this.$message.error("获取目录列表失败");
  929. }
  930. },
  931. // 同步数据处理
  932. async handleSyncData() {
  933. if (!this.selectedType) {
  934. this.$message.warning("请先选择目录");
  935. return;
  936. }
  937. this.isSyncing = true;
  938. try {
  939. await GET_sync({ doc_type_id: this.selectedType });
  940. this.$message.success("同步成功");
  941. await this.fetchGpuList();
  942. } catch (error) {
  943. this.$message.error("同步失败");
  944. } finally {
  945. this.isSyncing = false;
  946. }
  947. },
  948. // 修改 fetchGpuList 方法中的分页处理部分
  949. async fetchGpuList() {
  950. this.isLoading = true;
  951. try {
  952. const params = {
  953. page: this.currentPage,
  954. page_size: this.pageSize,
  955. keyword: this.searchForm.name,
  956. tag_names: this.searchForm.id,
  957. create_time_start: this.searchForm.createTimeRange?.[0] || "",
  958. create_time_end: this.searchForm.createTimeRange?.[1] || "",
  959. update_time_start: this.searchForm.updateTimeRange?.[0] || "", // 新增
  960. update_time_end: this.searchForm.updateTimeRange?.[1] || "", // 新增
  961. processing_status: this.searchForm.processing_status || null,
  962. };
  963. const response = await axios.post(
  964. `${process.env.VUE_APP_BASE_API}/literature-data/search/`,
  965. params,
  966. {
  967. headers: {
  968. "Content-Type": "application/json",
  969. },
  970. }
  971. );
  972. if (response.status !== 200) return;
  973. // 更新数据和分页信息
  974. const { list, total, current_page, page_size } = response.data.data;
  975. this.gpuList = list.map((item) => {
  976. const nameParts = item.name.split(".");
  977. const processedItem = {
  978. ...item,
  979. name: nameParts[0],
  980. type: nameParts.length > 1 ? nameParts.pop() : "未知",
  981. };
  982. // 检查处理状态,如果是处理中且没有设置轮询,则开始轮询
  983. if (
  984. processedItem.processing_status === 1 &&
  985. !this.pollingTimers[processedItem.id]
  986. ) {
  987. this.startPolling(processedItem.id);
  988. }
  989. // 如果状态不是处理中,但存在轮询,则停止轮询
  990. else if (
  991. processedItem.processing_status !== 1 &&
  992. this.pollingTimers[processedItem.id]
  993. ) {
  994. this.stopPolling(processedItem.id);
  995. }
  996. return processedItem;
  997. });
  998. // 确保这些值都是数字类型
  999. this.total = parseInt(total);
  1000. this.currentPage = parseInt(current_page);
  1001. this.pageSize = parseInt(page_size);
  1002. } catch (error) {
  1003. console.error("获取列表数据失败:", error);
  1004. this.$message.error("获取列表数据失败");
  1005. } finally {
  1006. this.isLoading = false;
  1007. }
  1008. },
  1009. // 新增:开始轮询
  1010. startPolling(id) {
  1011. if (this.pollingTimers[id]) return; // 如果已经在轮询,则不重复设置
  1012. this.pollingTimers[id] = setInterval(async () => {
  1013. try {
  1014. const response = await axios.post(
  1015. `${process.env.VUE_APP_BASE_API}/literature-data/search/`,
  1016. {
  1017. page: this.currentPage,
  1018. page_size: this.pageSize,
  1019. doc_type_id: this.selectedType || 36,
  1020. keyword: this.searchForm.name,
  1021. tag_names: this.searchForm.id,
  1022. }
  1023. );
  1024. if (response.status === 200) {
  1025. const item = response.data.data.list.find((item) => item.id === id);
  1026. if (item && item.processing_status !== 1) {
  1027. // 如果状态不再是"处理中",停止轮询
  1028. this.stopPolling(id);
  1029. await this.fetchGpuList(); // 刷新列表
  1030. }
  1031. }
  1032. } catch (error) {
  1033. console.error("轮询检查状态失败:", error);
  1034. this.stopPolling(id); // 发生错误时停止轮询
  1035. }
  1036. }, 10000); // 每分钟检查一次
  1037. },
  1038. // 新增:停止轮询
  1039. stopPolling(id) {
  1040. if (this.pollingTimers[id]) {
  1041. clearInterval(this.pollingTimers[id]);
  1042. delete this.pollingTimers[id];
  1043. }
  1044. },
  1045. /* // 修改分页处理方法
  1046. handleSizeChange(val) {
  1047. this.pageSize = val;
  1048. this.currentPage = 1; // 切换每页数量时重置为第一页
  1049. this.fetchGpuList(); // 移除防抖,直接调用
  1050. }, */
  1051. /* handleCurrentChange(val) {
  1052. this.currentPage = val;
  1053. this.fetchGpuList(); // 移除防抖,直接调用
  1054. }, */
  1055. /* 预览 */
  1056. handlePreview(row) {
  1057. console.log(row);
  1058. let _this = this;
  1059. let a = document.createElement("a");
  1060. a.href = `#/infoList?id=${row.id}&type=${row.type.toLowerCase()}`; // 使用 hash
  1061. a.target = "_blank";
  1062. a.click();
  1063. },
  1064. // 处理名称点击
  1065. async handleNameClick(row) {
  1066. try {
  1067. /* const res = await getDetail({ id: row.id })
  1068. console.log('详情数据:', res) */
  1069. this.$router.push({
  1070. path: "/knowledge/middPage/index",
  1071. query: {
  1072. id: row.id,
  1073. name: row.name,
  1074. tag: JSON.stringify(row.tags),
  1075. identifier: row.identifier,
  1076. },
  1077. });
  1078. // 这里可以添加详情处理逻辑
  1079. } catch (error) {
  1080. this.$message.error("获取详情失败");
  1081. }
  1082. },
  1083. // Tab切换处理
  1084. handleClick(tab) {
  1085. // 根据tab加载相应数据
  1086. switch (tab.name) {
  1087. case "second":
  1088. this.loadSotaDetails();
  1089. break;
  1090. case "third":
  1091. this.loadSotaSummary();
  1092. break;
  1093. }
  1094. },
  1095. // 加载SOTA详情数据
  1096. async loadSotaDetails() {
  1097. try {
  1098. // 这里调用API获取SOTA详情数据
  1099. const response = await getSotaDetails();
  1100. this.sotaDetailsList = response.data;
  1101. } catch (error) {
  1102. /* this.$message.error("获取SOTA详情失败");
  1103. console.error(error); */
  1104. }
  1105. },
  1106. // 加载SOTA汇总表数据
  1107. async loadSotaSummary() {
  1108. try {
  1109. // 这里调用API获取SOTA汇总数据
  1110. const response = await getSotaSummary();
  1111. this.sotaSummaryList = response.data;
  1112. } catch (error) {
  1113. /* this.$message.error("获取SOTA汇总数据失败");
  1114. console.error(error); */
  1115. }
  1116. },
  1117. },
  1118. // 在组件销毁时清理所有轮询
  1119. beforeDestroy() {
  1120. // 清理轮询
  1121. Object.keys(this.pollingTimers).forEach((id) => {
  1122. this.stopPolling(id);
  1123. });
  1124. // 清空选择
  1125. this.clearSelection();
  1126. },
  1127. };
  1128. </script>
  1129. <style scoped>
  1130. .gpu-list {
  1131. padding: 20px;
  1132. }
  1133. .filter-section {
  1134. margin-bottom: 20px;
  1135. }
  1136. .ml-15 {
  1137. margin-left: 15px;
  1138. }
  1139. .pagination {
  1140. display: flex;
  1141. justify-content: flex-end;
  1142. margin-top: 15px;
  1143. }
  1144. </style>