Box.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  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. })
  179. // 当前路径
  180. const filePath = computed(() => {
  181. return router.currentRoute.value.query.filePath
  182. })
  183. // 查看按钮是否显示
  184. const seeBtnShow = computed(() => {
  185. return fileType.value !== 6
  186. })
  187. // 删除按钮是否显示
  188. const deleteBtnShow = computed(() => {
  189. return fileType.value !== 8 && !['Share'].includes(routeName.value)
  190. })
  191. // 还原按钮是否显示
  192. const restoreBtnShow = computed(() => {
  193. return fileType.value === 6 && !['Share'].includes(routeName.value)
  194. })
  195. // 复制按钮是否显示
  196. const copyBtnShow = computed(() => {
  197. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  198. })
  199. // 移动按钮是否显示
  200. const moveBtnShow = computed(() => {
  201. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  202. })
  203. // 重命名按钮是否显示
  204. const renameBtnShow = computed(() => {
  205. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  206. })
  207. // 分享按钮是否显示
  208. const shareBtnShow = computed(() => {
  209. return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
  210. })
  211. // 下载按钮是否显示
  212. const downloadBtnShow = computed(() => {
  213. return ![6, 8].includes(fileType.value)
  214. })
  215. // 解压缩按钮是否显示
  216. const unzipBtnShow = computed(() => {
  217. return (
  218. ![6, 8].includes(fileType.value) &&
  219. !['Share'].includes(routeName.value) &&
  220. ['zip', 'rar', '7z', 'tar', 'gz'].includes(props.selectedFile.extendName)
  221. )
  222. })
  223. // 编辑文件夹按钮是否显示
  224. const folderEditBtnShow = computed(() => {
  225. return ![6, 8].includes(fileType.value) && props.selectedFile.isDir === 1 && !['Share'].includes(routeName.value)
  226. })
  227. // 在线编辑按钮是否显示
  228. const onlineEditBtnShow = computed(() => {
  229. return (
  230. ![6, 8].includes(fileType.value) &&
  231. (officeFileType.includes(props.selectedFile.extendName) ||
  232. markdownFileType.includes(props.selectedFile.extendName) ||
  233. fileSuffixCodeModeMap.has(props.selectedFile.extendName)) &&
  234. !['Share'].includes(routeName.value)
  235. )
  236. })
  237. // 复制链接按钮是否显示
  238. const copyLinkBtnShow = computed(() => {
  239. return fileType.value === 8
  240. })
  241. // 文件详情按钮是否显示
  242. const detailInfoBtnShow = computed(() => {
  243. return true
  244. })
  245. // 上传文件组件参数
  246. const uploadFileParams = computed(() => {
  247. return {
  248. filePath: filePath.value,
  249. isDir: 0
  250. }
  251. })
  252. // 监听右键列表状态
  253. watch(visible, (newValue) => {
  254. if (newValue === true) {
  255. document.body.addEventListener('click', closeRightMenu)
  256. handleOpenContextMenu()
  257. } else {
  258. document.body.removeEventListener('click', closeRightMenu)
  259. }
  260. })
  261. // 方法
  262. /**
  263. * 打开右键菜单
  264. */
  265. const handleOpenContextMenu = () => {
  266. // 纵坐标设置
  267. if (
  268. document.body.clientHeight - props.domEvent.clientY <
  269. document.querySelectorAll('#rightMenuList > .right-menu-item').length * 36 + 10
  270. ) {
  271. // 如果到底部的距离小于元素总高度
  272. rightMenu.value.top = 'auto'
  273. rightMenu.value.bottom = `${document.body.clientHeight - props.domEvent.clientY}px`
  274. unzipMenu.value.top = 'auto'
  275. unzipMenu.value.bottom = '0px'
  276. } else {
  277. rightMenu.value.top = `${props.domEvent.clientY}px`
  278. rightMenu.value.bottom = 'auto'
  279. unzipMenu.value.top = '0px'
  280. unzipMenu.value.bottom = 'auto'
  281. }
  282. // 横坐标设置
  283. if (document.body.clientWidth - props.domEvent.clientX < 138) {
  284. // 如果到右边的距离小于元素总宽度
  285. rightMenu.value.left = 'auto'
  286. rightMenu.value.right = `${document.body.clientWidth - props.domEvent.clientX}px`
  287. unzipMenu.value.left = '-200px'
  288. unzipMenu.value.right = 'auto'
  289. } else {
  290. rightMenu.value.left = `${props.domEvent.clientX + 8}px`
  291. rightMenu.value.right = 'auto'
  292. unzipMenu.value.left = '126px'
  293. unzipMenu.value.right = 'auto'
  294. }
  295. visible.value = true
  296. }
  297. /**
  298. * 关闭右键列表
  299. */
  300. const closeRightMenu = (event) => {
  301. // 使用 classList.contains 方法替代 className.includes
  302. const target = event.target
  303. const hasOperateMoreClass =
  304. target.classList.contains('operate-more-') ||
  305. (typeof target.className === 'string' && target.className.includes('operate-more-'))
  306. const hasUnzipMenuItemClass =
  307. target.classList.contains('unzip-menu-item') ||
  308. (typeof target.className === 'string' && target.className.includes('unzip-menu-item'))
  309. if (!hasOperateMoreClass && !hasUnzipMenuItemClass) {
  310. visible.value = false
  311. if (props.selectedFile !== undefined) {
  312. // 不是在空白处右键时
  313. props.callback('cancel')
  314. }
  315. }
  316. }
  317. /**
  318. * 复制按钮点击事件
  319. * @description 向父组件传递当前操作的文件信息,并打开"复制文件对话框"
  320. * @param {object} fileInfo 文件信息
  321. */
  322. const handleCopyFileBtnClick = (fileInfo) => {
  323. visible.value = false
  324. proxy.$openDialog
  325. .copyFile({
  326. fileInfo
  327. })
  328. .then((res) => {
  329. props.callback(res)
  330. })
  331. }
  332. /**
  333. * 移动按钮点击事件
  334. * @description 向父组件传递当前操作的文件信息,并打开"移动文件对话框"
  335. * @param {object} fileInfo 文件信息
  336. */
  337. const handleMoveFileBtnClick = (fileInfo) => {
  338. visible.value = false
  339. proxy.$openDialog
  340. .moveFile({
  341. isBatchOperation: false,
  342. fileInfo
  343. })
  344. .then((res) => {
  345. props.callback(res)
  346. })
  347. }
  348. /**
  349. * 解压缩按钮点击事件
  350. * @description 调用解压缩文件接口,并展示新的文件列表
  351. * @param {object} fileInfo 文件信息
  352. * @param {number} unzipMode 解压模式 0-解压到当前文件夹, 1-自动创建该文件名目录,并解压到目录里, 2-手动选择解压目录
  353. */
  354. const handleUnzipFileBtnClick = (fileInfo, unzipMode) => {
  355. visible.value = false
  356. proxy.$openDialog
  357. .unzipFile({
  358. unzipMode: unzipMode,
  359. userFileId: fileInfo.userFileId
  360. })
  361. .then((res) => {
  362. props.callback(res)
  363. })
  364. }
  365. /**
  366. * 删除按钮点击事件
  367. * @description 区分 删除到回收站中 | 在回收站中彻底删除,打开确认对话框
  368. * @param {object} fileInfo 文件信息
  369. */
  370. const handleDeleteFileBtnClick = (fileInfo) => {
  371. visible.value = false
  372. proxy.$openDialog
  373. .deleteFile({
  374. isBatchOperation: false,
  375. fileInfo,
  376. deleteMode: fileType.value === 6 ? 2 : 1 // 删除类型:1-删除到回收站 2-彻底删除
  377. })
  378. .then((res) => {
  379. props.callback(res)
  380. })
  381. }
  382. /**
  383. * 还原按钮点击事件
  384. * @description 调用接口,在回收站中还原文件
  385. * @param {object} fileInfo 文件信息
  386. */
  387. const handleRestoreFileBtnClick = (fileInfo) => {
  388. visible.value = false
  389. proxy.$openDialog
  390. .restoreFile({
  391. deleteBatchNum: fileInfo.deleteBatchNum,
  392. filePath: fileInfo.filePath
  393. })
  394. .then((res) => {
  395. props.callback(res)
  396. })
  397. }
  398. /**
  399. * 文件重命名按钮点击事件
  400. * @description 打开确认对话框让用户输入新的文件名
  401. * @param {object} fileInfo 文件信息
  402. */
  403. const handleRenameFileBtnClick = (fileInfo) => {
  404. visible.value = false
  405. proxy.$openDialog
  406. .renameFile({
  407. oldFileName: fileInfo.fileName,
  408. userFileId: fileInfo.userFileId
  409. })
  410. .then((res) => {
  411. props.callback(res)
  412. })
  413. }
  414. /**
  415. * 文件分享按钮点击事件
  416. * @description 打开对话框让用户选择过期时间和提取码
  417. * @param {object} fileInfo 文件信息
  418. */
  419. const handleShareFileBtnClick = (fileInfo) => {
  420. visible.value = false
  421. proxy.$openDialog.shareFile({
  422. fileInfo: [
  423. {
  424. userFileId: fileInfo.userFileId
  425. }
  426. ]
  427. })
  428. }
  429. /**
  430. * 编辑文件夹按钮点击事件
  431. */
  432. const handleClickFolderEdit = () => {
  433. router.push({
  434. name: 'WebIDE',
  435. query: { filePath: props.selectedFile.filePath }
  436. })
  437. }
  438. /**
  439. * 文件在线编辑按钮点击事件
  440. * @description 打开 代码预览对话框 或 office 编辑页面
  441. * @param {object} fileInfo 文件信息
  442. */
  443. const handleClickFileEdit = (fileInfo) => {
  444. if (officeFileType.includes(fileInfo.extendName)) {
  445. // office 编辑页面
  446. // proxy.$file.getFileOnlineEditPathByOffice(fileInfo)
  447. } else if (markdownFileType.includes(fileInfo.extendName)) {
  448. // markdown 编辑浮层
  449. proxy.$openBox.markdownPreview({
  450. fileInfo: fileInfo,
  451. editable: true
  452. })
  453. } else {
  454. // 代码编辑对话框
  455. proxy.$openBox.codePreview({ fileInfo: fileInfo, isEdit: true })
  456. }
  457. }
  458. /**
  459. * 文件详情按钮点击事件
  460. * @description 打开对话框展示文件完整信息
  461. * @param {object} fileInfo 文件信息
  462. */
  463. const handleShowDetailInfo = (fileInfo) => {
  464. visible.value = false
  465. proxy.$openDialog.showFileDetail({ fileInfo })
  466. }
  467. /**
  468. * 新建文件夹按钮点击事件
  469. * @description 调用新建文件夹服务,并在弹窗确认回调事件中刷新文件列表
  470. */
  471. const handleClickAddFolderBtn = () => {
  472. proxy.$openDialog
  473. .addFolder({
  474. filePath: router.currentRoute.value.query.filePath || '/'
  475. })
  476. .then((res) => {
  477. props.callback(res)
  478. })
  479. }
  480. /**
  481. * 新建 office 文件
  482. * @description 调用新建 office 文件服务,并在弹窗确认回调事件中刷新文件列表
  483. * @param {string} 文件扩展名 docx xlsx pptx
  484. */
  485. const handleCreateFile = (extendName) => {
  486. proxy.$openDialog
  487. .addFile({
  488. extendName: extendName
  489. })
  490. .then((res) => {
  491. props.callback(res)
  492. })
  493. }
  494. /**
  495. * 上传文件按钮点击事件
  496. * @description 通过Bus通信,开启全局上传文件流程
  497. * @param {boolean} uploadWay 上传方式 0-文件上传 1-文件夹上传 2-粘贴图片或拖拽上传
  498. */
  499. const handleUploadFileBtnClick = (uploadWay) => {
  500. proxy.$openBox.uploadFile({
  501. params: uploadFileParams.value,
  502. uploadWay,
  503. serviceEl: props.serviceEl,
  504. callType: true // callType 调用此服务的方式:1 - 顶部栏,2 - 右键菜单
  505. })
  506. }
  507. // 暴露方法给父组件
  508. defineExpose({
  509. visible,
  510. handleOpenContextMenu
  511. })
  512. </script>
  513. <style lang="less" scoped>
  514. @import '@/style/myResource/varibles.less';
  515. @import '@/style/myResource/mixins.less';
  516. .right-menu-list {
  517. position: fixed;
  518. display: flex;
  519. flex-direction: column;
  520. background: #fff;
  521. border: 1px solid @border-color-light;
  522. border-radius: 4px;
  523. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  524. z-index: 2;
  525. padding: 4px 0;
  526. color: @regular-text;
  527. list-style: none;
  528. .right-menu-item,
  529. .unzip-item {
  530. padding: 0 16px;
  531. height: 36px;
  532. line-height: 36px;
  533. cursor: pointer;
  534. &:hover {
  535. background: @primary-hover;
  536. color: @Primary;
  537. }
  538. i {
  539. margin-right: 8px;
  540. }
  541. }
  542. &.add {
  543. .right-menu-item {
  544. display: flex;
  545. align-items: center;
  546. img {
  547. margin-right: 4px;
  548. height: 20px;
  549. }
  550. i {
  551. margin-right: 4px;
  552. font-size: 18px;
  553. }
  554. }
  555. }
  556. .unzip-menu-item {
  557. position: relative;
  558. &:hover {
  559. .unzip-list {
  560. display: block;
  561. }
  562. }
  563. .unzip-list {
  564. position: absolute;
  565. display: none;
  566. list-style: none;
  567. .unzip-item {
  568. width: 200px;
  569. .setEllipsis(1);
  570. }
  571. }
  572. }
  573. }
  574. .right-menu-list,
  575. .unzip-list {
  576. background: #fff;
  577. border: 1px solid @border-color-light;
  578. border-radius: 6px;
  579. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  580. z-index: 2;
  581. padding: 8px 0;
  582. color: @regular-text;
  583. font-size: 14px;
  584. .a-divider {
  585. margin: 2px 0;
  586. }
  587. }
  588. </style>