Box.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. <template>
  2. <!-- 右键列表 -->
  3. <transition name="fade">
  4. <!-- 在某个文件上右键 -->
  5. <ul
  6. class="right-menu-list"
  7. id="rightMenuList"
  8. v-show="visible"
  9. v-if="selectedFile !== undefined"
  10. :style="`top: ${rightMenu.top};right: ${rightMenu.right};bottom: ${rightMenu.bottom};left: ${rightMenu.left};`"
  11. >
  12. <li class="right-menu-item" @click="$file.handleFileNameClick(selectedFile, 0, [selectedFile])" v-if="seeBtnShow">
  13. <eye-outlined /> 查看
  14. </li>
  15. <li class="right-menu-item" @click="handleDeleteFileBtnClick(selectedFile)" v-if="deleteBtnShow">
  16. <delete-outlined /> 删除
  17. </li>
  18. <li class="right-menu-item" @click="handleRestoreFileBtnClick(selectedFile)" v-if="restoreBtnShow">
  19. <rollback-outlined /> 还原
  20. </li>
  21. <li class="right-menu-item" @click="handleCopyFileBtnClick(selectedFile)" v-if="copyBtnShow">
  22. <copy-outlined /> 复制到
  23. </li>
  24. <li class="right-menu-item" @click="handleMoveFileBtnClick(selectedFile)" v-if="moveBtnShow">
  25. <export-outlined /> 移动
  26. </li>
  27. <li class="right-menu-item" @click="handleRenameFileBtnClick(selectedFile)" v-if="renameBtnShow">
  28. <edit-outlined /> 重命名
  29. </li>
  30. <li class="right-menu-item" @click="handleShareFileBtnClick(selectedFile)" v-if="shareBtnShow">
  31. <share-alt-outlined /> 分享
  32. </li>
  33. <li class="right-menu-item" @click="visible = false" v-if="downloadBtnShow">
  34. <a
  35. target="_blank"
  36. style="display: block; color: inherit"
  37. :href="$file.getDownloadFilePath(selectedFile)"
  38. :download="selectedFile.fileName + '.' + selectedFile.extendName"
  39. >
  40. <download-outlined /> 下载
  41. </a>
  42. </li>
  43. <!-- 0-解压到当前文件夹, 1-自动创建该文件名目录,并解压到目录里, 3-手动选择解压目录 -->
  44. <li class="right-menu-item unzip-menu-item" v-if="unzipBtnShow">
  45. <folder-outlined /> 解压缩
  46. <right-outlined />
  47. <ul
  48. class="unzip-list"
  49. :style="`top: ${unzipMenu.top};bottom: ${unzipMenu.bottom};left: ${unzipMenu.left};right: ${unzipMenu.right};`"
  50. >
  51. <li class="unzip-item" @click="handleUnzipFileBtnClick(selectedFile, 0)">
  52. <folder-outlined /> 解压到当前文件夹
  53. </li>
  54. <li
  55. class="unzip-item"
  56. @click="handleUnzipFileBtnClick(selectedFile, 1)"
  57. :title="`解压到&quot;${selectedFile.fileName}&quot;`"
  58. >
  59. <folder-outlined /> 解压到"{{ selectedFile.fileName }}"
  60. </li>
  61. <li class="unzip-item" @click="handleUnzipFileBtnClick(selectedFile, 2)">
  62. <folder-outlined /> 解压到目标文件夹
  63. </li>
  64. </ul>
  65. </li>
  66. <!-- <li
  67. class="right-menu-item"
  68. @click="handleClickFolderEdit"
  69. v-if="folderEditBtnShow"
  70. >
  71. <edit-outlined /> 编辑文件夹
  72. </li> -->
  73. <!-- <li class="right-menu-item" @click="handleClickFileEdit(selectedFile)" v-if="onlineEditBtnShow">
  74. <edit-outlined /> 在线编辑
  75. </li> -->
  76. <li
  77. class="right-menu-item"
  78. @click="$file.copyShareLink(selectedFile.shareBatchNum, selectedFile.extractionCode)"
  79. v-if="copyLinkBtnShow"
  80. >
  81. <edit-outlined /> 复制链接
  82. </li>
  83. <li class="right-menu-item" @click="handleShowDetailInfo(selectedFile)" v-if="detailInfoBtnShow">
  84. <file-outlined /> 文件详情
  85. </li>
  86. </ul>
  87. <!-- 在空白处右键,右键列表展示新建文件夹、新建文件等操作按钮 -->
  88. <ul
  89. class="right-menu-list add"
  90. id="rightMenuList"
  91. v-show="visible"
  92. v-else
  93. :style="`top: ${rightMenu.top};right: ${rightMenu.right};bottom: ${rightMenu.bottom};left: ${rightMenu.left};`"
  94. >
  95. <li class="right-menu-item" @click="callback('confirm')"><reload-outlined /> 刷新</li>
  96. <template v-if="fileType === 0">
  97. <a-divider />
  98. <li class="right-menu-item" @click="handleClickAddFolderBtn"><folder-add-outlined /> 新建文件夹</li>
  99. <!-- <li class="right-menu-item" @click="handleCreateFile('docx')"><img :src="wordImg" />新建 Word 文档</li>
  100. <li class="right-menu-item" @click="handleCreateFile('xlsx')"><img :src="excelImg" />新建 Excel 工作表</li>
  101. <li class="right-menu-item" @click="handleCreateFile('pptx')"><img :src="pptImg" />新建 PPT 演示文稿</li> -->
  102. <a-divider />
  103. <li class="right-menu-item" @click="handleUploadFileBtnClick(1)"><upload-outlined /> 上传文件</li>
  104. <li class="right-menu-item" @click="handleUploadFileBtnClick(2)"><folder-open-outlined /> 上传文件夹</li>
  105. <li class="right-menu-item" @click="handleUploadFileBtnClick(3)"><drag-outlined /> 拖拽上传</li>
  106. </template>
  107. </ul>
  108. </transition>
  109. </template>
  110. <script setup>
  111. import { ref, computed, watch, getCurrentInstance } from 'vue'
  112. import { useRouter } from 'vue-router'
  113. import { useMyResourceStore } from '@/store/myResource.js'
  114. import {
  115. EyeOutlined,
  116. DeleteOutlined,
  117. RollbackOutlined,
  118. CopyOutlined,
  119. ExportOutlined,
  120. EditOutlined,
  121. ShareAltOutlined,
  122. DownloadOutlined,
  123. FolderOutlined,
  124. RightOutlined,
  125. FileOutlined,
  126. ReloadOutlined,
  127. FolderAddOutlined,
  128. UploadOutlined,
  129. FolderOpenOutlined,
  130. DragOutlined
  131. } from '@ant-design/icons-vue'
  132. import { officeFileType, fileSuffixCodeModeMap, markdownFileType } from '@/libs/map.js'
  133. // 导入图片资源
  134. import dirImgSrc from '@/assets/images/myResource/file/dir.png'
  135. import wordImgSrc from '@/assets/images/myResource/file/file_word.svg'
  136. import excelImgSrc from '@/assets/images/myResource/file/file_excel.svg'
  137. import pptImgSrc from '@/assets/images/myResource/file/file_ppt.svg'
  138. const router = useRouter()
  139. const myResourceStore = useMyResourceStore()
  140. // 定义props
  141. const props = defineProps({
  142. selectedFile: Object,
  143. domEvent: Object,
  144. serviceEl: Object,
  145. callback: Function
  146. })
  147. // 响应式数据
  148. const visible = ref(false) // 右键菜单是否显示
  149. const sortedFileList = ref([]) // 排序后的表格数据
  150. const { proxy } = getCurrentInstance()
  151. // 右键菜单位置
  152. const rightMenu = ref({
  153. top: 0,
  154. left: 0,
  155. bottom: 'auto',
  156. right: 'auto'
  157. })
  158. // 右键解压缩菜单位置
  159. const unzipMenu = ref({
  160. top: 0,
  161. bottom: 'auto',
  162. left: '126px',
  163. right: 'auto'
  164. })
  165. // 图片资源 - 使用导入的图片资源
  166. const dirImg = ref(dirImgSrc)
  167. const wordImg = ref(wordImgSrc)
  168. const excelImg = ref(excelImgSrc)
  169. const pptImg = ref(pptImgSrc)
  170. // 计算属性
  171. // 路由名称
  172. const routeName = computed(() => {
  173. return router.currentRoute.value.name
  174. })
  175. // 左侧菜单选中的文件类型
  176. const fileType = computed(() => {
  177. // return router.currentRoute.value.query.fileType ? Number(router.currentRoute.value.query.fileType) : 0
  178. return myResourceStore.getQuery.fileType ? Number(myResourceStore.getQuery.fileType) : 0
  179. })
  180. // 当前路径
  181. const filePath = computed(() => {
  182. // return router.currentRoute.value.query.filePath
  183. return myResourceStore.getQuery.filePath ? myResourceStore.getQuery.filePath : '/'
  184. })
  185. // 查看按钮是否显示
  186. const seeBtnShow = computed(() => {
  187. return fileType.value !== 6
  188. })
  189. // 删除按钮是否显示
  190. const deleteBtnShow = computed(() => {
  191. return fileType.value !== 8 && !['Share'].includes(routeName.value)
  192. })
  193. // 还原按钮是否显示
  194. const restoreBtnShow = computed(() => {
  195. return fileType.value === 6 && !['Share'].includes(routeName.value)
  196. })
  197. // 复制按钮是否显示
  198. const copyBtnShow = computed(() => {
  199. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  200. })
  201. // 移动按钮是否显示
  202. const moveBtnShow = computed(() => {
  203. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  204. })
  205. // 重命名按钮是否显示
  206. const renameBtnShow = computed(() => {
  207. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  208. })
  209. // 分享按钮是否显示
  210. const shareBtnShow = computed(() => {
  211. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  212. })
  213. // 下载按钮是否显示
  214. const downloadBtnShow = computed(() => {
  215. return ![6, 8].includes(fileType.value)
  216. })
  217. // 解压缩按钮是否显示
  218. const unzipBtnShow = computed(() => {
  219. return (
  220. ![6, 8].includes(fileType.value) &&
  221. !['Share'].includes(routeName.value) &&
  222. ['zip', 'rar', '7z', 'tar', 'gz'].includes(props.selectedFile.extendName)
  223. )
  224. })
  225. // 编辑文件夹按钮是否显示
  226. const folderEditBtnShow = computed(() => {
  227. return ![6, 8].includes(fileType.value) && props.selectedFile.isDir === 1 && !['Share'].includes(routeName.value)
  228. })
  229. // 在线编辑按钮是否显示
  230. const onlineEditBtnShow = computed(() => {
  231. return (
  232. ![6, 8].includes(fileType.value) &&
  233. (officeFileType.includes(props.selectedFile.extendName) ||
  234. markdownFileType.includes(props.selectedFile.extendName) ||
  235. fileSuffixCodeModeMap.has(props.selectedFile.extendName)) &&
  236. !['Share'].includes(routeName.value)
  237. )
  238. })
  239. // 复制链接按钮是否显示
  240. const copyLinkBtnShow = computed(() => {
  241. return fileType.value === 8
  242. })
  243. // 文件详情按钮是否显示
  244. const detailInfoBtnShow = computed(() => {
  245. return true
  246. })
  247. // 上传文件组件参数
  248. const uploadFileParams = computed(() => {
  249. return {
  250. filePath: filePath.value,
  251. isDir: 0
  252. }
  253. })
  254. // 监听右键列表状态
  255. watch(visible, (newValue) => {
  256. if (newValue === true) {
  257. document.body.addEventListener('click', closeRightMenu)
  258. handleOpenContextMenu()
  259. } else {
  260. document.body.removeEventListener('click', closeRightMenu)
  261. }
  262. })
  263. // 方法
  264. /**
  265. * 打开右键菜单
  266. */
  267. const handleOpenContextMenu = () => {
  268. // 纵坐标设置
  269. if (
  270. document.body.clientHeight - props.domEvent.clientY <
  271. document.querySelectorAll('#rightMenuList > .right-menu-item').length * 36 + 10
  272. ) {
  273. // 如果到底部的距离小于元素总高度
  274. rightMenu.value.top = 'auto'
  275. rightMenu.value.bottom = `${document.body.clientHeight - props.domEvent.clientY}px`
  276. unzipMenu.value.top = 'auto'
  277. unzipMenu.value.bottom = '0px'
  278. } else {
  279. rightMenu.value.top = `${props.domEvent.clientY}px`
  280. rightMenu.value.bottom = 'auto'
  281. unzipMenu.value.top = '0px'
  282. unzipMenu.value.bottom = 'auto'
  283. }
  284. // 横坐标设置
  285. if (document.body.clientWidth - props.domEvent.clientX < 138) {
  286. // 如果到右边的距离小于元素总宽度
  287. rightMenu.value.left = 'auto'
  288. rightMenu.value.right = `${document.body.clientWidth - props.domEvent.clientX}px`
  289. unzipMenu.value.left = '-200px'
  290. unzipMenu.value.right = 'auto'
  291. } else {
  292. rightMenu.value.left = `${props.domEvent.clientX + 8}px`
  293. rightMenu.value.right = 'auto'
  294. unzipMenu.value.left = '126px'
  295. unzipMenu.value.right = 'auto'
  296. }
  297. visible.value = true
  298. }
  299. /**
  300. * 关闭右键列表
  301. */
  302. const closeRightMenu = (event) => {
  303. // 使用 classList.contains 方法替代 className.includes
  304. const target = event.target
  305. const hasOperateMoreClass =
  306. target.classList.contains('operate-more-') ||
  307. (typeof target.className === 'string' && target.className.includes('operate-more-'))
  308. const hasUnzipMenuItemClass =
  309. target.classList.contains('unzip-menu-item') ||
  310. (typeof target.className === 'string' && target.className.includes('unzip-menu-item'))
  311. if (!hasOperateMoreClass && !hasUnzipMenuItemClass) {
  312. visible.value = false
  313. if (props.selectedFile !== undefined) {
  314. // 不是在空白处右键时
  315. props.callback('cancel')
  316. }
  317. }
  318. }
  319. /**
  320. * 复制按钮点击事件
  321. * @description 向父组件传递当前操作的文件信息,并打开"复制文件对话框"
  322. * @param {object} fileInfo 文件信息
  323. */
  324. const handleCopyFileBtnClick = (fileInfo) => {
  325. visible.value = false
  326. proxy.$openDialog
  327. .copyFile({
  328. fileInfo
  329. })
  330. .then((res) => {
  331. props.callback(res)
  332. })
  333. }
  334. /**
  335. * 移动按钮点击事件
  336. * @description 向父组件传递当前操作的文件信息,并打开"移动文件对话框"
  337. * @param {object} fileInfo 文件信息
  338. */
  339. const handleMoveFileBtnClick = (fileInfo) => {
  340. visible.value = false
  341. proxy.$openDialog
  342. .moveFile({
  343. isBatchOperation: false,
  344. fileInfo
  345. })
  346. .then((res) => {
  347. props.callback(res)
  348. })
  349. }
  350. /**
  351. * 解压缩按钮点击事件
  352. * @description 调用解压缩文件接口,并展示新的文件列表
  353. * @param {object} fileInfo 文件信息
  354. * @param {number} unzipMode 解压模式 0-解压到当前文件夹, 1-自动创建该文件名目录,并解压到目录里, 2-手动选择解压目录
  355. */
  356. const handleUnzipFileBtnClick = (fileInfo, unzipMode) => {
  357. visible.value = false
  358. proxy.$openDialog
  359. .unzipFile({
  360. unzipMode: unzipMode,
  361. userFileId: fileInfo.userFileId
  362. })
  363. .then((res) => {
  364. props.callback(res)
  365. })
  366. }
  367. /**
  368. * 删除按钮点击事件
  369. * @description 区分 删除到回收站中 | 在回收站中彻底删除,打开确认对话框
  370. * @param {object} fileInfo 文件信息
  371. */
  372. const handleDeleteFileBtnClick = (fileInfo) => {
  373. visible.value = false
  374. proxy.$openDialog
  375. .deleteFile({
  376. isBatchOperation: false,
  377. fileInfo,
  378. deleteMode: fileType.value === 6 ? 2 : 1 // 删除类型:1-删除到回收站 2-彻底删除
  379. })
  380. .then((res) => {
  381. props.callback(res)
  382. })
  383. }
  384. /**
  385. * 还原按钮点击事件
  386. * @description 调用接口,在回收站中还原文件
  387. * @param {object} fileInfo 文件信息
  388. */
  389. const handleRestoreFileBtnClick = (fileInfo) => {
  390. visible.value = false
  391. proxy.$openDialog
  392. .restoreFile({
  393. deleteBatchNum: fileInfo.deleteBatchNum,
  394. filePath: fileInfo.filePath
  395. })
  396. .then((res) => {
  397. props.callback(res)
  398. })
  399. }
  400. /**
  401. * 文件重命名按钮点击事件
  402. * @description 打开确认对话框让用户输入新的文件名
  403. * @param {object} fileInfo 文件信息
  404. */
  405. const handleRenameFileBtnClick = (fileInfo) => {
  406. visible.value = false
  407. proxy.$openDialog
  408. .renameFile({
  409. oldFileName: fileInfo.fileName,
  410. userFileId: fileInfo.userFileId
  411. })
  412. .then((res) => {
  413. props.callback(res)
  414. })
  415. }
  416. /**
  417. * 文件分享按钮点击事件
  418. * @description 打开对话框让用户选择过期时间和提取码
  419. * @param {object} fileInfo 文件信息
  420. */
  421. const handleShareFileBtnClick = (fileInfo) => {
  422. visible.value = false
  423. proxy.$openDialog.shareFile({
  424. fileInfo: [
  425. {
  426. userFileId: fileInfo.userFileId
  427. }
  428. ]
  429. })
  430. }
  431. /**
  432. * 编辑文件夹按钮点击事件
  433. */
  434. const handleClickFolderEdit = () => {
  435. router.push({
  436. name: 'WebIDE',
  437. query: { filePath: props.selectedFile.filePath }
  438. })
  439. }
  440. /**
  441. * 文件在线编辑按钮点击事件
  442. * @description 打开 代码预览对话框 或 office 编辑页面
  443. * @param {object} fileInfo 文件信息
  444. */
  445. const handleClickFileEdit = (fileInfo) => {
  446. if (officeFileType.includes(fileInfo.extendName)) {
  447. // office 编辑页面
  448. // proxy.$file.getFileOnlineEditPathByOffice(fileInfo)
  449. } else if (markdownFileType.includes(fileInfo.extendName)) {
  450. // markdown 编辑浮层
  451. proxy.$openBox.markdownPreview({
  452. fileInfo: fileInfo,
  453. editable: true
  454. })
  455. } else {
  456. // 代码编辑对话框
  457. proxy.$openBox.codePreview({ fileInfo: fileInfo, isEdit: true })
  458. }
  459. }
  460. /**
  461. * 文件详情按钮点击事件
  462. * @description 打开对话框展示文件完整信息
  463. * @param {object} fileInfo 文件信息
  464. */
  465. const handleShowDetailInfo = (fileInfo) => {
  466. visible.value = false
  467. proxy.$openDialog.showFileDetail({ fileInfo })
  468. }
  469. /**
  470. * 新建文件夹按钮点击事件
  471. * @description 调用新建文件夹服务,并在弹窗确认回调事件中刷新文件列表
  472. */
  473. const handleClickAddFolderBtn = () => {
  474. proxy.$openDialog
  475. .addFolder({
  476. // filePath: router.currentRoute.value.query.filePath || '/'
  477. filePath: myResourceStore.getQuery.filePath || '/'
  478. })
  479. .then((res) => {
  480. props.callback(res)
  481. })
  482. }
  483. /**
  484. * 新建 office 文件
  485. * @description 调用新建 office 文件服务,并在弹窗确认回调事件中刷新文件列表
  486. * @param {string} 文件扩展名 docx xlsx pptx
  487. */
  488. const handleCreateFile = (extendName) => {
  489. proxy.$openDialog
  490. .addFile({
  491. extendName: extendName
  492. })
  493. .then((res) => {
  494. props.callback(res)
  495. })
  496. }
  497. /**
  498. * 上传文件按钮点击事件
  499. * @description 通过Bus通信,开启全局上传文件流程
  500. * @param {boolean} uploadWay 上传方式 0-文件上传 1-文件夹上传 2-粘贴图片或拖拽上传
  501. */
  502. const handleUploadFileBtnClick = (uploadWay) => {
  503. proxy.$openBox.uploadFile({
  504. params: uploadFileParams.value,
  505. uploadWay,
  506. serviceEl: props.serviceEl,
  507. callType: true // callType 调用此服务的方式:1 - 顶部栏,2 - 右键菜单
  508. })
  509. }
  510. // 暴露方法给父组件
  511. defineExpose({
  512. visible,
  513. handleOpenContextMenu
  514. })
  515. </script>
  516. <style lang="less" scoped>
  517. @import '@/style/myResource/varibles.less';
  518. @import '@/style/myResource/mixins.less';
  519. .right-menu-list {
  520. position: fixed;
  521. display: flex;
  522. flex-direction: column;
  523. background: #fff;
  524. border: 1px solid @border-color-light;
  525. border-radius: 4px;
  526. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  527. z-index: 2;
  528. padding: 4px 0;
  529. color: @regular-text;
  530. list-style: none;
  531. .right-menu-item,
  532. .unzip-item {
  533. padding: 0 16px;
  534. height: 36px;
  535. line-height: 36px;
  536. cursor: pointer;
  537. &:hover {
  538. background: @primary-hover;
  539. color: @Primary;
  540. }
  541. i {
  542. margin-right: 8px;
  543. }
  544. }
  545. &.add {
  546. .right-menu-item {
  547. display: flex;
  548. align-items: center;
  549. img {
  550. margin-right: 4px;
  551. height: 20px;
  552. }
  553. i {
  554. margin-right: 4px;
  555. font-size: 18px;
  556. }
  557. }
  558. }
  559. .unzip-menu-item {
  560. position: relative;
  561. &:hover {
  562. .unzip-list {
  563. display: block;
  564. }
  565. }
  566. .unzip-list {
  567. position: absolute;
  568. display: none;
  569. list-style: none;
  570. margin-left: -15px;
  571. .unzip-item {
  572. width: 200px;
  573. .setEllipsis(1);
  574. }
  575. }
  576. }
  577. }
  578. .right-menu-list,
  579. .unzip-list {
  580. background: #fff;
  581. border: 1px solid @border-color-light;
  582. border-radius: 6px;
  583. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  584. z-index: 2;
  585. padding: 8px 0;
  586. color: @regular-text;
  587. font-size: 14px;
  588. .a-divider {
  589. margin: 2px 0;
  590. }
  591. }
  592. </style>