scan.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. <template>
  2. <div class="hc-page-box">
  3. <hc-page-split>
  4. <template #left>
  5. <hc-card scrollbar>
  6. <!-- Element Plus 菜单组件 -->
  7. <ElMenu
  8. v-if="folderData.length > 0"
  9. v-loading="folderLoading"
  10. default-active="1-1"
  11. class="custom-menu"
  12. unique-opened
  13. @select="handleSelect"
  14. >
  15. <!-- 渲染动态菜单 -->
  16. <template v-for="item in folderData" :key="item.id">
  17. <MenuItem :menu-item-data="item" :selected-key-path="selectedKeyPath" />
  18. </template>
  19. </ElMenu>
  20. <div v-else class="mt-40">
  21. <hc-empty />
  22. </div>
  23. </hc-card>
  24. </template>
  25. <hc-new-card>
  26. <template #extra>
  27. <el-button hc-btn type="danger" :loading="scanLoading" @click="scanClick">开始扫描</el-button>
  28. </template>
  29. <template #header>
  30. <el-button hc-btn color="#12B9A7" class="text-white" :disabled="!folderId" @click="movesClick">移动</el-button>
  31. <!-- <el-button hc-btn color="#149BF4" class="text-white">自动识别</el-button> -->
  32. <HcTooltip keys="file_records_btn_split">
  33. <el-badge :value="taskMount" class="ml-1 mr-4 cursor-pointer" :offset="[5, 5]" @click.native.stop="taskCountClick">
  34. <el-button hc-btn style="background-color: #8B5CF6; border-color: #8B5CF6; color:white" @click.native.stop="splitClick">自动识别</el-button>
  35. <template #content="{ value }">
  36. <div class="custom-content">
  37. <span>{{ value }}</span>
  38. </div>
  39. </template>
  40. </el-badge>
  41. </HcTooltip>
  42. <el-button hc-btn class="text-white" color="#149BF4" :disabled="!tableCheckedKeys.length" @click="editClick">编辑</el-button>
  43. <el-button v-del-com:[delClick] hc-btn type="danger" :disabled="!tableCheckedKeys.length">删除</el-button>
  44. </template>
  45. <HcTable
  46. ref="tableRef" :check-style="{ width: 29 }" :column="tableColumn" :datas="tableData"
  47. :index-style="{ width: 60 }" :loading="tableLoading" is-check is-new
  48. :cell-style="tableCellStyle"
  49. @selection-change="tableSelection"
  50. >
  51. <template #fileName="{ row }">
  52. <span class="text-link" @click="viewPdf(row.ossUrl)">{{ row?.fileName }}</span>
  53. </template>
  54. </HcTable>
  55. <template #action>
  56. <HcPages :pages="searchForm" :sizes="[20, 50, 100, 200, 300, 500]" @change="pageChange" />
  57. </template>
  58. </hc-new-card>
  59. </hc-page-split>
  60. <!-- 跨目录移动 -->
  61. <hc-new-dialog v-model="movesModal" :loading="movesModalLoading" is-table title="跨目录移动" widths="80vw" @close="movesModalClose" @save="movesModalSave">
  62. <div class="hc-moves-transfer-box">
  63. <div class="hc-moves-transfer-panel">
  64. <div class="panel-header">
  65. <div class="panel-header-label">
  66. <el-checkbox v-model="movesCheckAll" :indeterminate="isIndeterminate" class="space size-xl" @change="handleCheckAllChange">
  67. 选择需要迁移的文件
  68. </el-checkbox>
  69. </div>
  70. <div class="panel-header-extra">{{ checkedMoves.length }}/{{ fileDatasList.length }}</div>
  71. </div>
  72. <div class="panel-body">
  73. <el-scrollbar v-loading="fileDatasListLoading">
  74. <el-checkbox-group v-model="checkedMoves" @change="handleCheckedMovesChange">
  75. <!-- 为文件名容器添加自定义class以便设置样式 -->
  76. <div v-for="item in fileDatasList" :key="item.id" class="file-item">
  77. <el-checkbox :label="item">
  78. <span class="file-name">{{ item.fileName }}</span>
  79. </el-checkbox>
  80. </div>
  81. </el-checkbox-group>
  82. </el-scrollbar>
  83. </div>
  84. </div>
  85. <div class="hc-moves-transfer-buttons">
  86. <HcIcon name="arrow-right-double" style="font-size: 22px;" type="primary" />
  87. </div>
  88. <div class="hc-moves-transfer-panel">
  89. <div class="panel-header">选择移动目录</div>
  90. <div v-loading="treePanelLoading" class="panel-body">
  91. <el-scrollbar>
  92. <HcTree
  93. :contract-id="contractId" :is-show-menu="false" :project-id="projectId"
  94. :show-radio-fun="showRadioFun" id-prefix="hc-tree-moves-" is-radio
  95. @radio-change="radioChange" @node-loading="panelTreeLoading"
  96. />
  97. </el-scrollbar>
  98. </div>
  99. </div>
  100. </div>
  101. </hc-new-dialog>
  102. <!-- 编辑 -->
  103. <hc-new-dialog v-model="editModal" :loading="editLoading" is-table title="编辑" widths="60vw" @close="editModalClose" @save="editModalSave">
  104. <HcTable
  105. ui="hc-form-table" is-new :is-index="false"
  106. :column="tableEditColumn" :datas="tableEditData"
  107. >
  108. <template #fileNum="{ row }">
  109. <el-input v-model="row.fileNum" />
  110. </template>
  111. <template #fileName="{ row }">
  112. <el-input v-model="row.fileName" type="textarea" />
  113. </template>
  114. <template #responsible="{ row }">
  115. <el-input v-model="row.responsible" type="textarea" />
  116. </template>
  117. <template #fileDate="{ row }">
  118. <el-date-picker v-model="row.fileDate" value-format="YYYY-MM-DD HH:mm:ss" type="datetime" />
  119. </template>
  120. </HcTable>
  121. </hc-new-dialog>
  122. <!-- 自动识别列表信息 -->
  123. <!-- 分解列表信息 -->
  124. <hc-new-dialog v-model="splitListModal" title="自动识别列表信息" widths="50vw" :footer="false" height="500px" @close="splitListModalClose">
  125. <HcTable
  126. heights="calc(100% - 50px)"
  127. :column="splitListColumn"
  128. :datas="splitList"
  129. :loading="splitListLoading"
  130. is-new
  131. :index-style="{ width: 70 }"
  132. >
  133. <template #fileNumber="{ row }">
  134. <span>{{ row.fileNumber }}</span>
  135. </template>
  136. <template #name="{ row }">
  137. <span>{{ row.name }}</span>
  138. </template>
  139. <template #status="{ row }">
  140. <span>{{ row.status }}</span>
  141. </template>
  142. <template #createTime="{ row }">
  143. <span>{{ row.createTime }}</span>
  144. </template>
  145. </HcTable>
  146. </hc-new-dialog>
  147. <!-- 分解文件 -->
  148. <hc-new-dialog v-model="splitModal" title="自动识别" widths="40vw" :loading="splitSaveLoad" @close="splitModalClose" @save="splitSave">
  149. <div class="split-modal-content">
  150. <div class="mb-6 text-4 font-bold">
  151. 本次识别共
  152. <span class="text-red">【{{ splitInfo?.fileCount }}】</span>
  153. 卷,预计耗费时长
  154. <span class="text-red">【{{ splitInfo?.taskTime }}】</span>
  155. 分钟,分解完成之后,序号图标会变成 <HcIcon name="checkbox-circle" style="color:lightgreen" />
  156. </div>
  157. <div class="mb-6 text-orange">请不要重复提交,过会儿再来查看,识别好的文件在对应节点可查看</div>
  158. <!-- <el-button type="primary" size="large" class="split-confirm-btn" hc-btn @click="splitModalClose">好的,我知道了</el-button> -->
  159. </div>
  160. </hc-new-dialog>
  161. </div>
  162. </template>
  163. <script setup>
  164. import { onMounted, ref } from 'vue'
  165. import scanApi from '~api/archiveFile/scanning'
  166. import { useAppStore } from '~src/store'
  167. import HcTree from '~src/components/tree/hc-tree.vue'
  168. import { arrToId, deepClone, getArrValue, getObjValue } from 'js-fast-way'
  169. import MenuItem from './MenuItem.vue' // 导入递归组件
  170. import { toPdfPage } from '~uti/btn-auth'
  171. import tasksApi from '~api/tasks/data'
  172. import { HcFirmMsg, HcUploadFileApi } from 'hc-vue3-ui'
  173. const useAppState = useAppStore()
  174. const contractId = ref(useAppState.getContractId)
  175. const projectId = ref(useAppState.getProjectId)
  176. const folderLoading = ref(false)
  177. onMounted(()=>{
  178. getMenuFolderData()
  179. getSplitInfo()
  180. })
  181. //菜单数据
  182. const folderData = ref([])
  183. const isCollapse = ref(false)
  184. // 记录当前选中的完整路径
  185. const selectedKeyPath = ref([])
  186. const folderId = ref('') // 当前选中的文件夹ID
  187. // 处理菜单选择事件 - 记录完整路径
  188. const handleSelect = (key, keyPath) => {
  189. selectedKeyPath.value = keyPath
  190. folderId.value = key
  191. getTableData()
  192. }
  193. const getMenuFolderData = async ()=>{
  194. folderLoading.value = true
  195. const { error, code, data } = await scanApi.getScanFolder({
  196. ...searchForm.value,
  197. projectId: projectId.value,
  198. contractId: contractId.value,
  199. })
  200. folderLoading.value = false
  201. if (!error && code === 200) {
  202. folderData.value = getArrValue(data)
  203. } else {
  204. folderData.value = []
  205. }
  206. }
  207. //分页被点击
  208. const pageChange = ({ current, size }) => {
  209. searchForm.value.current = current
  210. searchForm.value.size = size
  211. }
  212. //搜索表单
  213. const searchForm = ref({
  214. contractId: null, type: null, approval: null, betweenTime: null,
  215. current: 1, size: 20, total: 0,
  216. })
  217. //表格数据
  218. const tableRef = ref(null)
  219. const tableColumn = ref([
  220. { key: 'digitalNum', name: '数字编号' },
  221. { key: 'fileNum', name: '文件编号' },
  222. { key: 'fileName', name: '文件题名' },
  223. { key: 'fileSize', name: '文件页数' },
  224. { key: 'fileDate', name: '文件日期' },
  225. { key: 'responsible', name: '责任者' },
  226. ])
  227. const tableData = ref([ ])
  228. const viewPdf = (ossUrl)=>{
  229. toPdfPage(ossUrl)
  230. }
  231. //设置单元格的样式
  232. const tableCellStyle = ({ row, column, rowIndex, columnIndex }) => {
  233. if (row.id === 1 && column.property === 'name') {
  234. return {
  235. backgroundColor: '#FF7D43',
  236. color: 'black',
  237. }
  238. }
  239. }
  240. //获取数据
  241. const tableLoading = ref(false)
  242. const getTableData = async () => {
  243. tableLoading.value = true
  244. const { error, code, data } = await scanApi.getScanFile({
  245. ...searchForm.value,
  246. projectId: projectId.value,
  247. contractId: contractId.value,
  248. folderId: folderId.value,
  249. })
  250. tableLoading.value = false
  251. if (!error && code === 200) {
  252. tableData.value = getArrValue(data['records'])
  253. searchForm.value.total = data['total'] || 0
  254. } else {
  255. tableData.value = []
  256. searchForm.value.total = 0
  257. }
  258. }
  259. //多选
  260. const tableCheckedKeys = ref([])
  261. const tableSelection = (rows) => {
  262. tableCheckedKeys.value = rows
  263. }
  264. //跨目录移动
  265. const treePanelLoading = ref(false)
  266. const panelTreeLoading = () => {
  267. treePanelLoading.value = false
  268. }
  269. const movesModal = ref(false)
  270. const movesClick = async () => {
  271. movesModal.value = true
  272. checkedMoves.value = []
  273. movesCheckAll.value = false
  274. treePanelLoading.value = true
  275. fileDatasListLoading.value = true
  276. const { error, code, data } = await scanApi.getScanFile({
  277. ...searchForm.value,
  278. size: 5000,
  279. projectId: projectId.value,
  280. contractId: contractId.value,
  281. folderId: folderId.value,
  282. move: 0,
  283. })
  284. fileDatasListLoading.value = false
  285. if (!error && code === 200) {
  286. fileDatasList.value = getArrValue(data['records'])
  287. } else {
  288. fileDatasList.value = []
  289. }
  290. }
  291. //选择需要迁移的文件
  292. const movesCheckAll = ref(false)
  293. const isIndeterminate = ref(true)
  294. const checkedMoves = ref([])
  295. //左侧待迁移文件
  296. const fileDatasList = ref([])
  297. const fileDatasListLoading = ref(false)
  298. //全选
  299. const handleCheckAllChange = (val) => {
  300. console.log(val, 'val')
  301. const checked = fileDatasList.value
  302. checkedMoves.value = val ? checked : []
  303. isIndeterminate.value = false
  304. }
  305. //勾选
  306. const handleCheckedMovesChange = (value) => {
  307. const keys = fileDatasList.value
  308. const checkedCount = value.length
  309. movesCheckAll.value = checkedCount === keys.length
  310. isIndeterminate.value = checkedCount > 0 && checkedCount < keys.length
  311. }
  312. //右侧radio
  313. let radioNodeId = ''
  314. const radioChange = (id) => {
  315. //console.log(id)
  316. radioNodeId = id
  317. }
  318. //只显示储存节点的单选
  319. const showRadioFun = (data) => {
  320. if (data.isStorageNode == 1) {
  321. return true
  322. } else {
  323. return false
  324. }
  325. }
  326. //保存
  327. const movesModalLoading = ref(false)
  328. const movesModalSave = async () => {
  329. const keys = arrToId(checkedMoves.value).split(',')
  330. let ids = keys
  331. console.log(keys, 'keys')
  332. if (checkedMoves.value.length < 1) {
  333. window.$message?.warning('请勾选需要迁移的文件')
  334. return
  335. }
  336. if (radioNodeId == '') {
  337. window.$message?.warning('请选择要移动到的节点')
  338. return
  339. }
  340. movesModalLoading.value = true
  341. const { error, code, data } = await scanApi.moveScanFile({
  342. ids: ids,
  343. nodeId: radioNodeId,
  344. })
  345. movesModalLoading.value = false
  346. if (!error && code === 200) {
  347. window.$message?.success('保存成功')
  348. movesModal.value = false
  349. getTableData()
  350. } else {
  351. window.$message?.error('保存失败')
  352. }
  353. }
  354. //关闭
  355. const movesModalClose = () => {
  356. movesModal.value = false
  357. }
  358. //开始扫描
  359. const scanLoading = ref(false)
  360. const scanClick = async () => {
  361. scanLoading.value = true
  362. const { error, code, data, msg } = await scanApi.startOrEndScan({
  363. type: 1,
  364. contractId: contractId.value,
  365. })
  366. scanLoading.value = false
  367. if (!error && code === 200) {
  368. window.$message?.success(msg)
  369. getTableData()
  370. } else {
  371. window.$message?.warning(msg)
  372. }
  373. movesModal.value = false
  374. }
  375. //编辑
  376. const editModal = ref(false)
  377. const editClick = () => {
  378. editModal.value = true
  379. tableEditData.value = deepClone(tableCheckedKeys.value)
  380. }
  381. const editLoading = ref(false)
  382. const editModalSave = async () => {
  383. editLoading.value = true
  384. const { error, code } = await scanApi.updateScanFile(
  385. tableEditData.value)
  386. //判断状态
  387. editLoading.value = false
  388. if (!error && code === 200) {
  389. window.$message?.success('保存成功')
  390. editModalClose()
  391. getTableData()
  392. } else {
  393. window.$message?.error('保存失败')
  394. }
  395. }
  396. const editModalClose = () => {
  397. editModal.value = false
  398. tableRef.value?.clearSelection()
  399. }
  400. const tableEditColumn = ref([
  401. { key: 'fileNum', name: '文件编号' },
  402. { key: 'fileName', name: '文件题名' },
  403. { key: 'fileDate', name: '文件日期' },
  404. { key: 'responsible', name: '责任者' },
  405. ])
  406. const tableEditData = ref([])
  407. const delClick = async (_, resolve) => {
  408. let ids = []
  409. tableCheckedKeys.value.forEach((element)=>{
  410. ids.push(element.id)
  411. })
  412. const { error, code } = await scanApi.deleteScanFile({
  413. ids: ids.join(','),
  414. })
  415. //处理数据
  416. if (!error && code === 200) {
  417. window.$message?.success('操作成功')
  418. getTableData()
  419. }
  420. resolve()
  421. }
  422. //自动识别功能
  423. const taskMount = ref('')
  424. const splitLoad = ref(false)
  425. const splitClick = async ()=>{
  426. console.log('分解文件')
  427. splitLoad.value = true
  428. const rows = tableCheckedKeys.value
  429. if (rows.length < 1) {
  430. // await window.$message.warning('没有选择案卷时,将会对该合同段下所有案卷进行分解')
  431. HcFirmMsg({
  432. title: '操作确认',
  433. text: '没有选择案卷时,将会对该合同段下所有案卷进行识别',
  434. }, async (resolve) => {
  435. getSaveInfoData()
  436. resolve() //关闭弹窗的回调
  437. })
  438. } else {
  439. getSaveInfoData()
  440. }
  441. // splitModal.value = true
  442. }
  443. const getSaveInfoData = async ()=>{
  444. const rows = tableCheckedKeys.value
  445. const { error, code, data, msg } = await tasksApi.getSaveInfo({
  446. contractId:contractId.value,
  447. projectId:projectId.value,
  448. ids:rows.map(item=>item.id).join(','),
  449. dataType:2,
  450. })
  451. splitLoad.value = false
  452. //处理数据
  453. if (!error && code === 200) {
  454. splitInfo.value = data
  455. splitModal.value = true
  456. splitInfo.value = getObjValue(data) || {}
  457. } else {
  458. window.$message.error(msg)
  459. }
  460. // splitModal.value = true
  461. }
  462. const splitSaveLoad = ref(false)
  463. const splitSave = async ()=>{
  464. const rows = tableCheckedKeys.value
  465. splitSaveLoad.value = true
  466. const { error, code, data, msg } = await tasksApi.saveSplit({
  467. contractId:contractId.value,
  468. projectId:projectId.value,
  469. ids:rows.map(item=>item.id).join(','),
  470. dataType:2,
  471. })
  472. splitSaveLoad.value = false
  473. //处理数据
  474. if (!error && code === 200) {
  475. splitInfo.value = data
  476. splitModal.value = true
  477. splitInfo.value = getObjValue(data) || {}
  478. window.$message.success(msg)
  479. getSplitInfo()
  480. splitModal.value = false
  481. getTableData()
  482. } else {
  483. window.$message.error(msg)
  484. }
  485. }
  486. const splitInfo = ref({})
  487. const splitList = ref([
  488. ])
  489. // 在script setup中添加
  490. const splitListModal = ref(false)
  491. const splitListLoading = ref(false)
  492. const splitListColumn = ref([
  493. { key: 'taskName', name: '任务名称' },
  494. { key: 'finished', name: '完成数量', width: 100 },
  495. { key: 'toolCount', name: '总条数', width: 100 },
  496. ])
  497. const splitListModalClose = () => {
  498. splitListModal.value = false
  499. }
  500. // 修改taskCountClick方法
  501. const taskCountClick = async () => {
  502. await getSplitInfo()
  503. splitListModal.value = true
  504. }//查询任务信息
  505. const getSplitInfo = async ()=>{
  506. const { error, code, data } = await tasksApi.selectTaskSplit({
  507. contractId:contractId.value,
  508. projectId:projectId.value,
  509. dataType:2,
  510. })
  511. //处理数据
  512. if (!error && code === 200) {
  513. splitList.value = data['records']
  514. if (data['records'].length > 0) {
  515. taskMount.value = data['total']
  516. }
  517. } else {
  518. splitList.value = []
  519. taskMount.value = ''
  520. }
  521. }
  522. const splitModal = ref(false)
  523. const splitModalClose = ()=>{
  524. splitModal.value = false
  525. }
  526. </script>
  527. <style scoped lang="scss">
  528. .custom-menu {
  529. width: 100%;
  530. min-height: 400px;
  531. border-right: none !important;
  532. }
  533. .hc-card {
  534. position: relative;
  535. }
  536. .hc-moves-transfer-box {
  537. position: relative;
  538. display: flex;
  539. height: 100%;
  540. align-items: center;
  541. .hc-moves-transfer-panel {
  542. width: 47%;
  543. position: relative;
  544. height: 100%;
  545. background: #ffffff;
  546. display: inline-block;
  547. text-align: left;
  548. border: 1px solid #ebeef5;
  549. border-radius: 4px;
  550. .panel-header {
  551. position: relative;
  552. background: #f5f7fa;
  553. display: flex;
  554. align-items: center;
  555. height: 48px;
  556. padding: 0 16px;
  557. border-bottom: 1px solid #ebeef5;
  558. border-radius: 4px 4px 0 0;
  559. .panel-header-label {
  560. position: relative;
  561. flex: 1;
  562. }
  563. .panel-header-extra {
  564. color: #aaaaaa;
  565. }
  566. }
  567. .panel-body {
  568. position: relative;
  569. padding: 15px;
  570. height: calc(100% - 48px);
  571. .hc-file-checkbox {
  572. position: relative;
  573. }
  574. .hc-file-checkbox + .hc-file-checkbox {
  575. margin-top: 16px;
  576. }
  577. }
  578. }
  579. .hc-moves-transfer-buttons {
  580. display: inline-block;
  581. vertical-align: middle;
  582. padding: 0 16px;
  583. }
  584. }
  585. </style>
  586. <style scoped>
  587. /* 解决文件名过长问题的样式 */
  588. .file-item {
  589. margin-bottom: 10px; /* 增加项目之间的间距 */
  590. margin-top: 8px;
  591. }
  592. .file-name {
  593. white-space: normal; /* 允许换行 */
  594. word-wrap: break-word; /* 长单词换行 */
  595. word-break: break-all; /* 强制换行 */
  596. display: inline-block;
  597. vertical-align: middle;
  598. line-height: 1.5; /* 调整行高,使多行文本更易读 */
  599. }
  600. </style>