阅行客电子档案
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

629 lines
22 KiB

1 year ago
  1. <template>
  2. <div class="app-container category-container" style="height: calc(100vh - 140px);">
  3. <!-- 门类列表 -->
  4. <div class="container-main">
  5. <div class="elect-cont-left">
  6. <div class="container-left">
  7. <span class="right-top-line" />
  8. <span class="left-bottom-line" />
  9. <!--门类树状结构-->
  10. <div class="tree-scroll">
  11. <el-scrollbar style="height: calc(100vh - 230px);">
  12. <el-tree ref="categroyTree" v-loading="crud.loading" class="arc-tree arc-tree-01" :data="crud.data" :props="defaultProps" node-key="id" :expand-on-click-node="false" highlight-current @node-click="handleNodeClick">
  13. <span slot-scope="{ node, data }" class="custom-tree-node">
  14. <el-tooltip :content="node.label" placement="left" :enterable="false" effect="dark">
  15. <span v-if="data.isType === 0">
  16. {{ data.label }}
  17. </span>
  18. <span v-if="data.isType === 1" class="iconFolder tree-text">
  19. {{ data.label }}
  20. </span>
  21. <span v-if="data.isType === 2" class="iconArch tree-text">
  22. {{ data.label }}
  23. </span>
  24. <span v-if="data.isType === 3" class="iconFile tree-text">
  25. {{ data.label }}
  26. </span>
  27. </el-tooltip>
  28. </span>
  29. </el-tree>
  30. </el-scrollbar>
  31. </div>
  32. </div>
  33. </div>
  34. <div class="elect-cont-right">
  35. <div v-if="selectedCategory.isType === 2" class="container-right">
  36. <span class="right-top-line" />
  37. <span class="left-bottom-line" />
  38. <div class="ai-file-pickUp">
  39. <div class="ai-file-all">
  40. <div class="des-title">
  41. <p>原文列表</p>
  42. </div>
  43. <el-table
  44. ref="table"
  45. :data="tableData"
  46. style="min-width: 100%"
  47. class="archives-table"
  48. height="calc(100vh - 220px)"
  49. @row-click="clickRowHandler"
  50. >
  51. <el-table-column type="selection" align="center" width="55" />
  52. <!-- <el-table-column type="index" label="序号" width="55" align="center" /> -->
  53. <el-table-column prop="file_name" label="文件名称" show-overflow-tooltip min-width="120" />
  54. <el-table-column prop="file_type" label="格式" min-width="60" align="center">
  55. <template slot-scope="scope">
  56. <div v-if="scope.row.file_type === 'jpg' || scope.row.file_type === 'jpeg' || scope.row.file_type === 'png' || scope.row.file_type === 'bmp'|| scope.row.file_type === 'gif'">
  57. <i class="fileIcon icon-image" />
  58. </div>
  59. <div v-else-if="scope.row.file_type === 'xlsx' || scope.row.file_type === 'xls'">
  60. <i class="fileIcon icon-excel" />
  61. </div>
  62. <div v-else-if="scope.row.file_type === 'docx' || scope.row.file_type === 'doc'">
  63. <i class="fileIcon icon-word" />
  64. </div>
  65. <div v-else-if="scope.row.file_type === 'pdf'">
  66. <i class="fileIcon icon-pdf" />
  67. </div>
  68. <div v-else-if="scope.row.file_type === 'ppt' || scope.row.file_type === 'pptx'">
  69. <i class="fileIcon icon-ppt" />
  70. </div>
  71. <div v-else-if="scope.row.file_type === 'zip' || scope.row.file_type === 'rar'">
  72. <i class="fileIcon icon-zip" />
  73. </div>
  74. <div v-else-if="scope.row.file_type === 'txt'">
  75. <i class="fileIcon icon-txt" />
  76. </div>
  77. <div v-else>
  78. <i class="fileIcon icon-other" />
  79. </div>
  80. </template>
  81. </el-table-column>
  82. <el-table-column prop="file_status" label="是否标注" width="100" align="center">
  83. <template slot-scope="scope">
  84. <span v-if="scope.row.file_status === 0" class="row-state row-physical ">未标注</span>
  85. <span v-else class="row-state row-binding state-active">已标注</span>
  86. </template>
  87. </el-table-column>
  88. <el-table-column prop="file_status" label="操作" align="center" width="80">
  89. <template slot-scope="scope">
  90. <div class="handle-btn">
  91. <el-button class="iconfont icon-huoqu" @click.stop="handlePickUp(scope.row)" />
  92. </div>
  93. </template>
  94. </el-table-column>
  95. </el-table>
  96. </div>
  97. <div class="pickUp-right">
  98. <div class="pickUp-right-item">
  99. <div class="des-title">
  100. <p>已标注关键词</p>
  101. </div>
  102. <div v-if="currentRow && currentRow.file_status===1" style="height: calc(100% - 70px); overflow-y: auto;">
  103. <div v-for="(item, index) in keywords" :key="index" class="keywords-list">
  104. <div class="keywords-item">
  105. <div v-if="isEditing[index]" class="keywords-input">
  106. <el-input v-model="keywords[index]" style="width: 320px;" @blur="handleBlur(index)" />
  107. </div>
  108. <div v-else class="keywords-text">
  109. <!-- <p>{{ item }}</p> -->
  110. <el-tooltip class="item" effect="dark" :content="item" :enterable="false" placement="top">
  111. <p>{{ item }}</p>
  112. </el-tooltip>
  113. </div>
  114. </div>
  115. <div class="keywords-handle">
  116. <i class="iconfont icon-shanchu" @click="handleDelete(index)" />
  117. <i class="iconfont icon-bianji" @click="handleEdit(index)" />
  118. </div>
  119. </div>
  120. </div>
  121. <el-empty v-else description="暂无标注的数据" />
  122. </div>
  123. <div :key="reloadKey" class="pickUp-Ai">
  124. <div class="des-title">
  125. <p>AI关键词标注</p>
  126. <el-button @click="batchAiText">批量标注</el-button>
  127. </div>
  128. <div v-if="isPickUp " v-loading="aiLoadingPick" style="height: calc(100% - 70px); overflow-y: auto;">
  129. <div v-for="(item, index) in aiPickData" :key="index" class="keywords-list">
  130. <div class="keywords-item ai-item-text">
  131. <el-checkbox v-model="selectedItems[index]" @change="handleCheckboxChange(index)" />
  132. <div class="keywords-text">
  133. <el-tooltip class="item" effect="dark" :content="item" :enterable="false" placement="top">
  134. <p>{{ item }}</p>
  135. </el-tooltip>
  136. </div>
  137. </div>
  138. </div>
  139. </div>
  140. </div>
  141. </div>
  142. </div>
  143. </div>
  144. </div>
  145. </div>
  146. </div>
  147. </template>
  148. <script>
  149. import crudCategory from '@/api/system/category/category'
  150. import CRUD, { presenter, header } from '@crud/crud'
  151. const data = [
  152. {
  153. 'file_path': '/category//09A6ECD967BCC772DABB2F.txt',
  154. 'sequence': '',
  155. 'create_time': '2024-02-05 11:43:26',
  156. 'file_name': 'A002-WSWJ·XZGL·2024·Y-000201.txt',
  157. 'file_type': 'txt',
  158. 'document_file_id': '',
  159. 'file_dpi': 'null',
  160. 'id': '09A6ECD967BCC772DABB2F',
  161. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  162. 'document_id': '',
  163. 'file_size': 0,
  164. 'file_thumbnail': '',
  165. 'file_status': 1
  166. },
  167. {
  168. 'file_path': '/category//0EFEF1CB577453482ADE5F.txt',
  169. 'sequence': '',
  170. 'create_time': '2024-02-05 11:43:26',
  171. 'file_name': 'A002-WSWJ·XZGL·2024·Y-000202.txt',
  172. 'file_type': 'txt',
  173. 'document_file_id': '',
  174. 'file_dpi': 'null',
  175. 'id': '0EFEF1CB577453482ADE5F',
  176. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  177. 'document_id': '',
  178. 'file_size': 0,
  179. 'file_thumbnail': '',
  180. 'file_status': 0
  181. },
  182. {
  183. 'file_path': '/category/AE8B188F0C0314F9BE31B8/82FBCAE96CBC9F50809838/eb824e27-13eb-4f33-adc8-39ca5926d244.txt',
  184. 'sequence': '',
  185. 'create_time': '2024-3-20 16:16:32',
  186. 'file_name': '123.txt',
  187. 'file_type': 'txt',
  188. 'document_file_id': '',
  189. 'file_dpi': '',
  190. 'id': '3D49BD844334621851DF24',
  191. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  192. 'document_id': '',
  193. 'file_size': 37679,
  194. 'file_thumbnail': '',
  195. 'file_status': 0
  196. },
  197. {
  198. 'file_path': '/category/AE8B188F0C0314F9BE31B8/82FBCAE96CBC9F50809838/4B457990A874D55714FF8C.pdf',
  199. 'sequence': '',
  200. 'create_time': '2024-3-20 11:36:24',
  201. 'file_name': 'DAT 48-2009 基于XML的电子文件封装规范.pdf',
  202. 'file_type': 'pdf',
  203. 'document_file_id': '',
  204. 'file_dpi': '',
  205. 'id': '4B457990A874D55714FF8C',
  206. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  207. 'document_id': '',
  208. 'file_size': 92308284,
  209. 'file_thumbnail': '',
  210. 'file_status': 0
  211. },
  212. {
  213. 'file_path': '/category//6867E751998B41C3B89FA3.txt',
  214. 'sequence': '',
  215. 'create_time': '2024-01-25 10:26:44',
  216. 'file_name': 'A002-WSWJ·XZGL·2024·Y-000201.txt',
  217. 'file_type': 'txt',
  218. 'document_file_id': '',
  219. 'file_dpi': 'null',
  220. 'id': '6867E751998B41C3B89FA3',
  221. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  222. 'document_id': '',
  223. 'file_size': 0,
  224. 'file_thumbnail': '',
  225. 'file_status': 0
  226. },
  227. {
  228. 'file_path': '/category//694FD852367CD7465F7142.txt',
  229. 'sequence': '',
  230. 'create_time': '2024-02-05 13:21:39',
  231. 'file_name': 'A002-WSWJ·XZGL·2024·Y-000202.txt',
  232. 'file_type': 'txt',
  233. 'document_file_id': '',
  234. 'file_dpi': 'null',
  235. 'id': '694FD852367CD7465F7142',
  236. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  237. 'document_id': '',
  238. 'file_size': 0,
  239. 'file_thumbnail': '',
  240. 'file_status': 0
  241. },
  242. {
  243. 'file_path': '/category//6E39B6C1428217A5AAA835.txt',
  244. 'sequence': '',
  245. 'create_time': '2024-02-05 11:52:33',
  246. 'file_name': 'A002-WSWJ·XZGL·2024·Y-000201.txt',
  247. 'file_type': 'txt',
  248. 'document_file_id': '',
  249. 'file_dpi': 'null',
  250. 'id': '6E39B6C1428217A5AAA835',
  251. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  252. 'document_id': '',
  253. 'file_size': 0,
  254. 'file_thumbnail': '',
  255. 'file_status': 0
  256. },
  257. {
  258. 'file_path': '/category//761753E47A37F7F1C141BB.txt',
  259. 'sequence': '',
  260. 'create_time': '2024-01-25 10:25:25',
  261. 'file_name': 'A002-WSWJ·XZGL·2024·Y-000202.txt',
  262. 'file_type': 'txt',
  263. 'document_file_id': '',
  264. 'file_dpi': 'null',
  265. 'id': '761753E47A37F7F1C141BB',
  266. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  267. 'document_id': '',
  268. 'file_size': 0,
  269. 'file_thumbnail': '',
  270. 'file_status': 1
  271. },
  272. {
  273. 'file_path': '/category/AE8B188F0C0314F9BE31B8/82FBCAE96CBC9F50809838/95ec71e9-4745-4210-ba28-77c71b95bf06.ofd',
  274. 'sequence': '',
  275. 'create_time': '2024-3-20 14:20:57',
  276. 'file_name': '999.ofd',
  277. 'file_type': 'ofd',
  278. 'document_file_id': '',
  279. 'file_dpi': '',
  280. 'id': '7F846C5F6E3D5E50950384',
  281. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  282. 'document_id': '',
  283. 'file_size': 29752,
  284. 'file_thumbnail': '',
  285. 'file_status': 0
  286. },
  287. {
  288. 'file_path': '/category//831817367E0C3D2E739787.txt',
  289. 'sequence': '',
  290. 'create_time': '2024-01-25 10:25:25',
  291. 'file_name': 'A002-WSWJ·XZGL·2024·Y-000201.txt',
  292. 'file_type': 'txt',
  293. 'document_file_id': '',
  294. 'file_dpi': 'null',
  295. 'id': '831817367E0C3D2E739787',
  296. 'archive_id': '0FBCB1FCB6341BFF9709B6',
  297. 'document_id': '',
  298. 'file_size': 0,
  299. 'file_thumbnail': '',
  300. 'file_status': 1
  301. }
  302. ]
  303. export default {
  304. name: 'AIKeywords',
  305. components: { },
  306. cruds() {
  307. return [
  308. CRUD({
  309. title: 'AI关键词提取', url: 'api/category/fondMenu',
  310. crudMethod: { ...crudCategory },
  311. optShow: {
  312. add: false,
  313. edit: false,
  314. del: false,
  315. download: false,
  316. group: false
  317. }
  318. })
  319. ]
  320. },
  321. mixins: [presenter(), header()],
  322. data() {
  323. return {
  324. defaultProps: {
  325. children: 'children',
  326. label: 'label'
  327. },
  328. selectedCategory: {},
  329. tableData: data,
  330. selections: [],
  331. keywords: [],
  332. isEditing: [],
  333. aiPickData: [],
  334. isPickUp: false,
  335. reloadKey: 0,
  336. aiLoadingPick: false,
  337. currentRow: {},
  338. selectedItems: []
  339. }
  340. },
  341. computed: {
  342. },
  343. created() {
  344. this.isEditing = this.keywords.map(() => false)
  345. },
  346. mounted() {
  347. },
  348. methods: {
  349. // 处理编辑图标点击事件
  350. handleEdit(index) {
  351. this.$set(this.isEditing, index, true)
  352. },
  353. // 处理输入框失去焦点事件
  354. handleBlur(index) {
  355. this.$set(this.isEditing, index, false)
  356. },
  357. // 处理删除操作的方法
  358. handleDelete(index) {
  359. // 从 keywords 数组中删除指定索引的项
  360. this.keywords.splice(index, 1)
  361. // 从 isEditing 数组中删除对应索引的项
  362. this.isEditing.splice(index, 1)
  363. },
  364. filterData(data) {
  365. return data.filter(node => {
  366. if (node.children && node.children.length > 0) {
  367. node.children = this.filterData(node.children) // 递归处理子节点
  368. }
  369. return node.isType !== 3 // 过滤掉isType为3的节点
  370. })
  371. },
  372. // 逆归实现 获取指定元素
  373. findNode(tree, func) {
  374. for (const node of tree) {
  375. if (func(node)) return node
  376. if (node.children) {
  377. const res = this.findNode(node.children, func)
  378. if (res) return res
  379. }
  380. }
  381. return null
  382. },
  383. // 根据父级展开全部子级
  384. expandAllChildren(node, targetElement) {
  385. node.expanded = true
  386. // 递归展开当前节点的每个子节点
  387. if (node.childNodes && node.childNodes.length > 0) {
  388. for (let i = 0; i < node.childNodes.length; i++) {
  389. if (node.childNodes[i].data.id === targetElement.id) {
  390. this.$refs.categroyTree.setCurrentKey(node.childNodes[i])
  391. }
  392. this.expandAllChildren(node.childNodes[i], targetElement)
  393. }
  394. }
  395. },
  396. // 转换函数,将原始数据转换为el-tree所需格式
  397. transformData(rawData) {
  398. return rawData.map(item => {
  399. return {
  400. label: item.fondName,
  401. isType: 0,
  402. id: item.fondsId,
  403. fondsNo: item.fondsNo,
  404. children: item.categoryList.map(category => {
  405. return {
  406. label: category.cnName,
  407. id: category.id,
  408. arrangeType: category.arrangeType,
  409. isType: category.isType,
  410. fondsId: item.fondsId,
  411. fondName: item.fondName,
  412. fondsNo: item.fondsNo,
  413. children: this.transformChildren(category.children, item.fondsId, item.fondName, item.fondsNo)
  414. }
  415. })
  416. }
  417. })
  418. },
  419. // 递归函数,用于处理数据的子节点
  420. transformChildren(children, fondsId, fondName, fondsNo) {
  421. return children.map(child => {
  422. return {
  423. label: child.cnName,
  424. id: child.id,
  425. isType: child.isType,
  426. pid: child.pid,
  427. code: child.code,
  428. arrangeType: child.arrangeType,
  429. fondsId: fondsId,
  430. fondName: fondName,
  431. fondsNo: fondsNo,
  432. children: child.children.length ? this.transformChildren(child.children, fondsId, fondName, fondsNo) : []
  433. }
  434. })
  435. },
  436. // 找顶级节点
  437. findTopLevelNode(data, fondsId) {
  438. for (let i = 0; i < data.length; i++) {
  439. if (data[i].id === fondsId) {
  440. return data[i]
  441. }
  442. }
  443. return null
  444. },
  445. [CRUD.HOOK.afterRefresh]() {
  446. this.crud.data = this.filterData(this.transformData(this.crud.data))
  447. this.$nextTick(() => {
  448. let currentKey
  449. if (localStorage.getItem('currentArchivesKey') !== null) {
  450. currentKey = JSON.parse(localStorage.getItem('currentArchivesKey'))
  451. // 删除门类节点后
  452. if (this.$refs.categroyTree.getCurrentKey(currentKey.id) == null) {
  453. localStorage.removeItem('currentArchivesKey')
  454. }
  455. this.topLevelNode = this.findTopLevelNode(this.crud.data, currentKey.fondsId)
  456. // 如果找到了顶级节点,则从该节点开始递归查找指定元素
  457. if (this.topLevelNode) {
  458. if (currentKey) {
  459. // 展开顶级节点的子节点
  460. if (currentKey.isType === 1) {
  461. currentKey = this.findNode(this.crud.data[0].children, (node) => {
  462. return node.isType !== 1
  463. })
  464. }
  465. this.expandAllChildren(this.$refs.categroyTree.getNode(this.topLevelNode), currentKey)
  466. } else {
  467. this.defaultSetting(currentKey)
  468. }
  469. } else {
  470. this.defaultSetting(currentKey)
  471. }
  472. } else {
  473. this.defaultSetting(currentKey)
  474. }
  475. if (currentKey && currentKey.id) {
  476. this.$nextTick(() => {
  477. // 选中节点的门类详情
  478. this.handleNodeClick(currentKey)
  479. })
  480. }
  481. })
  482. },
  483. defaultSetting(currentKey) {
  484. if (this.crud.data[0].isType === 0) {
  485. currentKey = this.findNode(this.crud.data[0].children, (node) => {
  486. return node.isType !== 1
  487. })
  488. this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey)
  489. } else {
  490. currentKey = this.crud.data[0]
  491. this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey)
  492. }
  493. },
  494. // 选中门类后,设置门类详情数据
  495. handleNodeClick(val) {
  496. if (val) {
  497. localStorage.setItem('currentArchivesKey', JSON.stringify(val))
  498. this.selectedCategory = val
  499. this.$nextTick(() => {
  500. })
  501. }
  502. },
  503. // table
  504. clickRowHandler(row) {
  505. this.isPickUp = false
  506. this.$refs.table.clearSelection()
  507. this.$refs.table.toggleRowSelection(row)
  508. this.selections = [row]
  509. this.currentRow = row
  510. if (this.currentRow.file_status === 1) {
  511. this.keywords = [
  512. '永久', '武汉飞天', '2024'
  513. ]
  514. } else {
  515. this.keywords = []
  516. }
  517. },
  518. handlePickUp(row) {
  519. this.currentRow = row
  520. this.aiPickData = []
  521. this.isPickUp = true
  522. this.aiLoadingPick = true
  523. setTimeout(() => {
  524. this.aiPickData = ['数字人', '多媒体技术在档案信息存贮与检索中的应用中的应用中的应用中的应用', '2023', '30年']
  525. this.aiLoadingPick = false
  526. }, 3000)
  527. },
  528. // 处理复选框状态变化
  529. handleCheckboxChange(index) {
  530. // console.log(`第 ${index} 项的选中状态变为: ${this.selectedItems[index]}`)
  531. const allSelectedItems = this.aiPickData.filter((_, i) => this.selectedItems[i])
  532. console.log('所有选中项:', allSelectedItems)
  533. },
  534. batchAiText() {
  535. const selected = this.aiPickData.filter((_, index) => this.selectedItems[index])
  536. if (selected.length === 0) {
  537. this.$message({ message: '请选择要提取的关键词', type: 'warning', offset: 8 })
  538. return
  539. }
  540. this.keywords = this.keywords.concat(selected)
  541. this.isEditing = this.keywords.map(() => false)
  542. // 移除已提取的关键词
  543. this.aiPickData = this.aiPickData.filter((_, index) => !this.selectedItems[index])
  544. this.selectedItems = this.aiPickData.map(() => false)
  545. // 更新当前行的 file_status 状态
  546. if (this.currentRow) {
  547. this.currentRow.file_status = 1
  548. // 找到 tableData 中对应的行并更新状态
  549. const rowIndex = this.tableData.findIndex(row => row.id === this.currentRow.id)
  550. if (rowIndex !== -1) {
  551. this.$set(this.tableData, rowIndex, { ...this.tableData[rowIndex], file_status: 1 })
  552. }
  553. }
  554. }
  555. }
  556. }
  557. </script>
  558. <style lang="scss" scoped>
  559. .elect-cont-right .container-right {
  560. // min-height: calc(100vh - 188px);
  561. }
  562. .category-container {
  563. .elect-cont-left{
  564. width: 296px !important;
  565. }
  566. }
  567. .openSidebar .category-container .elect-cont-right{
  568. width: calc(100vw - 614px) !important;
  569. }
  570. .hideSidebar .category-container .elect-cont-right {
  571. width: calc(100vw - 412px) !important;
  572. }
  573. .tree-scroll{
  574. font-size: 14px;
  575. }
  576. .ai-file-pickUp{
  577. display: flex;
  578. justify-content: space-between;
  579. }
  580. .ai-file-all{
  581. flex: 1;
  582. }
  583. .pickUp-right{
  584. width: 400px;
  585. margin-left: 20px;
  586. .pickUp-right-item,
  587. .pickUp-Ai{
  588. height: calc(100% / 2);
  589. }
  590. }
  591. .keywords-text p{
  592. width: 340px;
  593. overflow: hidden;
  594. text-overflow: ellipsis;
  595. display: -webkit-box;
  596. -webkit-line-clamp: 1;
  597. -webkit-box-orient: vertical;
  598. }
  599. .keywords-list{
  600. display: flex;
  601. justify-content: space-between;
  602. line-height: 40px;
  603. border-bottom: 1px solid #e6e8ed;
  604. // padding: 0 10px;
  605. }
  606. .keywords-handle i{
  607. cursor: pointer;
  608. }
  609. .ai-item-text{
  610. display: flex;
  611. justify-content: flex-start;
  612. p{
  613. margin-left: 10px;
  614. }
  615. }
  616. .des-title{
  617. position: relative;
  618. .el-button{
  619. position: absolute;
  620. right: 0;
  621. top: -5px;
  622. }
  623. }
  624. </style>