index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. <!--
  2. * @Descripttion: POCT文件培训
  3. * @version: 1.0
  4. * @Author: Liu_jiaYin
  5. * @Date: 2024-03-01 13:47:32
  6. * @LastEditors: Do not edit
  7. * @LastEditTime: 2024-04-26 16:04:02
  8. -->
  9. <template>
  10. <!-- <el-dialog
  11. :visible.sync="dialogVisible"
  12. fullscreen
  13. :title="title"
  14. append-to-body
  15. custom-class="ibps-file-preview-dialog"
  16. >
  17. <div>
  18. <fView v-if="refresh" ref="fvView" :option-file="optionFile" :operation_status="operation_status" @hadLoadedFile="hadLoadedFile" />
  19. </div>
  20. </el-dialog> -->
  21. <!--个人修改 -->
  22. <el-dialog
  23. :visible.sync="dialogVisible"
  24. fullscreen
  25. append-to-body
  26. custom-class="ibps-file-preview-dialog"
  27. :show-close="false"
  28. >
  29. <template #title>
  30. <el-row>
  31. <el-col :span="11" class="titleHander">{{ title }}</el-col>
  32. <el-col :span="3" class="read" style="text-align: right;">阅读量:{{ lookNum }}</el-col>
  33. <el-col :span="10" style="text-align: right;">
  34. <el-popover
  35. v-model="deleteVisible"
  36. placement="top"
  37. width="160"
  38. >
  39. <p>文件删除之后将不能查看,确定要删除该文件吗?</p>
  40. <div style="text-align: right; margin: 0">
  41. <el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
  42. <el-button type="primary" size="mini" @click="deleteFile">确定</el-button>
  43. </div>
  44. <el-button v-if="deleteShow" slot="reference" type="danger" icon="el-icon-delete" class="deleteBtn" @click="deleteVisible=true">删除</el-button>
  45. </el-popover>
  46. <el-button type="primary" icon="el-icon-view" @click="lookFile">查看文件信息</el-button>
  47. <el-button type="primary" icon="el-icon-s-fold" @click="hideLeft">{{ leftContent }}</el-button>
  48. <el-button v-if="updateShow" type="primary" icon="el-icon-download" @click="updateFile">下载文件</el-button>
  49. <el-button type="danger" icon="el-icon-close" @click="closeDialog">关闭</el-button>
  50. </el-col>
  51. </el-row>
  52. </template>
  53. <div>
  54. <el-row>
  55. <el-col v-if="leftShow" :span="4" class="left-content">
  56. <div class="left-title">文件修订历史</div>
  57. <el-timeline :reverse="reverse">
  58. <el-timeline-item
  59. v-for="(activity, index) in leftData"
  60. :key="index"
  61. :timestamp="activity.fa_fang_shi_jian_"
  62. :type="index === activeIndex ? type : ''"
  63. @click.stop.native="toggleActive(activity, index)"
  64. >
  65. <div class="timeline-content">
  66. <el-tooltip class="itemStyle" effect="dark" placement="right-end" :content="showContent(activity,index)">
  67. <div>版本号:{{ activity.ban_ben_ }}/修订人:{{ getUserName(activity.bian_zhi_ren_) }}</div>
  68. </el-tooltip>
  69. </div>
  70. </el-timeline-item>
  71. </el-timeline>
  72. </el-col>
  73. <el-col :span="computedSpan"><fView v-if="refresh" ref="fvView" :option-file="optionFile" :operation_status="operation_status" @hadLoadedFile="hadLoadedFile" /></el-col>
  74. </el-row>
  75. </div>
  76. <!-- 查看文件信息弹窗 @close="closeDialog"-->
  77. <FileDialog
  78. v-if="innerVisible"
  79. :show-list="showList"
  80. :dig-data="digData"
  81. :inner-visible="innerVisible"
  82. :file-index="fileIndex"
  83. @pause="pauseTimer"
  84. @start="startTimer"
  85. @update-inner-visible="updateInnerVisible"
  86. />
  87. </el-dialog>
  88. </template>
  89. <script>
  90. /**
  91. * 文件预览
  92. * 1、'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx' 类型支持
  93. * 2、图片支持缩放
  94. * 3、音频,语音支持
  95. * ==================
  96. * 下一版本支持
  97. * 1、pdf支持缩放
  98. * 2、音频,语音多格式支持
  99. * 3、压缩包支持
  100. */
  101. import fView from '@/business/platform/file/attachment/editFile/fView.vue'
  102. import { SYSTEM_URL, BASE_API } from '@/api/baseUrl'
  103. import ViewFile from '@/views/viewFile/index.vue'
  104. import Template from '@/business/platform/form/form-print/template.vue'
  105. import curdPost from '@/business/platform/form/utils/custom/joinCURD.js'
  106. import FileDialog from './fileDialog.vue'
  107. // import * as selectbox from 'bpmn-js-properties-panel/lib/factory/SelectEntryFactory'
  108. // import func from 'vue-editor-bridge'
  109. export default {
  110. name: 'file-training',
  111. components: {
  112. fView,
  113. ViewFile,
  114. Template,
  115. FileDialog
  116. },
  117. props: {
  118. visible: {
  119. type: Boolean,
  120. default: false
  121. },
  122. // fileInfos: {
  123. // type: Object,
  124. // default: () => {}
  125. // },
  126. fileInfos: {
  127. type: Array,
  128. default: () => {}
  129. }
  130. // template: {
  131. // type: Object,
  132. // default: () => {}
  133. // }
  134. },
  135. data () {
  136. const { userList, userId, role, isSuper } = this.$store.getters
  137. // const userId = this.$store.getters.userInfo.employee.id// 本人修改
  138. return {
  139. updateShow: false,
  140. type: 'success',
  141. curFileName: '',
  142. curZid: '',
  143. timeId: '',
  144. dialogVisible: false,
  145. operation_status: 'fileTraining',
  146. title: '',
  147. browseTime: 0, // 浏览时长初始值为 0
  148. clearTimeSet: null,
  149. optionFile: {},
  150. tmpId: '',
  151. upFunc: () => {},
  152. height: 0,
  153. out: false, // 记录鼠标是否离开过被监听的位置,未离开过则startTimer不启用
  154. hadLoad: false,
  155. refresh: false,
  156. // 本人修改
  157. leftShow: true,
  158. scrollTimeout: null,
  159. reverse: false,
  160. fileInfo: '',
  161. fileJie: '',
  162. leftData: [],
  163. activeIndex: 0,
  164. userList: userList,
  165. userId: userId,
  166. innerVisible: false,
  167. currentPage: 1,
  168. pageSize: 10,
  169. lookNum: null,
  170. showList: [],
  171. digData: null,
  172. paused: false,
  173. role: role,
  174. isSuper: isSuper,
  175. deleteVisible: false,
  176. deleteShow: false,
  177. leftContent: '隐藏修订历史'
  178. }
  179. },
  180. // 本人修改
  181. computed: {
  182. computedSpan () {
  183. return this.leftShow ? 20 : 24
  184. },
  185. fileIndex () {
  186. if (this.leftData.length <= 0) {
  187. return 0
  188. }
  189. return this.leftData.findIndex(i => i === this.digData)
  190. }
  191. },
  192. watch: {
  193. visible: {
  194. immediate: true,
  195. handler (val) {
  196. this.digData = this.leftData[0]
  197. }
  198. },
  199. leftShow: {
  200. immediate: true, // 强制执行一次
  201. handler (val) {
  202. if (val) {
  203. this.leftContent = '隐藏修订历史'
  204. return
  205. }
  206. this.leftContent = '显示修订历史'
  207. }
  208. },
  209. // 本人修改
  210. fileInfos: {
  211. handler (newVal) {
  212. this.leftData = newVal
  213. const temp = JSON.parse(JSON.stringify(newVal))
  214. temp.sort((a, b) => {
  215. return new Date(b.fa_fang_shi_jian_).getTime() - new Date(a.fa_fang_shi_jian_).getTime()
  216. })
  217. if (newVal !== temp) {
  218. this.leftData = temp
  219. }
  220. temp.forEach(val => {
  221. this.fileShow(temp[0])
  222. })
  223. // newVal.forEach(val => {
  224. // this.fileShow(val)
  225. // })
  226. },
  227. deep: true, // 深度监听,确保对象属性变化也能触发(本人修改)
  228. immediate: true
  229. },
  230. browseTime: {
  231. handler: function (val, oldVal) {
  232. if (this.curFileName) {
  233. this.title = `文件:《 ${this.curFileName} 》,查阅时长:${val}秒`
  234. } else {
  235. this.title = `文件:《 ${this.leftData[0]?.FILE_NAME_} 》,查阅时长:${val}秒`
  236. }
  237. if (!this.lookNum) {
  238. if (this.leftData[0]?.id) {
  239. this.checkNum(this.leftData[0])
  240. }
  241. }
  242. },
  243. immediate: true
  244. },
  245. dialogVisible: {
  246. handler: function (val, oldVal) {
  247. if (!val) {
  248. this.closeDialog()
  249. }
  250. }
  251. }
  252. },
  253. beforeDestroy () {
  254. this.fileType = ''
  255. this.option = {}
  256. // 本人修改
  257. this.removeMouseMoveListener()
  258. },
  259. mounted () {
  260. this.height = this.getDialogHeightHeight()
  261. // 页面切换时改变计时状态
  262. document.addEventListener('visibilitychange', this.handlePageChange)
  263. this.checkDialogBody()
  264. const roleKey = ['xtgljs']
  265. const curRole = this.role.map(i => i.alias)
  266. const isPower = curRole.some(item => roleKey.includes(item))
  267. this.deleteShow = !!((isPower || this.isPower))
  268. const hasRole = localStorage.getItem('hasHighRole') || 0
  269. if (this.isSuper || hasRole == 1) {
  270. this.updateShow = true
  271. }
  272. },
  273. methods: {
  274. getUserName (data) {
  275. const user = this.userList.find(item => item.userId === data)
  276. return user ? user.userName : '未知用户'
  277. },
  278. showContent (activity, index) {
  279. if (activity.cao_zuo_lei_xing_ === '新增') {
  280. return '第一版本'
  281. }
  282. return activity.xiu_ding_nei_rong ? activity.xiu_ding_nei_rong : '无修订原因'
  283. },
  284. handlePageChange () {
  285. if (document.visibilityState === 'hidden') {
  286. this.pauseTimer()
  287. } else {
  288. this.startTimer()
  289. }
  290. },
  291. closeDialog () {
  292. // 关闭时存入查阅时间
  293. if (this.browseTime && this.browseTime > 0 && this.timeId) {
  294. this.handleUpdate()
  295. }
  296. this.$emit('colseVisible', false)
  297. const fvView = this.$refs.fvView
  298. // 销毁子组件方法
  299. fvView.destoryZiComponent()
  300. if (this.browseTime >= 30) {
  301. this.upFunc(this.tmpId, this.browseTime)
  302. }
  303. // 针对关闭窗口或者浏览器的
  304. if (this.clearTimeSet != null) {
  305. clearInterval(this.clearTimeSet)
  306. this.clearTimeSet = null
  307. }
  308. this.leftShow = true
  309. this.out = false
  310. this.browseTime = 0
  311. // this.curFileName = ''// 本人添加
  312. // this.lookNum = null
  313. // this.showList = []
  314. // this.leftData = []
  315. // this.digData = null
  316. },
  317. hadLoadedFile (v) {
  318. // 计时开始,添加查看记录
  319. if (!this.curZid) {
  320. this.handleAdd(this.leftData[0]?.zId, 0)
  321. }
  322. this.setBrowseTime()
  323. this.hadLoad = true
  324. },
  325. setBrowseTime () {
  326. // 设置定时器
  327. this.clearTimeSet = setInterval(() => {
  328. this.browseTime++
  329. }, 1000)
  330. },
  331. getDialogHeightHeight () {
  332. return ((document.documentElement.clientHeight || document.body.clientHeight) - 60) + 'px'
  333. },
  334. startTimer () {
  335. if (this.dialogVisible && this.hadLoad && this.out && this.clearTimeSet == null) {
  336. this.clearTimeSet = setInterval(() => {
  337. this.browseTime++
  338. }, 1000)
  339. }
  340. },
  341. pauseTimer () {
  342. if (this.dialogVisible) {
  343. this.out = true
  344. clearInterval(this.clearTimeSet)
  345. this.clearTimeSet = null
  346. }
  347. },
  348. // 本人修改
  349. // id转换
  350. async idChange (id) {
  351. const sql = `select id_ FROM t_wjxxb WHERE shu_ju_lai_yuan_ = '${id}'`
  352. return new Promise((resolve, reject) => {
  353. this.$common.request('sql', sql).then((res) => {
  354. const { data = [] } = res.variables || {}
  355. const firstId = data[0]?.id_
  356. resolve(firstId) // 解析 Promise 时返回 firstId
  357. }).catch(error => {
  358. reject(error) // 捕获错误并拒绝 Promise
  359. })
  360. })
  361. },
  362. toggleActive (activity, index) {
  363. if (this.activeIndex === index) { return }
  364. // 切换文件修订历史时,保存上一个文件查看记录,新增当前文件查看记录
  365. if (this.browseTime && this.browseTime > 0 && this.timeId) {
  366. this.handleUpdate()
  367. this.handleAdd(this.curZid || this.leftData[0]?.zId, 0)
  368. }
  369. this.activeIndex = index
  370. this.digData = activity
  371. this.fileShow(activity)
  372. if (this.browseTime >= 30) {
  373. this.upFunc(this.tmpId, this.browseTime)
  374. }
  375. clearInterval(this.clearTimeSet)
  376. this.browseTime = 0
  377. this.curFileName = activity.FILE_NAME_
  378. this.curZid = activity.zid
  379. this.checkNum(activity)// 阅读量
  380. // this.$forceUpdate()// 触发监听器
  381. },
  382. // 阅读量函数
  383. async checkNum (activity) {
  384. const sql = `select t_wjcyjl.* from t_wjcyjl
  385. INNER JOIN t_wjxxb ON t_wjcyjl.parent_id_ = t_wjxxb.id_
  386. WHERE t_wjxxb.shu_ju_lai_yuan_ = '${activity.id}' order by create_time_ desc`
  387. // const sql1 = `select * from t_wjcyjl where parent_id_= '${activity.id}' order by create_time_ desc`
  388. await this.$common.request('sql', sql).then((res) => {
  389. const { data = [] } = res.variables || {}
  390. this.lookNum = data.length
  391. this.showList = data
  392. })
  393. },
  394. handleAdd (fileId, time) {
  395. const addParams = {
  396. tableName: 't_wjcyjl',
  397. paramWhere: [
  398. {
  399. bian_zhi_ren_: this.userId,
  400. bian_zhi_shi_jian: this.$common.getDateNow(19),
  401. parent_id_: fileId,
  402. shi_chang_: time
  403. }
  404. ]
  405. }
  406. curdPost('add', addParams).then(res => {
  407. // this.refreshData()
  408. const { cont = [] } = res.variables || {}
  409. this.timeId = cont[0]?.id_ || ''
  410. })
  411. },
  412. handleUpdate () {
  413. const updateParams = {
  414. tableName: 't_wjcyjl',
  415. updList: [{
  416. where: {
  417. id_: this.timeId
  418. },
  419. param: {
  420. // shi_chang_: this.browseTime
  421. shi_chang_: 20
  422. }
  423. }]
  424. }
  425. curdPost('update', updateParams).then((res) => {
  426. })
  427. },
  428. hideLeft () {
  429. this.leftShow = !this.leftShow
  430. },
  431. async lookFile () {
  432. // console.log(document.querySelector('iframe').contentWindow.document)
  433. // console.log(document.querySelector('iframe').contentWindow.document.body.innerHTML);
  434. if (this.digData) {
  435. await this.checkNum(this.digData)
  436. } else {
  437. this.digData = this.leftData[0]
  438. await this.checkNum(this.digData)
  439. }
  440. this.innerVisible = true
  441. },
  442. a () {
  443. fetch(this.optionFile.url)
  444. .then(response => {
  445. if (response.ok) {
  446. // 如果响应状态码为 200-299,则创建下载链接
  447. const a = document.createElement('a')
  448. a.href = this.optionFile.url
  449. a.download = this.optionFile.data.fileName
  450. document.body.appendChild(a)
  451. a.click()
  452. a.remove()
  453. } else {
  454. // 如果响应状态码不是 200-299,则显示错误消息
  455. this.$message({
  456. message: '文件未找到,请联系管理员',
  457. type: 'warning'
  458. })
  459. console.error('文件未找到:', response.status, response.statusText)
  460. }
  461. })
  462. .catch(error => {
  463. // 捕获网络请求错误
  464. this.$message({
  465. message: '网络请求失败,请联系管理员',
  466. type: 'warning'
  467. })
  468. console.error('网络请求失败:', error)
  469. })
  470. },
  471. deleteFile () {
  472. this.deleteVisible = false
  473. // const roleKey = ['xtgljs', 'syszr', 'wjgly', 'wjglzzc']
  474. const roleKey = ['xtgljs']
  475. const curRole = this.role.map(i => i.alias)
  476. const isPower = curRole.some(item => roleKey.includes(item))
  477. if (this.isSuper || isPower) {
  478. const deleteParams = {
  479. tableName: 't_wjxxb',
  480. paramWhere: { id_: this.leftData[0].zId }
  481. }
  482. curdPost('delete', deleteParams).then(() => {
  483. this.$message({
  484. message: '删除成功!',
  485. type: 'warning'
  486. })
  487. this.dialogVisible = false
  488. })
  489. return
  490. }
  491. this.$message({
  492. message: '您还没有权限,请联系管理员',
  493. type: 'warning'
  494. })
  495. },
  496. // closeDialog1 (val) {
  497. // this.innerVisible = val
  498. // },
  499. updateFile () {
  500. const hasRole = localStorage.getItem('hasHighRole') || 0
  501. // const roleKey = ['xtgljs', 'wjglzzc', 'wjgly', 'zhsfzr']
  502. // const curRole = this.role.map(i => i.alias)
  503. // const isPower = curRole.some(i => roleKey.includes(i))
  504. if (this.isSuper || hasRole == 1) {
  505. const a = document.createElement('a')
  506. a.href = this.optionFile.url
  507. a.download = this.optionFile.data.fileName
  508. document.body.appendChild(a)
  509. a.click()
  510. a.remove()
  511. return
  512. }
  513. this.$message({
  514. message: '您还没有权限,请联系管理员',
  515. type: 'warning'
  516. })
  517. },
  518. fileShow (val) {
  519. return new Promise((resolve, reject) => {
  520. try {
  521. this.dialogVisible = true
  522. this.title = `文件:《${val.FILE_NAME_}》`
  523. this.idChange(val.id).then(res => {
  524. this.tmpId = res
  525. if (val.func) { this.upFunc = val.func }
  526. const data = {
  527. ext: val.fileInfos.EXT_,
  528. fileName: val.fileInfos.FILE_NAME_,
  529. id: val.fileInfos.ID_,
  530. index: 0,
  531. totalBytes: val.fileInfos.TOTAL_BYTES_
  532. }
  533. this.optionFile.url = `${this.$onlyofficeApi}/file/download?attachmentId=${data.id}`
  534. this.optionFile.editUrl = `${this.$onlyofficeApi}/file/editCallback?fileName=${data.fileName}&fileType=${data.ext}&type=fileTraining&id=${data.id}`
  535. this.optionFile.title = data.fileName // 文件名称
  536. this.optionFile.fileType = data.ext // 类型
  537. this.optionFile.data = data // 记录编制的位置,需要替换。
  538. this.optionFile.data.index = data.index
  539. // 使用 v-if 实现组件刷新功能
  540. this.refresh = false
  541. this.$nextTick(() => {
  542. this.refresh = true
  543. resolve() // 异步操作完成后 resolve
  544. })
  545. })
  546. } catch (error) {
  547. reject(error)
  548. }
  549. })
  550. },
  551. // 排序函数
  552. sortByFabushijianDesc (data) {
  553. return data.sort((a, b) => {
  554. const dateA = new Date(a.fa_fang_shi_jian_)
  555. const dateB = new Date(b.fa_fang_shi_jian_)
  556. return dateB - dateA // 降序排序
  557. })
  558. },
  559. formattedTimestamp (timestamp) {
  560. const date = new Date(timestamp)
  561. // 获取年月日时分秒
  562. const year = date.getFullYear()
  563. const month = String(date.getMonth() + 1).padStart(2, '0')
  564. const day = String(date.getDate()).padStart(2, '0')
  565. const hours = String(date.getHours()).padStart(2, '0')
  566. const minutes = String(date.getMinutes()).padStart(2, '0')
  567. const seconds = String(date.getSeconds()).padStart(2, '0')
  568. // 格式化日期
  569. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  570. },
  571. checkDialogBody () {
  572. const intervalId = setInterval(() => {
  573. if (document.querySelector('iframe')) {
  574. this.dialogBody = document.querySelector('iframe')
  575. this.addMouseMoveListener()
  576. clearInterval(intervalId)
  577. }
  578. }, 200)
  579. },
  580. addMouseMoveListener () {
  581. if (this.dialogBody && !this.mouseMoveHandler) {
  582. this.mouseMoveHandler = () => {
  583. console.log('鼠标移动事件!!!!')
  584. }
  585. this.dialogBody.addEventListener('mousemove', this.mouseMoveHandler, true)
  586. }
  587. },
  588. removeMouseMoveListener () {
  589. if (this.dialogBody && this.mouseMoveHandler) {
  590. this.dialogBody.removeEventListener('mousemove', this.mouseMoveHandler, true)
  591. this.mouseMoveHandler = null
  592. }
  593. },
  594. updateInnerVisible (newVal) {
  595. this.innerVisible = newVal
  596. }
  597. }
  598. }
  599. </script>
  600. <style lang="scss">
  601. .ibps-file-preview-dialog {
  602. width: 80%;
  603. z-index:99999;
  604. .el-dialog__body {
  605. padding: 0;
  606. }
  607. .file-type-txt {
  608. height: calc(88vh) !important;
  609. }
  610. .itemStyle:hover{
  611. cursor: pointer;
  612. }
  613. .titleHander, .read{
  614. line-height: 32px;
  615. }
  616. .deleteBtn{
  617. margin: 0 10px 0 0;
  618. }
  619. }
  620. .left-content{
  621. .left-title{
  622. text-align: left;
  623. padding: 15px;
  624. font-size: 18px;
  625. font-weight: 600;
  626. }
  627. .el-timeline{
  628. padding: 0 15px;
  629. }
  630. }
  631. // .file-read-num{
  632. // display: inline-block;
  633. // margin-left: 60px;
  634. // }
  635. </style>