Box.vue 17 KB

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