tanshanming hai 8 meses
pai
achega
0cf3b60dd0
Modificáronse 100 ficheiros con 7304 adicións e 3 borrados
  1. 6 0
      package.json
  2. 104 0
      src/api/myResource/file.js
  3. 11 0
      src/api/myResource/onlyoffice.js
  4. 14 0
      src/api/myResource/user.js
  5. 92 0
      src/assets/codemirror/css/tomorrow-night.css
  6. BIN=BIN
      src/assets/images/myResource/file/dir.png
  7. 2 0
      src/assets/images/myResource/file/file_7z.svg
  8. BIN=BIN
      src/assets/images/myResource/file/file_avi.png
  9. BIN=BIN
      src/assets/images/myResource/file/file_c#.png
  10. BIN=BIN
      src/assets/images/myResource/file/file_c++.png
  11. BIN=BIN
      src/assets/images/myResource/file/file_c.png
  12. BIN=BIN
      src/assets/images/myResource/file/file_chm.png
  13. BIN=BIN
      src/assets/images/myResource/file/file_css.png
  14. BIN=BIN
      src/assets/images/myResource/file/file_csv.png
  15. BIN=BIN
      src/assets/images/myResource/file/file_dmg.png
  16. 2 0
      src/assets/images/myResource/file/file_excel.svg
  17. BIN=BIN
      src/assets/images/myResource/file/file_exe.png
  18. 2 0
      src/assets/images/myResource/file/file_flac.svg
  19. BIN=BIN
      src/assets/images/myResource/file/file_gif.png
  20. BIN=BIN
      src/assets/images/myResource/file/file_go.png
  21. BIN=BIN
      src/assets/images/myResource/file/file_html.png
  22. BIN=BIN
      src/assets/images/myResource/file/file_jar.png
  23. BIN=BIN
      src/assets/images/myResource/file/file_java.png
  24. BIN=BIN
      src/assets/images/myResource/file/file_js.png
  25. BIN=BIN
      src/assets/images/myResource/file/file_json.png
  26. BIN=BIN
      src/assets/images/myResource/file/file_jsp.png
  27. BIN=BIN
      src/assets/images/myResource/file/file_kotlin.png
  28. BIN=BIN
      src/assets/images/myResource/file/file_less.png
  29. BIN=BIN
      src/assets/images/myResource/file/file_lua.png
  30. BIN=BIN
      src/assets/images/myResource/file/file_markdown.png
  31. BIN=BIN
      src/assets/images/myResource/file/file_music.png
  32. BIN=BIN
      src/assets/images/myResource/file/file_nginx.png
  33. BIN=BIN
      src/assets/images/myResource/file/file_oa.png
  34. BIN=BIN
      src/assets/images/myResource/file/file_objective_c.png
  35. BIN=BIN
      src/assets/images/myResource/file/file_open.png
  36. BIN=BIN
      src/assets/images/myResource/file/file_pdf.png
  37. BIN=BIN
      src/assets/images/myResource/file/file_php.png
  38. BIN=BIN
      src/assets/images/myResource/file/file_powershell.png
  39. 2 0
      src/assets/images/myResource/file/file_ppt.svg
  40. BIN=BIN
      src/assets/images/myResource/file/file_properties.png
  41. BIN=BIN
      src/assets/images/myResource/file/file_python.png
  42. BIN=BIN
      src/assets/images/myResource/file/file_r.png
  43. BIN=BIN
      src/assets/images/myResource/file/file_rar.png
  44. BIN=BIN
      src/assets/images/myResource/file/file_rtf.png
  45. BIN=BIN
      src/assets/images/myResource/file/file_rust.png
  46. BIN=BIN
      src/assets/images/myResource/file/file_sass.png
  47. BIN=BIN
      src/assets/images/myResource/file/file_scss.png
  48. BIN=BIN
      src/assets/images/myResource/file/file_shell.png
  49. BIN=BIN
      src/assets/images/myResource/file/file_sql.png
  50. BIN=BIN
      src/assets/images/myResource/file/file_stylus.png
  51. BIN=BIN
      src/assets/images/myResource/file/file_svg.png
  52. BIN=BIN
      src/assets/images/myResource/file/file_swift.png
  53. 2 0
      src/assets/images/myResource/file/file_tar.svg
  54. BIN=BIN
      src/assets/images/myResource/file/file_txt.png
  55. BIN=BIN
      src/assets/images/myResource/file/file_typescript.png
  56. BIN=BIN
      src/assets/images/myResource/file/file_unknown.png
  57. BIN=BIN
      src/assets/images/myResource/file/file_vue.png
  58. 2 0
      src/assets/images/myResource/file/file_word.svg
  59. BIN=BIN
      src/assets/images/myResource/file/file_xml.png
  60. BIN=BIN
      src/assets/images/myResource/file/file_yaml.png
  61. BIN=BIN
      src/assets/images/myResource/file/file_zip.png
  62. 2 0
      src/assets/mavonEditor/css/github-markdown.css
  63. 0 0
      src/assets/mavonEditor/css/katex.min.css
  64. 75 0
      src/assets/mavonEditor/css/tomorrow-night.css
  65. 531 0
      src/assets/mavonEditor/js/highlight.min.js
  66. 0 0
      src/assets/mavonEditor/js/katex.min.js
  67. 322 0
      src/assets/mavonEditor/js/lang.hljs.js
  68. 4 0
      src/config/reSource.js
  69. 48 0
      src/libs/fileOperationPlugins.js
  70. 400 0
      src/libs/globalFunction/file.js
  71. 3 0
      src/libs/globalFunction/index.js
  72. 203 0
      src/libs/map.js
  73. 3 1
      src/main.js
  74. 9 2
      src/snowy.js
  75. 1 0
      src/store/index.js
  76. 140 0
      src/store/myResource.js
  77. 45 0
      src/style/myResource/mixins.less
  78. 47 0
      src/style/myResource/varibles.less
  79. 185 0
      src/utils/reSourceRequest.js
  80. 188 0
      src/views/myResource/common/BreadCrumb.vue
  81. 340 0
      src/views/myResource/common/DragVerify.vue
  82. 497 0
      src/views/myResource/common/FileTable.vue
  83. 18 0
      src/views/myResource/common/MarkdownPreview.vue
  84. 270 0
      src/views/myResource/components/EnterpriseDisk.vue
  85. 299 0
      src/views/myResource/components/FileGrid.vue
  86. 173 0
      src/views/myResource/components/FileTimeLine.vue
  87. 585 0
      src/views/myResource/components/OperationMenu.vue
  88. 121 0
      src/views/myResource/components/SelectColumn.vue
  89. 155 0
      src/views/myResource/components/SensitiveList.vue
  90. 142 0
      src/views/myResource/components/SensitiveListManage.vue
  91. 58 0
      src/views/myResource/components/TransferList.vue
  92. 785 0
      src/views/myResource/file/box/audioPreview/BoxMask.vue
  93. 64 0
      src/views/myResource/file/box/audioPreview/index.js
  94. 443 0
      src/views/myResource/file/box/codePreview/BoxMask.vue
  95. 7 0
      src/views/myResource/file/box/codePreview/fold.js
  96. 64 0
      src/views/myResource/file/box/codePreview/index.js
  97. 24 0
      src/views/myResource/file/box/codePreview/mode.js
  98. 69 0
      src/views/myResource/file/box/codePreview/theme.js
  99. 667 0
      src/views/myResource/file/box/contextMenu/Box.vue
  100. 78 0
      src/views/myResource/file/box/contextMenu/index.js

+ 6 - 0
package.json

@@ -29,6 +29,7 @@
 		"@vue-office/pdf": "1.2.0",
 		"@vue-office/pdf": "1.2.0",
 		"ant-design-vue": "3.2.14",
 		"ant-design-vue": "3.2.14",
 		"axios": "1.1.3",
 		"axios": "1.1.3",
+		"codemirror-editor-vue3": "^2.8.0",
 		"cropperjs": "1.5.12",
 		"cropperjs": "1.5.12",
 		"dayjs": "1.11.7",
 		"dayjs": "1.11.7",
 		"echarts": "5.4.0",
 		"echarts": "5.4.0",
@@ -38,9 +39,11 @@
 		"fuse.js": "6.6.2",
 		"fuse.js": "6.6.2",
 		"highlight.js": "11.6.0",
 		"highlight.js": "11.6.0",
 		"hotkeys-js": "3.10.1",
 		"hotkeys-js": "3.10.1",
+		"js-base64": "^3.7.7",
 		"js-pinyin": "0.1.9",
 		"js-pinyin": "0.1.9",
 		"jsbarcode": "^3.11.5",
 		"jsbarcode": "^3.11.5",
 		"lodash-es": "4.17.21",
 		"lodash-es": "4.17.21",
+		"mavon-editor": "^2.10.4",
 		"nprogress": "0.2.0",
 		"nprogress": "0.2.0",
 		"pinia": "2.0.33",
 		"pinia": "2.0.33",
 		"qrcode": "1.5.1",
 		"qrcode": "1.5.1",
@@ -50,12 +53,15 @@
 		"snowflake-id": "1.1.0",
 		"snowflake-id": "1.1.0",
 		"snowy-form-design": "1.1.9-Bata-02",
 		"snowy-form-design": "1.1.9-Bata-02",
 		"sortablejs": "1.15.0",
 		"sortablejs": "1.15.0",
+		"spark-md5": "^3.0.2",
 		"tinymce": "^6.2.0",
 		"tinymce": "^6.2.0",
+		"video.js": "^8.23.3",
 		"vue": "3.2.44",
 		"vue": "3.2.44",
 		"vue-cropper": "1.0.5",
 		"vue-cropper": "1.0.5",
 		"vue-demi": "0.13.11",
 		"vue-demi": "0.13.11",
 		"vue-i18n": "9.2.2",
 		"vue-i18n": "9.2.2",
 		"vue-router": "4.1.6",
 		"vue-router": "4.1.6",
+		"vue-simple-uploader": "^1.0.3",
 		"vue3-colorpicker": "2.0.4",
 		"vue3-colorpicker": "2.0.4",
 		"vue3-print-nb": "0.1.4",
 		"vue3-print-nb": "0.1.4",
 		"vue3-tree-org": "4.2.2",
 		"vue3-tree-org": "4.2.2",

+ 104 - 0
src/api/myResource/file.js

@@ -0,0 +1,104 @@
+// 文件模块相关接口
+import { moduleRequest } from '@/utils/reSourceRequest'
+
+const request = moduleRequest(`/api/webapp/`)
+
+/**
+ * 获取文件列表相关接口
+ */
+// 获取文件列表(区分文件路径)
+export const getFileListByPath = (p) => request('file/getfilelist', p, 'get')
+// 获取文件列表(区分文件类型)
+export const getFileListByType = (p) => request('file/selectfilebyfiletype', p, 'get')
+// 获取回收站文件列表
+export const getRecoveryFile = (p) => request('recoveryfile/list', p, 'get')
+// 获取我已分享的文件列表
+export const getMyShareFileList = (p) => request('share/shareList', p, 'get')
+// 获取存储占用
+export const getStorage = (p) => request('filetransfer/getstorage', p, 'get')
+// 获取文件目录树
+export const getFoldTree = (p) => request('file/getfiletree', p, 'get')
+// 获取摆渡功能状态
+export const getFerryStatus = () => request('file/getFerryStatus', {}, 'get')
+/**
+ * 单文件操作相关接口
+ */
+// 创建文件
+export const createFold = (p) => request('file/createFold', p, 'post')
+// 获取文件详细信息
+export const getFileDetail = (p) => request('file/detail', p, 'get')
+// 删除文件
+export const deleteFile = (p) => request('file/deletefile', p, 'post')
+// 复制文件
+export const copyFile = (p) => request('file/copyfile', p, 'post')
+// 移动文件
+export const moveFile = (p) => request('file/movefile', p, 'post')
+// 重命名文件
+export const renameFile = (p) => request('file/renamefile', p, 'post')
+// 解压文件
+export const unzipFile = (p) => request('file/unzipfile', p, 'post')
+// 全局搜索文件
+export const searchFile = (p) => request('file/search', p, 'get')
+// 全局搜索文件2
+export const searchByFileName = (p) => request('file/searchByFileName', p, 'get')
+// 分享文件
+export const shareFile = (p) => request('share/sharefile', p, 'post')
+// 校验分享链接过期时间
+export const checkShareLinkEndtime = (p) => request('share/checkendtime', p, 'get')
+// 校验分享链接是否需要提取码
+export const checkShareLinkType = (p) => request('share/sharetype', p, 'get')
+// 校验分享链接提取码是否正确
+export const checkShareLinkCode = (p) => request('share/checkextractioncode', p, 'get')
+// 获取分享文件列表
+export const getShareFileList = (p) => request('share/sharefileList', p, 'get')
+// 保存分享文件
+export const saveShareFile = (p) => request('share/savesharefile', p, 'post')
+
+/**
+ * 文件批量操作相关接口
+ */
+// 批量删除文件
+export const batchDeleteFile = (p) => request('file/batchdeletefile', p, 'post')
+// 批量移动文件
+export const batchMoveFile = (p) => request('file/batchmovefile', p, 'post')
+
+/**
+ * 回收站文件操作相关接口
+ */
+// 回收站文件删除
+export const deleteRecoveryFile = (p) => request('recoveryfile/deleterecoveryfile', p, 'post')
+// 回收站文件还原
+export const restoreRecoveryFile = (p) => request('recoveryfile/restorefile', p, 'post')
+// 回收站文件批量删除
+export const batchDeleteRecoveryFile = (p) => request('recoveryfile/batchdelete', p, 'post')
+
+/**
+ * 文件公共接口
+ */
+// 文件预览
+export const getFilePreview = (p) => request('filetransfer/preview', p, 'get')
+// 文件修改
+export const modifyFileContent = (p) => request('file/update', p, 'post')
+
+/**
+ * 查询敏感词列表
+ */
+export const getSensitiveList = (p) => request('sensitive/list', p, 'get')
+export const addSensitiveList = (p) => request('sensitive/add', p, 'post')
+export const delSensitiveList = (p) => request(`sensitive/delete/${p}`, {}, 'get')
+export const updateSensitiveList = (p) => request('sensitive/update', p, 'post')
+/**
+ * 查询敏感词管理列表
+ */
+export const getSensitiveRecord = (p) => request('sensitiveRecord/list', p, 'get')
+export const delSensitiveRecord = (p) => request(`sensitiveRecord/delete/${p}`, {}, 'get')
+// 跨网传输记录
+export const getTransferRecord = (p) => request('ferryRecord/list', p, 'get')
+export const ferryRecordDelete = (p) => request(`ferryRecord/delete/${p}`, {}, 'get')
+
+// 获取企业云盘文件列表(区分文件路径)
+export const getEnterpriseDiskList = (p) => request('file/getEnterpriseDisk', p, 'get')
+// 添加至我的云盘
+export const addMyFileListApi = (p) => request('file/addFileToMyList', p, 'get')
+// 添加至企业云盘
+export const addEnterpriseDiskApi = (p) => request('file/addEnterpriseDisk', p, 'get')

+ 11 - 0
src/api/myResource/onlyoffice.js

@@ -0,0 +1,11 @@
+// 文件模块相关接口
+import { moduleRequest } from '@/utils/reSourceRequest'
+
+const request = moduleRequest(`/api/webapp/`)
+
+// 创建文档
+export const createOfficeFile = (p) => request('/file/createFile', p, 'post')
+// 编辑文档
+export const editOfficeFile = (p) => request('/office/editofficefile', p, 'post')
+// 查看文档
+export const previewOfficeFile = (p) => request('/office/previewofficefile', p, 'post')

+ 14 - 0
src/api/myResource/user.js

@@ -0,0 +1,14 @@
+// 文件模块相关接口
+import { moduleRequest } from '@/utils/reSourceRequest'
+
+const request = moduleRequest(`/api/webapp/`)
+
+// 获取文件列表(区分文件类型)
+export const checkUserLoginInfo = (p) => {
+	return request('user/checkuserlogininfo', p, 'get')
+}
+
+// 获取微信订阅号认证状态
+export const getWeChatAuthState = (p) => {
+	return request('user/checkWxAuth', p, 'get')
+}

+ 92 - 0
src/assets/codemirror/css/tomorrow-night.css

@@ -0,0 +1,92 @@
+/*
+  Name:       Tomorrow Night
+  Author:     小鲤鱼听听
+*/
+
+.cm-s-tomorrow-night.CodeMirror {
+	background: #1d1f21;
+	color: #c5c8c6;
+}
+.cm-s-tomorrow-night div.CodeMirror-selected {
+	background: #2d2d2d;
+}
+.cm-s-tomorrow-night .CodeMirror-line::selection,
+.cm-s-tomorrow-night .CodeMirror-line > span::selection,
+.cm-s-tomorrow-night .CodeMirror-line > span > span::selection {
+	background: rgba(45, 45, 45, 0.99);
+}
+.cm-s-tomorrow-night .CodeMirror-line::-moz-selection,
+.cm-s-tomorrow-night .CodeMirror-line > span::-moz-selection,
+.cm-s-tomorrow-night .CodeMirror-line > span > span::-moz-selection {
+	background: rgba(45, 45, 45, 0.99);
+}
+.cm-s-tomorrow-night .CodeMirror-gutters {
+	background: #1d1f21;
+	border-right: 0px;
+}
+.cm-s-tomorrow-night .CodeMirror-guttermarker {
+	color: #cc6666;
+}
+.cm-s-tomorrow-night .CodeMirror-guttermarker-subtle {
+	color: #777;
+}
+.cm-s-tomorrow-night .CodeMirror-linenumber {
+	color: #515151;
+}
+.cm-s-tomorrow-night .CodeMirror-cursor {
+	border-left: 1px solid #6a6a6a;
+}
+
+.cm-s-tomorrow-night span.cm-comment {
+	color: #969896;
+}
+.cm-s-tomorrow-night span.cm-atom {
+	color: #a16a94;
+}
+.cm-s-tomorrow-night span.cm-number {
+	color: #de935f;
+}
+
+.cm-s-tomorrow-night span.cm-property {
+	color: #81a2be;
+}
+.cm-s-tomorrow-night span.cm-attribute {
+	color: #99cc99;
+}
+.cm-s-tomorrow-night span.cm-keyword {
+	color: #b294bb;
+}
+.cm-s-tomorrow-night span.cm-string {
+	color: #ffcc66;
+}
+
+.cm-s-tomorrow-night span.cm-variable {
+	color: #99cc99;
+}
+.cm-s-tomorrow-night span.cm-variable-2 {
+	color: #3c8ddf;
+}
+.cm-s-tomorrow-night span.cm-def {
+	color: #f99157;
+}
+.cm-s-tomorrow-night span.cm-bracket {
+	color: #cccccc;
+}
+.cm-s-tomorrow-night span.cm-tag {
+	color: #cc6666;
+}
+.cm-s-tomorrow-night span.cm-link {
+	color: #a16a94;
+}
+.cm-s-tomorrow-night span.cm-error {
+	background: #cc6666;
+	color: #6a6a6a;
+}
+
+.cm-s-tomorrow-night .CodeMirror-activeline-background {
+	background: #343600;
+}
+.cm-s-tomorrow-night .CodeMirror-matchingbracket {
+	text-decoration: underline;
+	color: white !important;
+}

BIN=BIN
src/assets/images/myResource/file/dir.png


+ 2 - 0
src/assets/images/myResource/file/file_7z.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1650850638380" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="40824" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M192 384h640a42.666667 42.666667 0 0 1 42.666667 42.666667v362.666666a42.666667 42.666667 0 0 1-42.666667 42.666667H192v106.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h725.333334a21.333333 21.333333 0 0 0 21.333333-21.333333V308.821333L949.909333 298.666667h-126.528A98.048 98.048 0 0 1 725.333333 200.618667V72.661333L716.714667 64H213.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v298.666667zM128 832H42.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V426.666667a42.666667 42.666667 0 0 1 42.666667-42.666667h85.333333V85.333333a85.333333 85.333333 0 0 1 85.333333-85.333333h530.026667L1024 282.453333V938.666667a85.333333 85.333333 0 0 1-85.333333 85.333333H213.333333a85.333333 85.333333 0 0 1-85.333333-85.333333v-106.666667z m91.989333-359.509333v40.704h140.16L261.845333 746.666667h47.616l96-238.08v-36.096h-185.472z m221.952 0v38.4h147.072l-158.592 200.448V746.666667h226.176v-38.4H489.173333l158.208-200.064v-35.712h-205.44z" fill="#57C1FF" p-id="40825"></path></svg>

BIN=BIN
src/assets/images/myResource/file/file_avi.png


BIN=BIN
src/assets/images/myResource/file/file_c#.png


BIN=BIN
src/assets/images/myResource/file/file_c++.png


BIN=BIN
src/assets/images/myResource/file/file_c.png


BIN=BIN
src/assets/images/myResource/file/file_chm.png


BIN=BIN
src/assets/images/myResource/file/file_css.png


BIN=BIN
src/assets/images/myResource/file/file_csv.png


BIN=BIN
src/assets/images/myResource/file/file_dmg.png


+ 2 - 0
src/assets/images/myResource/file/file_excel.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1648199339714" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1481" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M535.601129 0h68.997178v95.247413h349.200376c19.747883 0.842897 41.301976 0 58.400753 12.041392 12.041392 17.580433 10.596425 39.977422 12.041392 60.206962q-0.963311 310.186265 0 619.890875c0 34.67921 3.130762 70.080903-3.97366 104.158043-4.575729 24.805268-33.715898 25.407338-53.10254 26.250235H605.32079V1023.518344h-72.248353C355.100659 991.006585 177.490122 960.541863 0 928.752587V95.367827C178.573848 63.578551 357.147695 32.270931 535.601129 0z" fill="#207245" p-id="1482"></path><path d="M604.598307 131.010348h383.879586v750.058326H604.598307v-71.405457h93.079962v-83.326434h-93.079962v-48.165569h93.079962v-82.844779h-93.079962v-47.5635h93.079962v-83.446848h-93.079962v-48.16557h93.079962v-82.724365h-93.079962v-48.165569h93.079962v-82.844779h-93.079962z" fill="#FFFFFF" p-id="1483"></path><path d="M744.278457 202.415804h162.799624v83.326435H744.278457z" fill="#207245" p-id="1484"></path><path d="M344.504233 314.039511q39.616181-2.889934 79.111948-4.936971-46.600188 97.655691-93.802446 195.190969c31.90969 66.588899 64.662277 132.455315 96.331138 199.52587l-84.289746-5.418626c-19.747883-49.73095-43.830668-97.655691-58.039511-149.674507-15.774224 48.165569-38.412041 94.043274-56.594544 141.365946-25.407338 0-50.935089-1.444967-76.342427-2.528692 29.862653-60.206961 58.761994-120.413923 89.587959-179.777987-26.250235-61.290687-54.908749-121.377234-81.881468-182.427093l76.703669-4.455316c17.339605 46.600188 36.124177 92.477893 50.573848 140.282221 16.135466-50.694262 39.254939-98.257761 58.64158-147.145814z" fill="#FFFFFF" p-id="1485"></path><path d="M744.278457 333.426152h162.799624v83.326435H744.278457zM744.278457 464.316087h162.799624v83.446848H744.278457zM744.278457 595.326435h162.799624v83.326434H744.278457zM744.278457 726.336783h162.799624v83.326434H744.278457z" fill="#207245" p-id="1486"></path></svg>

BIN=BIN
src/assets/images/myResource/file/file_exe.png


+ 2 - 0
src/assets/images/myResource/file/file_flac.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1651997466360" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1318" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M766.1 57.2c-0.8-0.8-1.6-1.4-2.4-2.2l-50-50c-2.8-2.8-8.2-5-12.1-5H286c-58.1 0-105.4 47.2-105.4 105.1v465.5h82.5V105.1c0-12.4 10.5-22.9 22.9-22.9h364.4v152c0 58 47.3 105.1 105.4 105.1h152.4v579.5c0 12.4-10.5 22.9-22.9 22.9H286c-12.4 0-22.9-10.5-22.9-22.9v-57.9h-82.5v57.9c0 58 47.3 105.1 105.4 105.1h599.2c58.1 0 105.4-47.2 105.4-105.1V288.4c0-3.9-2.2-9.3-5-12l-50.2-50c-0.8-0.8-1.5-1.6-2.3-2.4l-167-166.8z m-33.2 177V129.1c0-3.9 2.2-4.8 5-2.1l125.3 125c2.8 2.8 1.8 5-2.1 5H755.8c-12.4 0.1-22.9-10.4-22.9-22.8z" p-id="1319" fill="#E6A23C"></path><path d="M421 666.4h-0.9c-0.4 4.4-1.3 8.7-2.6 12.8L400.2 732h40.3l-17.2-52.4c-1-3.4-1.8-7.8-2.3-13.2z" p-id="1320" fill="#E6A23C"></path><path d="M679.7 598.5H68c-15.1 0-27.5 12.3-27.5 27.4v180.6c0 15.1 12.4 27.4 27.5 27.4h611.7c15.1 0 27.5-12.3 27.5-27.4V626c0-15.1-12.4-27.5-27.5-27.5z m-460.3 107v26.4h-49.7v59.7h-32.2V640.3h86.2v26.4h-54v38.9h49.7z m118.4 86.1H249V640.3h32.2v124.9h56.5v26.4z m121.7 0l-11.3-34.7h-55l-11.1 34.7h-35l55.4-151.3h37.9l54.3 151.3h-35.2z m159-5.5c-11 5.4-25.4 8.1-43.1 8.1-22.8 0-40.9-6.8-54.2-20.5-13.4-13.6-20.1-31.8-20.1-54.5 0-23.9 7.5-43.4 22.4-58.6 14.9-15.2 34.2-22.8 57.9-22.8 14.8 0 27.1 1.9 37.1 5.8v31.2c-10.2-6-21.8-9-34.8-9-14.8 0-26.6 4.7-35.4 14.2-8.9 9.4-13.3 21.8-13.3 37 0 14.8 4.2 26.8 12.6 35.8 8.4 9 19.8 13.6 34.1 13.6 13.5 0 25.7-3.3 36.8-9.8v29.5z" p-id="1321" fill="#E6A23C"></path><path d="M582.1 140.8l-153 46.8c-18.5 5.3-33.6 24.6-33.6 43.1V392.1s-11.1-7.5-35.8-4c-36.3 5.2-65.7 33-65.7 62.2s29.4 47.1 65.7 41.9c36.3-5.1 62.9-32.1 62.9-61.3V293.1c0-13 15.6-18.5 15.6-18.5l135.3-42.4s15-5 15 8.8V351.7s-13.8-7.9-38.5-4.9c-36.3 4.4-65.7 31.6-65.7 60.8 0 29.2 29.4 47.7 65.7 43.3 36.3-4.4 65.7-31.6 65.7-60.8V164.8c0-18.6-15-29.3-33.6-24z" p-id="1322" fill="#E6A23C"></path></svg>

BIN=BIN
src/assets/images/myResource/file/file_gif.png


BIN=BIN
src/assets/images/myResource/file/file_go.png


BIN=BIN
src/assets/images/myResource/file/file_html.png


BIN=BIN
src/assets/images/myResource/file/file_jar.png


BIN=BIN
src/assets/images/myResource/file/file_java.png


BIN=BIN
src/assets/images/myResource/file/file_js.png


BIN=BIN
src/assets/images/myResource/file/file_json.png


BIN=BIN
src/assets/images/myResource/file/file_jsp.png


BIN=BIN
src/assets/images/myResource/file/file_kotlin.png


BIN=BIN
src/assets/images/myResource/file/file_less.png


BIN=BIN
src/assets/images/myResource/file/file_lua.png


BIN=BIN
src/assets/images/myResource/file/file_markdown.png


BIN=BIN
src/assets/images/myResource/file/file_music.png


BIN=BIN
src/assets/images/myResource/file/file_nginx.png


BIN=BIN
src/assets/images/myResource/file/file_oa.png


BIN=BIN
src/assets/images/myResource/file/file_objective_c.png


BIN=BIN
src/assets/images/myResource/file/file_open.png


BIN=BIN
src/assets/images/myResource/file/file_pdf.png


BIN=BIN
src/assets/images/myResource/file/file_php.png


BIN=BIN
src/assets/images/myResource/file/file_powershell.png


+ 2 - 0
src/assets/images/myResource/file/file_ppt.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1648199330263" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1279" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M538.731891 0h65.98683v107.168391c124.387582 0.722484 248.895579-1.324553 373.28316 0a40.699906 40.699906 0 0 1 45.034808 46.118533c2.047037 222.404516 0 444.929445 1.204139 667.454374-1.204139 24.082785 2.287865 50.694262-11.198495 72.248354-16.978363 12.041392-39.014111 10.957667-59.002822 12.041392-116.319849-0.60207-232.639699 0-349.200376 0V1023.518344h-72.248354C355.100659 990.886171 177.490122 960.662277 0 928.752587V95.488241C179.537159 63.698965 359.074318 31.30762 538.731891 0z" fill="#D24625" p-id="1280"></path><path d="M604.718721 142.931326H988.598307v726.216369H604.718721v-95.247413h279.239887v-47.563499H604.718721v-60.206962h279.239887v-46.96143H604.839135v-69.960489c46.118532 14.570085 98.619003 14.208843 139.800564-14.088429 44.553151-27.093133 67.793039-78.630292 71.646284-130.047036H663.119473c0-51.777987 0.60207-103.555974-0.963311-155.213547-19.145814 3.732832-38.171214 7.826905-57.196614 12.041392z" fill="#FFFFFF" p-id="1281"></path><path d="M686.35936 224.69238a165.689558 165.689558 0 0 1 153.16651 156.5381c-51.055503 0.60207-102.111007 0-153.286924 0 0.120414-52.380056 0.120414-104.278457 0.120414-156.5381z" fill="#D24625" p-id="1282"></path><path d="M186.64158 314.521167c63.21731 3.130762 139.680151-25.527752 192.662277 22.878645 50.092192 62.374412 36.84666 176.888053-37.44873 214.095955-26.370649 13.847601-56.714958 12.041392-85.373471 10.957667v139.68015l-69.238006-5.900282c-1.806209-127.157103-2.047037-254.434619-0.60207-381.712135z" fill="#FFFFFF" p-id="1283"></path><path d="M255.759172 378.942615c22.878645-0.963311 51.296331-5.298213 66.709313 16.737536a87.902164 87.902164 0 0 1 1.565381 78.148635c-13.245532 24.082785-43.228598 22.035748-66.468485 24.925682-2.408278-39.857008-2.167451-79.714017-1.806209-119.811853z" fill="#D24625" p-id="1284"></path></svg>

BIN=BIN
src/assets/images/myResource/file/file_properties.png


BIN=BIN
src/assets/images/myResource/file/file_python.png


BIN=BIN
src/assets/images/myResource/file/file_r.png


BIN=BIN
src/assets/images/myResource/file/file_rar.png


BIN=BIN
src/assets/images/myResource/file/file_rtf.png


BIN=BIN
src/assets/images/myResource/file/file_rust.png


BIN=BIN
src/assets/images/myResource/file/file_sass.png


BIN=BIN
src/assets/images/myResource/file/file_scss.png


BIN=BIN
src/assets/images/myResource/file/file_shell.png


BIN=BIN
src/assets/images/myResource/file/file_sql.png


BIN=BIN
src/assets/images/myResource/file/file_stylus.png


BIN=BIN
src/assets/images/myResource/file/file_svg.png


BIN=BIN
src/assets/images/myResource/file/file_swift.png


+ 2 - 0
src/assets/images/myResource/file/file_tar.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1650850892160" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="41166" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M192 384h640a42.666667 42.666667 0 0 1 42.666667 42.666667v362.666666a42.666667 42.666667 0 0 1-42.666667 42.666667H192v106.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h725.333334a21.333333 21.333333 0 0 0 21.333333-21.333333V308.821333L949.909333 298.666667h-126.528A98.048 98.048 0 0 1 725.333333 200.618667V72.661333L716.714667 64H213.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v298.666667zM128 832H42.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V426.666667a42.666667 42.666667 0 0 1 42.666667-42.666667h85.333333V85.333333a85.333333 85.333333 0 0 1 85.333333-85.333333h530.026667L1024 282.453333V938.666667a85.333333 85.333333 0 0 1-85.333333 85.333333H213.333333a85.333333 85.333333 0 0 1-85.333333-85.333333v-106.666667zM80.853333 472.490667v38.4h85.056V746.666667H208.426667V510.890667h85.034666v-38.4H80.853333z m317.269334 0L298.922667 746.666667h45.397333l23.637333-68.736h108.650667L500.224 746.666667h45.44l-99.2-274.176h-48.341333z m-17.450667 168.576l41.066667-120.192h1.450666l40.704 120.192h-83.221333z m190.421333-168.576V746.666667h42.517334v-111.744h67.221333c14.549333 0 25.813333 3.072 33.450667 9.984 7.253333 6.528 11.626667 17.28 13.077333 32.256l3.264 33.792c1.450667 15.36 5.098667 27.264 11.264 35.712h46.144c-8.704-9.984-14.165333-23.424-15.616-40.32l-4.352-44.16c-2.922667-26.88-15.637333-42.624-38.165333-48v-0.768a56.512 56.512 0 0 0 33.066666-24.192c7.253333-11.136 10.901333-23.808 10.901334-38.016 0-26.112-8.362667-46.08-24.341334-59.904-15.274667-12.672-36.352-18.816-62.869333-18.816H571.093333z m42.517334 38.4h67.221333c17.450667 0 30.165333 3.072 38.165333 9.984 8 6.528 11.989333 17.28 11.989334 32.256 0 13.824-3.989333 24.576-11.989334 32.256-8.725333 7.296-21.44 11.136-38.165333 11.136h-67.221333v-85.632z" fill="#BA4CD7" p-id="41167"></path></svg>

BIN=BIN
src/assets/images/myResource/file/file_txt.png


BIN=BIN
src/assets/images/myResource/file/file_typescript.png


BIN=BIN
src/assets/images/myResource/file/file_unknown.png


BIN=BIN
src/assets/images/myResource/file/file_vue.png


+ 2 - 0
src/assets/images/myResource/file/file_word.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1648199299550" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3869" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M535.119473 0h69.599248v95.247413C729.226717 96.331138 853.614299 93.92286 977.881468 96.331138a40.459078 40.459078 0 0 1 44.914393 45.516463c2.047037 234.566322 0 469.614299 1.204139 703.819379-1.204139 24.082785 2.287865 50.694262-11.318909 72.248354-16.978363 12.041392-38.893697 10.837253-58.761994 12.041392h-349.200376V1023.518344h-72.248354C354.980245 990.886171 177.490122 960.541863 0 928.752587V95.488241C178.33302 63.578551 356.786453 32.511759 535.119473 0z" fill="#2A5699" p-id="3870"></path><path d="M604.718721 131.010348H988.598307v761.979304H604.718721v-95.247413h302.479774v-48.165569H604.718721v-59.002822h302.479774v-48.16557H604.718721v-59.002822h302.479774v-48.165569H604.718721v-60.206961h302.479774V428.673565H604.718721v-60.206961h302.479774v-46.96143H604.718721v-59.604892h302.479774V214.336783H604.718721zM240.827846 341.373471c22.156162-1.324553 44.19191-2.287865 66.348071-3.492003 15.533396 80.4365 31.30762 160.632173 48.165569 240.827845 13.125118-82.724365 27.695202-165.087488 41.783632-247.571025 23.239887-0.842897 46.479774-2.167451 69.719661-3.612418-26.370649 115.356538-49.369708 231.796802-78.148636 346.430856-19.386642 10.355597-48.165569 0-71.52587 1.204139C301.034807 596.169332 283.093133 517.779868 269.245532 438.667921c-13.606773 76.944497-31.30762 153.16651-46.841016 229.508937-22.39699-1.204139-44.793979-2.528692-67.311383-4.094073-19.266228-104.760113-42.024459-208.918156-60.206962-313.919097 19.868297-0.963311 39.857008-1.806209 60.206962-2.528693 12.041392 75.860771 25.648166 151.360301 36.124177 227.341487 16.135466-77.907808 32.873001-155.695202 49.610536-233.603011z" fill="#FFFFFF" p-id="3871"></path></svg>

BIN=BIN
src/assets/images/myResource/file/file_xml.png


BIN=BIN
src/assets/images/myResource/file/file_yaml.png


BIN=BIN
src/assets/images/myResource/file/file_zip.png


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2 - 0
src/assets/mavonEditor/css/github-markdown.css


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
src/assets/mavonEditor/css/katex.min.css


+ 75 - 0
src/assets/mavonEditor/css/tomorrow-night.css

@@ -0,0 +1,75 @@
+/* Tomorrow Night Theme */
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+/* Original theme - https://github.com/chriskempson/tomorrow-theme */
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+
+/* Tomorrow Comment */
+.hljs-comment,
+.hljs-quote {
+	color: #969896;
+}
+
+/* Tomorrow Red */
+.hljs-variable,
+.hljs-template-variable,
+.hljs-tag,
+.hljs-name,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-regexp,
+.hljs-deletion {
+	color: #cc6666;
+}
+
+/* Tomorrow Orange */
+.hljs-number,
+.hljs-built_in,
+.hljs-builtin-name,
+.hljs-literal,
+.hljs-type,
+.hljs-params,
+.hljs-meta,
+.hljs-link {
+	color: #de935f;
+}
+
+/* Tomorrow Yellow */
+.hljs-attribute {
+	color: #f0c674;
+}
+
+/* Tomorrow Green */
+.hljs-string,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-addition {
+	color: #b5bd68;
+}
+
+/* Tomorrow Blue */
+.hljs-title,
+.hljs-section {
+	color: #81a2be;
+}
+
+/* Tomorrow Purple */
+.hljs-keyword,
+.hljs-selector-tag {
+	color: #b294bb;
+}
+
+.hljs {
+	display: block;
+	overflow-x: auto;
+	background: #1d1f21;
+	color: #c5c8c6;
+	padding: 16px;
+}
+
+.hljs-emphasis {
+	font-style: italic;
+}
+
+.hljs-strong {
+	font-weight: bold;
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 531 - 0
src/assets/mavonEditor/js/highlight.min.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
src/assets/mavonEditor/js/katex.min.js


+ 322 - 0
src/assets/mavonEditor/js/lang.hljs.js

@@ -0,0 +1,322 @@
+export default {
+	'1c': '1c',
+	abnf: 'abnf',
+	accesslog: 'accesslog',
+	actionscript: 'actionscript',
+	as: 'actionscript',
+	ada: 'ada',
+	apache: 'apache',
+	apacheconf: 'apache',
+	applescript: 'applescript',
+	osascript: 'applescript',
+	arduino: 'arduino',
+	armasm: 'armasm',
+	arm: 'armasm',
+	asciidoc: 'asciidoc',
+	adoc: 'asciidoc',
+	aspectj: 'aspectj',
+	autohotkey: 'autohotkey',
+	ahk: 'autohotkey',
+	autoit: 'autoit',
+	avrasm: 'avrasm',
+	awk: 'awk',
+	axapta: 'axapta',
+	bash: 'bash',
+	sh: 'bash',
+	zsh: 'bash',
+	basic: 'basic',
+	bnf: 'bnf',
+	brainfuck: 'brainfuck',
+	bf: 'brainfuck',
+	cal: 'cal',
+	capnproto: 'capnproto',
+	capnp: 'capnproto',
+	ceylon: 'ceylon',
+	clean: 'clean',
+	icl: 'clean',
+	dcl: 'clean',
+	'clojure-repl': 'clojure-repl',
+	clojure: 'clojure',
+	clj: 'clojure',
+	cmake: 'cmake',
+	'cmake.in': 'cmake',
+	coffeescript: 'coffeescript',
+	coffee: 'coffeescript',
+	cson: 'coffeescript',
+	iced: 'coffeescript',
+	coq: 'coq',
+	cos: 'cos',
+	cls: 'cos',
+	cpp: 'cpp',
+	c: 'cpp',
+	cc: 'cpp',
+	h: 'cpp',
+	'c++': 'cpp',
+	'h++': 'cpp',
+	hpp: 'cpp',
+	crmsh: 'crmsh',
+	crm: 'crmsh',
+	pcmk: 'crmsh',
+	crystal: 'crystal',
+	cr: 'crystal',
+	cs: 'cs',
+	csharp: 'cs',
+	csp: 'csp',
+	css: 'css',
+	d: 'd',
+	dart: 'dart',
+	delphi: 'delphi',
+	dpr: 'delphi',
+	dfm: 'delphi',
+	pas: 'delphi',
+	pascal: 'delphi',
+	freepascal: 'delphi',
+	lazarus: 'delphi',
+	lpr: 'delphi',
+	lfm: 'delphi',
+	diff: 'diff',
+	patch: 'diff',
+	django: 'django',
+	jinja: 'django',
+	dns: 'dns',
+	bind: 'dns',
+	zone: 'dns',
+	dockerfile: 'dockerfile',
+	docker: 'dockerfile',
+	dos: 'dos',
+	bat: 'dos',
+	cmd: 'dos',
+	dsconfig: 'dsconfig',
+	dts: 'dts',
+	dust: 'dust',
+	dst: 'dust',
+	ebnf: 'ebnf',
+	elixir: 'elixir',
+	elm: 'elm',
+	erb: 'erb',
+	'erlang-repl': 'erlang-repl',
+	erlang: 'erlang',
+	erl: 'erlang',
+	excel: 'excel',
+	xlsx: 'excel',
+	xls: 'excel',
+	fix: 'fix',
+	flix: 'flix',
+	fortran: 'fortran',
+	f90: 'fortran',
+	f95: 'fortran',
+	fsharp: 'fsharp',
+	fs: 'fsharp',
+	gams: 'gams',
+	gms: 'gams',
+	gauss: 'gauss',
+	gss: 'gauss',
+	gcode: 'gcode',
+	nc: 'gcode',
+	gherkin: 'gherkin',
+	feature: 'gherkin',
+	glsl: 'glsl',
+	go: 'go',
+	golang: 'go',
+	golo: 'golo',
+	gradle: 'gradle',
+	groovy: 'groovy',
+	haml: 'haml',
+	handlebars: 'handlebars',
+	hbs: 'handlebars',
+	'html.hbs': 'handlebars',
+	'html.handlebars': 'handlebars',
+	haskell: 'haskell',
+	hs: 'haskell',
+	haxe: 'haxe',
+	hx: 'haxe',
+	hsp: 'hsp',
+	htmlbars: 'htmlbars',
+	http: 'http',
+	https: 'http',
+	hy: 'hy',
+	hylang: 'hy',
+	inform7: 'inform7',
+	i7: 'inform7',
+	ini: 'ini',
+	toml: 'ini',
+	irpf90: 'irpf90',
+	java: 'java',
+	jsp: 'java',
+	javascript: 'javascript',
+	js: 'javascript',
+	jsx: 'javascript',
+	'jboss-cli': 'jboss-cli',
+	'wildfly-cli': 'jboss-cli',
+	json: 'json',
+	'julia-repl': 'julia-repl',
+	julia: 'julia',
+	kotlin: 'kotlin',
+	lasso: 'lasso',
+	ls: 'livescript',
+	lassoscript: 'lasso',
+	ldif: 'ldif',
+	leaf: 'leaf',
+	less: 'less',
+	lisp: 'lisp',
+	livecodeserver: 'livecodeserver',
+	livescript: 'livescript',
+	llvm: 'llvm',
+	lsl: 'lsl',
+	lua: 'lua',
+	makefile: 'makefile',
+	mk: 'makefile',
+	mak: 'makefile',
+	markdown: 'markdown',
+	md: 'markdown',
+	mkdown: 'markdown',
+	mkd: 'markdown',
+	mathematica: 'mathematica',
+	mma: 'mathematica',
+	matlab: 'matlab',
+	maxima: 'maxima',
+	mel: 'mel',
+	mercury: 'mercury',
+	m: 'mercury',
+	moo: 'mercury',
+	mipsasm: 'mipsasm',
+	mips: 'mipsasm',
+	mizar: 'mizar',
+	mojolicious: 'mojolicious',
+	monkey: 'monkey',
+	moonscript: 'moonscript',
+	moon: 'moonscript',
+	n1ql: 'n1ql',
+	nginx: 'nginx',
+	nginxconf: 'nginx',
+	nimrod: 'nimrod',
+	nim: 'nimrod',
+	nix: 'nix',
+	nixos: 'nix',
+	nsis: 'nsis',
+	objectivec: 'objectivec',
+	mm: 'objectivec',
+	objc: 'objectivec',
+	'obj-c': 'objectivec',
+	ocaml: 'ocaml',
+	ml: 'sml',
+	openscad: 'openscad',
+	scad: 'openscad',
+	oxygene: 'oxygene',
+	parser3: 'parser3',
+	perl: 'perl',
+	pl: 'perl',
+	pm: 'perl',
+	pf: 'pf',
+	'pf.conf': 'pf',
+	php: 'php',
+	php3: 'php',
+	php4: 'php',
+	php5: 'php',
+	php6: 'php',
+	pony: 'pony',
+	powershell: 'powershell',
+	ps: 'powershell',
+	processing: 'processing',
+	profile: 'profile',
+	prolog: 'prolog',
+	protobuf: 'protobuf',
+	puppet: 'puppet',
+	pp: 'puppet',
+	purebasic: 'purebasic',
+	pb: 'purebasic',
+	pbi: 'purebasic',
+	python: 'python',
+	py: 'python',
+	gyp: 'python',
+	q: 'q',
+	k: 'q',
+	kdb: 'q',
+	qml: 'qml',
+	qt: 'qml',
+	r: 'r',
+	rib: 'rib',
+	roboconf: 'roboconf',
+	graph: 'roboconf',
+	instances: 'roboconf',
+	routeros: 'routeros',
+	mikrotik: 'routeros',
+	rsl: 'rsl',
+	ruby: 'ruby',
+	rb: 'ruby',
+	gemspec: 'ruby',
+	podspec: 'ruby',
+	thor: 'ruby',
+	irb: 'ruby',
+	ruleslanguage: 'ruleslanguage',
+	rust: 'rust',
+	rs: 'rust',
+	scala: 'scala',
+	scheme: 'scheme',
+	scilab: 'scilab',
+	sci: 'scilab',
+	scss: 'scss',
+	shell: 'shell',
+	console: 'shell',
+	smali: 'smali',
+	smalltalk: 'smalltalk',
+	st: 'smalltalk',
+	sml: 'sml',
+	sqf: 'sqf',
+	sql: 'sql',
+	stan: 'stan',
+	stata: 'stata',
+	do: 'stata',
+	ado: 'stata',
+	step21: 'step21',
+	p21: 'step21',
+	step: 'step21',
+	stp: 'step21',
+	stylus: 'stylus',
+	styl: 'stylus',
+	subunit: 'subunit',
+	swift: 'swift',
+	taggerscript: 'taggerscript',
+	tap: 'tap',
+	tcl: 'tcl',
+	tk: 'tcl',
+	tex: 'tex',
+	thrift: 'thrift',
+	tp: 'tp',
+	twig: 'twig',
+	craftcms: 'twig',
+	typescript: 'typescript',
+	ts: 'typescript',
+	vala: 'vala',
+	vbnet: 'vbnet',
+	vb: 'vbnet',
+	'vbscript-html': 'vbscript-html',
+	vbscript: 'vbscript',
+	vbs: 'vbscript',
+	verilog: 'verilog',
+	v: 'verilog',
+	sv: 'verilog',
+	svh: 'verilog',
+	vhdl: 'vhdl',
+	vim: 'vim',
+	x86asm: 'x86asm',
+	xl: 'xl',
+	tao: 'xl',
+	xml: 'xml',
+	html: 'xml',
+	xhtml: 'xml',
+	rss: 'xml',
+	atom: 'xml',
+	xjb: 'xml',
+	xsd: 'xml',
+	xsl: 'xml',
+	plist: 'xml',
+	xquery: 'xquery',
+	xpath: 'xquery',
+	xq: 'xquery',
+	yaml: 'yaml',
+	yml: 'yaml',
+	YAML: 'yaml',
+	zephir: 'zephir',
+	zep: 'zephir'
+}

+ 4 - 0
src/config/reSource.js

@@ -0,0 +1,4 @@
+const config = {
+	baseContext: '/api/webapp'
+}
+export default config

+ 48 - 0
src/libs/fileOperationPlugins.js

@@ -0,0 +1,48 @@
+/**
+ * 以服务的方式,将对文件的一些操作挂载到 Vue 上
+ * @description 各个服务的参数传递查看服务封装对应目录下的 index.js 文件,里面注明了需要传递的参数
+ */
+
+import { defineAsyncComponent } from 'vue'
+
+/**
+ * 动态引入操作文件的弹窗组件
+ */
+const importDialog = (path) => {
+	return defineAsyncComponent(() => import(`@/views/myResource/file/dialog/${path}/index.js`))
+}
+
+/**
+ * 服务挂载到 Vue - $openDialog,各个服务的名称为其对应的文件夹的名称
+ * @description 例如,创建文件夹功能:服务封装路径 'views/myResource/file/dialog/addFolder' ,则如下:
+ * 在 *.vue 文件中,使用 this.$openDialog.addFolder 调用创建文件夹服务
+ * 在 *.js 文件中,需要先在文件顶部引入 Vue ,即 import Vue from 'vue' ,然后使用 Vue.property.$openDialog.addFolder 调用创建文件夹服务
+ */
+const dialogModules = import.meta.glob('@/views/myResource/file/dialog/*/index.js', { eager: true })
+const openDialog = Object.keys(dialogModules).reduce((acc, path) => {
+	const moduleName = path.split('/').slice(-2)[0]
+	acc[moduleName] = dialogModules[path].default
+	return acc
+}, {})
+
+/**
+ * 动态添加操作文件的遮罩或浮层组件
+ */
+const importBox = (path) => {
+	return defineAsyncComponent(() => import(`@/views/myResource/file/box/${path}/index.js`))
+}
+
+/**
+ * 服务挂载到 Vue - $openBox,各个服务的名称为其对应的文件夹的名称
+ * @description 例如,图片预览功能:服务封装路径 'views/myResource/file/box/imgPreview' ,则如下:
+ * 在 *.vue 文件中,使用 this.$openBox.imgPreview 调用图片预览服务
+ * 在 *.js 文件中,需要先在文件顶部引入 Vue ,即 import Vue from 'vue' ,然后使用 Vue.property.$openBox.imgPreview 调用图片预览服务
+ */
+const boxModules = import.meta.glob('@/views/myResource/file/box/*/index.js', { eager: true })
+const openBox = Object.keys(boxModules).reduce((acc, path) => {
+	const moduleName = path.split('/').slice(-2)[0]
+	acc[moduleName] = boxModules[path].default
+	return acc
+}, {})
+
+export default { openDialog, openBox }

+ 400 - 0
src/libs/globalFunction/file.js

@@ -0,0 +1,400 @@
+import * as Vue from 'vue'
+import router from '@/router'
+import config from '@/config/reSource'
+import { message } from 'ant-design-vue'
+import { fileImgMap, unknownImg, fileSuffixCodeModeMap, markdownFileType } from '@/libs/map.js'
+import { officeFileType } from '@/libs/map.js'
+
+// 全局函数 - 文件相关
+const fileFunction = {
+	/**
+	 * 格式化文件大小
+	 * @param {number} size 文件大小
+	 * @param {boolean} isInteger 是否只显示整数位,默认不截取
+	 * @returns {string} 文件大小(带单位)
+	 */
+	calculateFileSize(size, isInteger = false) {
+		const B = 1024
+		const KB = Math.pow(1024, 2)
+		const MB = Math.pow(1024, 3)
+		const GB = Math.pow(1024, 4)
+		if (isInteger) {
+			// 截取为整数
+			if (size < B) {
+				return `${size}B`
+			} else if (size < KB) {
+				return `${(size / B).toFixed(0)}KB`
+			} else if (size < MB) {
+				return `${(size / KB).toFixed(0)}MB`
+			} else if (size < GB) {
+				return `${(size / MB).toFixed(0)}GB`
+			} else {
+				return `${(size / GB).toFixed(0)}TB`
+			}
+		} else {
+			// 保留小数位
+			if (size < B) {
+				return `${size}B`
+			} else if (size < KB) {
+				return `${(size / B).toFixed(0)}KB`
+			} else if (size < MB) {
+				return `${(size / KB).toFixed(1)}MB`
+			} else if (size < GB) {
+				return `${(size / MB).toFixed(2)}GB`
+			} else {
+				return `${(size / GB).toFixed(3)}TB`
+			}
+		}
+	},
+	/**
+	 * 获取流式的缩略图、视频封面图
+	 * @param {object} row 文件信息
+	 * @returns {string} 流式图片
+	 */
+	getMinImgStream(row) {
+		return `${config.baseContext}/filetransfer/preview?userFileId=${row.userFileId}&isMin=true&shareBatchNum=${
+			row.shareBatchNum == null ? '' : row.shareBatchNum
+		}&extractionCode=${row.extractionCode == null ? '' : row.extractionCode}`
+	},
+	/**
+	 * 获取文件查看路径
+	 * @param {object} row 文件信息
+	 * @returns {string} 文件路径
+	 */
+	getViewFilePath(row) {
+		return `${config.baseContext}/filetransfer/preview?userFileId=${row.userFileId}&isMin=false&shareBatchNum=${
+			row.shareBatchNum == null ? '' : row.shareBatchNum
+		}&extractionCode=${row.extractionCode == null ? '' : row.extractionCode}`
+	},
+	/**
+	 * 获取文件下载路径
+	 * @param {object} row 文件信息
+	 * @returns {string}  文件下载路径
+	 */
+	getDownloadFilePath(row) {
+		return `${config.baseContext}/filetransfer/downloadfile?userFileId=${row.userFileId}&shareBatchNum=${
+			row.shareBatchNum == null ? '' : row.shareBatchNum
+		}&extractionCode=${row.extractionCode == null ? '' : row.extractionCode}`
+	},
+	getDownloadFilePath2(row) {
+		return `${config.baseContext}/filetransfer/downloadfile?userFileId=${row.userFileId}&shareBatchNum=${
+			row.shareBatchNum == null ? '' : row.shareBatchNum
+		}&extractionCode=${row.extractionCode == null ? '' : row.extractionCode}&admin=true`
+	},
+	/**
+	 * 获取 Onlyoffice 文件创建路径
+	 * @param {object} row
+	 * @returns {string} office 文件创建路径
+	 */
+	createFileOnlineByOffice(data) {
+		let fileUrl = `${location.protocol}//${location.host}${config.baseContext}`
+		const { href } = router.resolve({
+			name: 'Onlyoffice',
+			query: {
+				fileUrl: fileUrl,
+				fileName: data.fileName,
+				filePath: data.filePath,
+				extendName: data.extendName,
+				ot: 'add'
+			}
+		})
+		window.open(href, '_blank')
+	},
+	/**
+	 * 获取 Onlyoffice 文件在线预览路径
+	 * @param {object} row
+	 * @returns {string} office 文件在线预览路径
+	 */
+	getFileOnlineViewPathByOffice(row) {
+		let userFileId = row.userFileId
+
+		const { href } = router.resolve({
+			name: 'Onlyoffice',
+			query: {
+				userFileId: userFileId,
+				ot: 'detail'
+			}
+		})
+		window.open(href, '_blank')
+	},
+	/**
+	 * 获取 Onlyoffice 文件在线编辑路径
+	 * @param {object} row
+	 * @returns {string} office 文件在线编辑路径
+	 */
+	getFileOnlineEditPathByOffice(row) {
+		let userFileId = row.userFileId
+
+		const { href } = router.resolve({
+			name: 'Onlyoffice',
+			query: {
+				userFileId: userFileId,
+				ot: 'edit'
+			}
+		})
+		window.open(href, '_blank')
+	},
+	/**
+	 * 获取分享链接
+	 * @param {string} shareBatchNum
+	 * @returns {string} 完整的分享链接
+	 */
+	getShareLink(shareBatchNum) {
+		return `${location.protocol}//${location.host}/share/${shareBatchNum}`
+	},
+	/**
+	 * 复制分享链接
+	 * @param {string} shareBatchNum
+	 * @param {string} extractionCode
+	 */
+	copyShareLink(shareBatchNum, extractionCode) {
+		let input = document.createElement('textarea') // 直接构建textarea以保持换行
+		input.value =
+			extractionCode === null
+				? `分享链接:${this.getShareLink(shareBatchNum)}\n复制链接到浏览器中并输入提取码即可查看文件`
+				: `分享链接:${this.getShareLink(
+						shareBatchNum
+				  )}\n提取码:${extractionCode}\n复制链接到浏览器中并输入提取码即可查看文件` // 设置内容
+		document.body.appendChild(input) // 添加临时实例
+		input.select() // 选择实例内容
+		document.execCommand('Copy') // 执行复制
+		document.body.removeChild(input) // 删除临时实例
+		message.success('复制成功')
+	},
+	/**
+	 * 根据文件扩展名设置文件图片
+	 * @param {object} file 文件信息
+	 */
+	setFileImg(file) {
+		if (file.isDir === 1) {
+			//  文件夹
+			return fileImgMap.get('dir')
+		} else if (
+			Number(router.currentRoute.value.query.fileType) !== 6 &&
+			['jpg', 'png', 'jpeg', 'gif', 'mp4', 'webp'].includes(file.extendName.toLowerCase())
+		) {
+			// 图片、视频类型,直接显示缩略图
+			return this.getMinImgStream(file)
+		} else if (fileImgMap.has(file.extendName.toLowerCase())) {
+			// 可以识别文件类型的文件
+			return fileImgMap.get(file.extendName.toLowerCase())
+		} else {
+			// 无法识别文件类型的文件
+			return unknownImg
+		}
+	},
+	/**
+	 * 判断是否是视频文件
+	 * @param {object} file 文件信息
+	 */
+	isVideoFile(file) {
+		if (['avi', 'mp4', 'mpg', 'mov', 'swf'].includes(file.extendName?.toLowerCase())) {
+			return true
+		} else {
+			return false
+		}
+	},
+	/**
+	 * 图片预览
+	 * @param {*} currentIndex 当前图片索引
+	 * @param {*} imgInfo 单个图片信息
+	 * @param {*} imgInfoList 多个图片列表
+	 */
+	handleImgPreview(currentIndex, imgInfo = {}, imgInfoList = []) {
+		// 图片分类下 - 传递整个页面的图片列表;非图片分类下 - 由单个图片构建图片列表
+		const imgList =
+			Number(router.currentRoute.query.fileType) === 1
+				? imgInfoList.map((item) => {
+						return {
+							...item,
+							fileUrl: this.getViewFilePath(item),
+							downloadLink: this.getDownloadFilePath(item)
+						}
+				  })
+				: [
+						{
+							...imgInfo,
+							fileUrl: this.getViewFilePath(imgInfo),
+							downloadLink: this.getDownloadFilePath(imgInfo)
+						}
+				  ]
+		const defaultIndex = Number(router.currentRoute.query.fileType) === 1 ? currentIndex : 0
+		Vue.prototype.$openBox.imgPreview({ imgList, defaultIndex })
+	},
+	/**
+	 * 视频预览
+	 * @param {*} currentIndex 当前视频索引
+	 * @param {*} videoInfo 单个视频信息
+	 * @param {*} videoInfoList 多个视频列表
+	 */
+	handleVideoPreview(currentIndex, videoInfo = {}, videoInfoList = []) {
+		// 视频分类下 - 传递整个页面的视频列表;非视频分类下 - 由单个视频构建视频列表
+		const videoList =
+			Number(router.currentRoute.query.fileType) === 3
+				? videoInfoList.map((item) => {
+						return {
+							...item,
+							fileUrl: this.getViewFilePath(item),
+							downloadLink: this.getDownloadFilePath(item)
+						}
+				  })
+				: [
+						{
+							...videoInfo,
+							fileUrl: this.getViewFilePath(videoInfo),
+							downloadLink: this.getDownloadFilePath(videoInfo)
+						}
+				  ]
+		const defaultIndex = Number(router.currentRoute.query.fileType) === 3 ? currentIndex : 0
+		Vue.prototype.$openBox.videoPreview({ videoList, defaultIndex })
+	},
+	/**
+	 * 音频预览
+	 * @param {*} currentIndex 当前音频索引
+	 * @param {*} audioInfo 单个音频信息
+	 * @param {*} audioInfoList 多个音频列表
+	 */
+	handleAudioPreview(currentIndex, audioInfo = {}, audioInfoList = []) {
+		// 音频分类下 - 传递整个页面的音频列表;非音频分类下 - 由单个音频构建音频列表
+		const audioList =
+			Number(router.currentRoute.query.fileType) === 4
+				? audioInfoList.map((item) => {
+						return {
+							...item,
+							fileUrl: this.getViewFilePath(item),
+							downloadLink: this.getDownloadFilePath(item)
+						}
+				  })
+				: [
+						{
+							...audioInfo,
+							fileUrl: this.getViewFilePath(audioInfo),
+							downloadLink: this.getDownloadFilePath(audioInfo)
+						}
+				  ]
+		const defaultIndex = Number(router.currentRoute.query.fileType) === 4 ? currentIndex : 0
+		Vue.prototype.$openBox.audioPreview({ audioList, defaultIndex })
+	},
+	/**
+	 * 文件预览
+	 * @description 若当前点击的为文件夹,则进入文件夹内部;若是文件,则进行相应的预览。
+	 * @param {object} row 文件信息
+	 * @param {number} currentIndex 当前文件索引
+	 * @param {array} fileList 文件列表
+	 */
+	handleFileNameClick(row, currentIndex, fileList = []) {
+		// 如果当前文件在回收站中,则不允许预览
+		if (row.deleteFlag !== undefined && row.deleteFlag !== 0) {
+			return false
+		}
+		// 若是文件夹则进入该文件夹
+		if (row.isDir) {
+			if (router.currentRoute.name === 'Share') {
+				// 当前是查看他人分享列表的页面
+				router.push({
+					query: {
+						filePath: `${row.shareFilePath === '/' ? '' : row.shareFilePath}/${row.fileName}`
+					}
+				})
+			} else if (Number(router.currentRoute.query.fileType) === 8) {
+				// 当前是我的已分享列表页面
+				router.push({
+					query: {
+						fileType: 8,
+						filePath: `${row.shareFilePath === '/' ? '' : row.shareFilePath}/${row.fileName}`,
+						shareBatchNum: row.shareBatchNum
+					}
+				})
+			} else if (Number(router.currentRoute.query.fileType) !== 6) {
+				// 回收站页面不允许打开文件夹
+				// 网盘页面
+				router.push({
+					query: {
+						filePath: `${row.filePath === '/' ? '' : row.filePath}/${row.fileName}`,
+						fileType: 0
+					}
+				})
+			}
+		}
+		// 若是文件,则进行相应的预览
+		else {
+			// 若当前点击项是图片
+			const PIC = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp']
+			if (PIC.includes(row.extendName.toLowerCase())) {
+				this.handleImgPreview(currentIndex, row, fileList)
+				return false
+			}
+			//  若当前点击项是可以使用office在线预览的
+			if ([...officeFileType].includes(row.extendName.toLowerCase())) {
+				// this.getFileOnlineViewPathByOffice(row)
+				return false
+			}
+			//  若当前点击项是pdf
+			if (row.extendName.toLowerCase() === 'pdf') {
+				// window.open(this.getViewFilePath(row), '_blank')
+			}
+			//  若当前点击项是代码或文本文件
+			let codeFileSuffix = row.extendName.toLowerCase()
+			if (codeFileSuffix === 'yaml') {
+				codeFileSuffix = 'yml'
+			}
+			// 无格式文件也可以在线编辑
+			if (fileSuffixCodeModeMap.has(codeFileSuffix) || (row.isDir === 0 && row.extendName === '')) {
+				Vue.prototype.$openBox.codePreview({ fileInfo: row, isEdit: false })
+				return false
+			}
+			//  若当前点击项是 markdown 文档
+			if (markdownFileType.includes(row.extendName.toLowerCase())) {
+				Vue.prototype.$openBox.markdownPreview({
+					fileInfo: row,
+					editable: false
+				})
+				return false
+			}
+			//  若当前点击项是视频mp4格式
+			const VIDEO = ['mp4']
+			if (VIDEO.includes(row.extendName.toLowerCase())) {
+				this.handleVideoPreview(currentIndex, row, fileList)
+				return false
+			}
+			//  若当前点击项是音频 mp3、flac 格式
+			const AUDIO = ['mp3', 'flac']
+			if (AUDIO.includes(row.extendName.toLowerCase())) {
+				this.handleAudioPreview(currentIndex, row, fileList)
+				return false
+			}
+		}
+	},
+	/**
+	 * 文件名称拼接,包括文件名称 + 文件后缀
+	 * @param {object} file 文件信息
+	 * @param {boolean} isHighlight 是否需要展示高亮效果,默认不需要
+	 * @returns {string} 完整文件名称
+	 */
+	getFileNameComplete(file, isHighlight = false) {
+		return isHighlight === true && file.highlightFields
+			? `${file.highlightFields}${file.isDir === 0 && file.extendName ? `.${file.extendName}` : ''}`
+			: `${file.fileName}${file.isDir === 0 && file.extendName ? `.${file.extendName}` : ''}`
+	},
+	/**
+	 * 文件类型
+	 * @param {object} file 文件信息
+	 */
+	getFileType(file) {
+		return file.isDir === 1 ? '文件夹' : file.extendName ? file.extendName : '文件'
+	},
+	/**
+	 * 获取文件分享过期状态
+	 * @param {string} time 日期
+	 * @returns {boolean} 是否过期
+	 */
+	getFileShareStatus(time) {
+		if (new Date(time).getTime() > new Date().getTime()) {
+			return false
+		} else {
+			return true
+		}
+	}
+}
+
+export default fileFunction

+ 3 - 0
src/libs/globalFunction/index.js

@@ -0,0 +1,3 @@
+import file from './file.js'
+
+export default { file }

+ 203 - 0
src/libs/map.js

@@ -0,0 +1,203 @@
+/**
+ * 存放一些静态变量、Map 等全局可用的变量
+ * @author 小鲤鱼听听
+ */
+/**
+ * 未知文件类型图标
+ */
+export const unknownImg = import('@/assets/images/myResource/file/file_unknown.png')
+console.log('unknownImg==', unknownImg.default)
+/**
+ * 文件类型图标 Map 映射
+ */
+export const fileImgMap = new Map([
+	['avi', import('@/assets/images/myResource/file/file_avi.png')],
+	['bat', import('@/assets/images/myResource/file/file_powershell.png')],
+	// ['c', import('@/assets/images/myResource/file/file_c.png')],
+	// ['c++', import('@/assets/images/myResource/file/file_c++.png')],
+	// ['c#', import('@/assets/images/myResource/file/file_c#.png')],
+	['css', import('@/assets/images/myResource/file/file_css.png')],
+	['go', import('@/assets/images/myResource/file/file_go.png')],
+	['py', import('@/assets/images/myResource/file/file_python.png')],
+	['styl', import('@/assets/images/myResource/file/file_stylus.png')],
+	['less', import('@/assets/images/myResource/file/file_less.png')],
+	['conf', import('@/assets/images/myResource/file/file_nginx.png')],
+	['m', import('@/assets/images/myResource/file/file_objective_c.png')],
+	['scss', import('@/assets/images/myResource/file/file_scss.png')],
+	['sass', import('@/assets/images/myResource/file/file_sass.png')],
+	['csv', import('@/assets/images/myResource/file/file_csv.png')],
+	['dmg', import('@/assets/images/myResource/file/file_dmg.png')],
+	['dir', import('@/assets/images/myResource/file/dir.png')],
+	['doc', import('@/assets/images/myResource/file/file_word.svg')],
+	['docx', import('@/assets/images/myResource/file/file_word.svg')],
+	['exe', import('@/assets/images/myResource/file/file_exe.png')],
+	['html', import('@/assets/images/myResource/file/file_html.png')],
+	['jar', import('@/assets/images/myResource/file/file_jar.png')],
+	['java', import('@/assets/images/myResource/file/file_java.png')],
+	['js', import('@/assets/images/myResource/file/file_js.png')],
+	['json', import('@/assets/images/myResource/file/file_json.png')],
+	['jsp', import('@/assets/images/myResource/file/file_jsp.png')],
+	['kt', import('@/assets/images/myResource/file/file_kotlin.png')],
+	['mp3', import('@/assets/images/myResource/file/file_music.png')],
+	['flac', import('@/assets/images/myResource/file/file_flac.svg')],
+	['oa', import('@/assets/images/myResource/file/file_oa.png')],
+	['open', import('@/assets/images/myResource/file/file_open.png')],
+	['pdf', import('@/assets/images/myResource/file/file_pdf.png')],
+	['php', import('@/assets/images/myResource/file/file_php.png')],
+	['ppt', import('@/assets/images/myResource/file/file_ppt.svg')],
+	['pptx', import('@/assets/images/myResource/file/file_ppt.svg')],
+	['properties', import('@/assets/images/myResource/file/file_properties.png')],
+	['r', import('@/assets/images/myResource/file/file_r.png')],
+	['rar', import('@/assets/images/myResource/file/file_rar.png')],
+	['rs', import('@/assets/images/myResource/file/file_rust.png')],
+	['rtf', import('@/assets/images/myResource/file/file_rtf.png')],
+	['sh', import('@/assets/images/myResource/file/file_shell.png')],
+	['sql', import('@/assets/images/myResource/file/file_sql.png')],
+	['svg', import('@/assets/images/myResource/file/file_svg.png')],
+	['swift', import('@/assets/images/myResource/file/file_swift.png')],
+	['ts', import('@/assets/images/myResource/file/file_typescript.png')],
+	['txt', import('@/assets/images/myResource/file/file_txt.png')],
+	['vue', import('@/assets/images/myResource/file/file_vue.png')],
+	['xls', import('@/assets/images/myResource/file/file_excel.svg')],
+	['xlsx', import('@/assets/images/myResource/file/file_excel.svg')],
+	['xml', import('@/assets/images/myResource/file/file_xml.png')],
+	['zip', import('@/assets/images/myResource/file/file_zip.png')],
+	['7z', import('@/assets/images/myResource/file/file_7z.svg')],
+	['tar', import('@/assets/images/myResource/file/file_tar.svg')],
+	['md', import('@/assets/images/myResource/file/file_markdown.png')],
+	['markdown', import('@/assets/images/myResource/file/file_markdown.png')],
+	['yaml', import('@/assets/images/myResource/file/file_yaml.png')],
+	['yml', import('@/assets/images/myResource/file/file_yaml.png')]
+])
+
+/**
+ * 支持使用 onlyoffice 打开的文件格式
+ */
+export const officeFileType = ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx']
+
+/**
+ * markdown 文件后缀
+ */
+export const markdownFileType = ['markdown', 'md']
+
+/**
+ * 文件展示模式选择列表时,所有可供选择的表格列名
+ */
+export const allColumnList = ['extendName', 'fileSize', 'uploadTime', 'deleteTime']
+
+/**
+ * 可以用代码编辑器打开的文件后缀和 解析语言、 codemirror 解析模式 mode 映射关系
+ * language:解析语言,
+ * mime: codemirror 解析模式
+ */
+export const fileSuffixCodeModeMap = new Map([
+	['c', { language: 'C', mime: 'text/x-csrc' }],
+	['c++', { language: 'C++', mime: 'text/x-c++src' }],
+	['c#', { language: 'C#', mime: 'text/x-csharp' }],
+	['m', { language: 'Objective-C', mime: 'text/x-objectivec' }],
+	['go', { language: 'GO', mime: 'text/x-go' }],
+	['kt', { language: 'Kotlin', mime: 'text/x-java' }],
+	['css', { language: 'CSS', mime: 'text/css' }],
+	['less', { language: 'Less', mime: 'text/x-less' }],
+	['php', { language: 'PHP', mime: 'text/x-php' }],
+	['bat', { language: 'PowerShell', mime: 'application/x-powershell' }],
+	['py', { language: 'Python', mime: 'text/x-python' }],
+	['properties', { language: 'Properties', mime: 'text/x-properties' }],
+	['R', { language: 'R', mime: 'text/x-rsrc' }],
+	['rs', { language: 'Rust', mime: 'text/x-rustsrc' }],
+	['scss', { language: 'Scss', mime: 'text/x-scss' }],
+	['sass', { language: 'Sass', mime: 'text/x-sass' }],
+	['sh', { language: 'Shell', mime: 'text/x-sh' }],
+	['styl', { language: 'stylus', mime: 'text/x-styl' }],
+	['xml', { language: 'XML', mime: 'text/xml' }],
+	['html', { language: 'HTML', mime: 'text/html' }],
+	['http', { language: 'HTTP', mime: 'text/http' }],
+	['conf', { language: 'Nginx', mime: 'text/x-nginx-conf' }],
+	['java', { language: 'Java', mime: 'text/x-java' }],
+	['js', { language: 'JavaScript', mime: 'text/javascript' }],
+	['ts', { language: 'Typescript', mime: 'text/typescript' }],
+	['json', { language: 'JSON', mime: 'application/json' }],
+	['jsp', { language: 'JSP', mime: 'application/x-jsp' }],
+	// ['txt', { language: '', mime: '' }],
+	['vue', { language: 'HTML', mime: 'text/html' }],
+	['sql', { language: 'SQL', mime: 'text/x-sql' }],
+	['swift', { language: 'Swift', mime: 'text/x-swift' }],
+	['yml', { language: 'YAML', mime: 'text/x-yaml' }]
+])
+
+/**
+ * codemirror 编辑器代码高亮主题列表
+ */
+export const codeMirrorThemeList = [
+	'3024-day',
+	'3024-night',
+	'abbott',
+	'abcdef',
+	'ambiance-mobile',
+	'ambiance',
+	'ayu-dark',
+	'ayu-mirage',
+	'base16-dark',
+	'base16-light',
+	'bespin',
+	'blackboard',
+	'cobalt',
+	'colorforth',
+	'darcula',
+	'dracula',
+	'duotone-dark',
+	'duotone-light',
+	'eclipse',
+	'elegant',
+	'erlang-dark',
+	'gruvbox-dark',
+	'hopscotch',
+	'icecoder',
+	'idea',
+	'isotope',
+	'juejin',
+	'lesser-dark',
+	'liquibyte',
+	'lucario',
+	'material-darker',
+	'material-ocean',
+	'material-palenight',
+	'material',
+	'mbo',
+	'mdn-like',
+	'midnight',
+	'monokai',
+	'moxer',
+	'neat',
+	'neo',
+	'night',
+	'nord',
+	'oceanic-next',
+	'panda-syntax',
+	'paraiso-dark',
+	'paraiso-light',
+	'pastel-on-dark',
+	'railscasts',
+	'rubyblue',
+	'seti',
+	'shadowfox',
+	'solarized',
+	'ssms',
+	'the-matrix',
+	'tomorrow-night',
+	'tomorrow-night-bright',
+	'tomorrow-night-eighties',
+	'ttcn',
+	'twilight',
+	'vibrant-ink',
+	'xq-dark',
+	'xq-light',
+	'yeti',
+	'yonce',
+	'zenburn'
+]
+
+/**
+ * codemirror 编辑器代码字体大小
+ */
+export const fontSizeList = [12, 14, 16, 18, 20, 22, 24, 26, 28, 30]

+ 3 - 1
src/main.js

@@ -8,13 +8,15 @@ import i18n from './locales'
 import router from './router'
 import router from './router'
 import App from './App.vue'
 import App from './App.vue'
 import './tailwind.css'
 import './tailwind.css'
-
+import uploader from 'vue-simple-uploader'
+import 'vue-simple-uploader/dist/style.css'
 const app = createApp(App)
 const app = createApp(App)
 app.use(createPinia())
 app.use(createPinia())
 app.use(router)
 app.use(router)
 app.use(Antd)
 app.use(Antd)
 app.use(i18n)
 app.use(i18n)
 app.use(snowy)
 app.use(snowy)
+app.use(uploader)
 
 
 // 挂载app
 // 挂载app
 app.mount('#app')
 app.mount('#app')

+ 9 - 2
src/snowy.js

@@ -22,6 +22,8 @@ import hljsVuePlugin from '@highlightjs/vue-plugin'
 import STable from './components/Table/index.vue'
 import STable from './components/Table/index.vue'
 import Ellipsis from './components/Ellipsis/index.vue'
 import Ellipsis from './components/Ellipsis/index.vue'
 import DragModal from './components/DragModal/index.vue'
 import DragModal from './components/DragModal/index.vue'
+import globalFunction from './libs/globalFunction/index.js'
+import fileOperationPlugins from './libs/fileOperationPlugins.js'
 
 
 export default {
 export default {
 	install(app) {
 	install(app) {
@@ -29,7 +31,12 @@ export default {
 		app.config.globalProperties.$CONFIG = config
 		app.config.globalProperties.$CONFIG = config
 		app.config.globalProperties.$TOOL = tool
 		app.config.globalProperties.$TOOL = tool
 		app.config.globalProperties.hasPerm = hasPerm
 		app.config.globalProperties.hasPerm = hasPerm
-
+		console.log('globalFunction==', globalFunction)
+		app.config.globalProperties.$file = globalFunction.file
+		for (let key in fileOperationPlugins) {
+			console.log('fileOperationPlugins==', key, fileOperationPlugins[key])
+			app.config.globalProperties[`$${key}`] = fileOperationPlugins[key]
+		}
 		// 注册常用组件
 		// 注册常用组件
 		app.component('STable', STable)
 		app.component('STable', STable)
 		app.component('Ellipsis', Ellipsis)
 		app.component('Ellipsis', Ellipsis)
@@ -45,7 +52,7 @@ export default {
 		app.use(SnowyFormDesign)
 		app.use(SnowyFormDesign)
 		// 注册代码高亮组件 (博客:https://blog.csdn.net/weixin_41897680/article/details/124925222)
 		// 注册代码高亮组件 (博客:https://blog.csdn.net/weixin_41897680/article/details/124925222)
 		// 注意:解决Vue使用highlight.js build打包发布后样式消失问题,原因是webpack在打包的时候没有把未被使用的代码打包进去,因此,在此处引用一下,看似无意义实则有用
 		// 注意:解决Vue使用highlight.js build打包发布后样式消失问题,原因是webpack在打包的时候没有把未被使用的代码打包进去,因此,在此处引用一下,看似无意义实则有用
-        hljsCommon.highlightAuto('<h1>Highlight.js has been registered successfully!</h1>').value
+		hljsCommon.highlightAuto('<h1>Highlight.js has been registered successfully!</h1>').value
 		app.use(hljsVuePlugin)
 		app.use(hljsVuePlugin)
 
 
 		// 全局代码错误捕捉
 		// 全局代码错误捕捉

+ 1 - 0
src/store/index.js

@@ -3,3 +3,4 @@ export * from './search'
 export * from './iframe'
 export * from './iframe'
 export * from './keepAlive'
 export * from './keepAlive'
 export * from './viewTags'
 export * from './viewTags'
+export * from './myResource'

+ 140 - 0
src/store/myResource.js

@@ -0,0 +1,140 @@
+import { defineStore } from 'pinia'
+import { getStorage } from '@/api/myResource/file'
+import { checkUserLoginInfo } from '@/api/myResource/user'
+import { message } from 'ant-design-vue'
+export const useMyResourceStore = defineStore('myResource', {
+	state: () => ({
+		showUploadMask: false,
+		// 表格中显示的列
+		selectedColumnList: localStorage.getItem('qiwen_selected_column_list'),
+		// 文件展示模式 0 列表模式 | 1 网格模式 | 2 时间线模式
+		fileModel: localStorage.getItem('qiwen_file_model'),
+		// 网格模式 & 时间线模式下 图标大小 单位px
+		gridSize: localStorage.getItem('qiwen_grid_size') ? Number(localStorage.getItem('qiwen_grid_size')) : 80,
+		// 批量模式下:被选中的文件列表
+		selectedFiles: [],
+		// 是否批量操作:true - 批量,false - 单文件
+		isBatchOperation: false,
+		isAdmin: -1,
+		screenWidth: document.body.clientWidth, //  屏幕宽度
+		storageValue: 0, //  文件已占用的存储空间大小
+		totalStorageValue: 0,
+		isLogin: false, //  用户登录状态
+		userInfoObj: {} //  用户信息
+	}),
+	getters: {
+		// 登录状态
+		getIsLogin: (state) => state.isLogin,
+		// 用户姓名
+		getUsername: (state) => state.userInfoObj.username,
+		// 用户ID
+		getUserId: (state) => state.userInfoObj.userId,
+		getIsAdmin: (state) => state.isAdmin,
+		// 表格显示列
+		getSelectedColumnList: (state) =>
+			state.selectedColumnList === null
+				? [] // 假设 allColumnList 是一个空数组,如果它有其他来源,请您自行补充
+				: state.selectedColumnList.split(','),
+		// 文件查看模式
+		getFileModel: (state) => (state.fileModel === null ? 0 : Number(state.fileModel)),
+		// 网格模式 & 时间线模式下 文件图标大小
+		getGridSize: (state) => state.gridSize,
+		// 剩余存储空间
+		getRemainderStorageValue: (state) => state.totalStorageValue - state.storageValue
+	},
+	actions: {
+		toggleUploadMask() {
+			this.showUploadMask = !this.showUploadMask
+		},
+		/**
+		 * 改变表格显示列
+		 * @description 表格显示列保存在 Pinia 和 cookie 中
+		 * @param {[]} data 表格需要显示的列数组
+		 */
+		changeSelectedColumnList(data) {
+			localStorage.setItem('qiwen_selected_column_list', data.toString())
+			this.selectedColumnList = data.toString()
+		},
+		/**
+		 * 改变文件展示模式
+		 * @description 文件展示模式保存在 Pinia 和 cookie 中
+		 * @param {string} data 文件展示模式
+		 */
+		changeFileModel(data) {
+			console.log('改变文件展示模式', data)
+			localStorage.setItem('qiwen_file_model', data)
+			this.fileModel = data
+		},
+		/**
+		 * 网格模式 & 时间线模式 改变文件图标大小
+		 * @description 文件图标大小保存在 Pinia 和 cookie 中
+		 * @param {string} data 文件图标大小
+		 */
+		changeGridSize(data) {
+			localStorage.setItem('qiwen_grid_size', data)
+			this.gridSize = data
+		},
+		/**
+		 * 设置批量操作模式下被选中的文件列表
+		 * @param {array} data 批量操作模式下,被选中的文件列表
+		 */
+		changeSelectedFiles(data) {
+			this.selectedFiles = data
+		},
+		/**
+		 * 设置是否批量操作
+		 * @param {boolean} data 是否批量操作
+		 */
+		changeIsBatchOperation(data) {
+			this.isBatchOperation = data
+		},
+		changeIsAdmin(data) {
+			this.isAdmin = data
+		},
+		/**
+		 * 改变屏幕宽度
+		 * @param {[]} data 屏幕宽度
+		 */
+		changeScreenWidth(data) {
+			this.screenWidth = data
+		},
+		/**
+		 * 获取文件已占用的存储空间
+		 */
+		async showStorage() {
+			try {
+				const res = await getStorage()
+				if (res.success) {
+					this.storageValue = res.data ? (res.data.storageSize === null ? 0 : Number(res.data.storageSize)) : 0
+					this.totalStorageValue = res.data
+						? res.data.totalStorageSize === null
+							? 0
+							: Number(res.data.totalStorageSize)
+						: 0
+				} else {
+					message.error(res.message)
+				}
+			} catch (error) {
+				message.error('获取存储空间失败')
+			}
+		},
+		/**
+		 * 获取用户信息
+		 */
+		async getUserInfo() {
+			try {
+				const res = await checkUserLoginInfo()
+				if (res.success) {
+					this.isLogin = true
+					this.userInfoObj = Object.assign({}, res.data.userInfoObj, data)
+				} else {
+					this.isLogin = false
+					this.userInfoObj = {}
+				}
+			} catch (error) {
+				this.isLogin = false
+				this.userInfoObj = {}
+			}
+		}
+	}
+})

+ 45 - 0
src/style/myResource/mixins.less

@@ -0,0 +1,45 @@
+// 设置滚动条样式
+// scrollbarWidth:滚动条宽度
+// trackColor:轨道颜色
+// thumbColor:滑块颜色
+.setScrollbar(@scrollbarWidth, @trackColor: #EBEEF5, @thumbColor: #909399) {
+	// 修改滚动条下面的宽度
+	&::-webkit-scrollbar {
+		width: @scrollbarWidth;
+	}
+	// 修改滚动条的下面的样式
+	&::-webkit-scrollbar-track {
+		background-color: @trackColor;
+		-webkit-border-radius: 2em;
+		-moz-border-radius: 2em;
+		border-radius: 2em;
+	}
+	// 修改滑块
+	&::-webkit-scrollbar-thumb {
+		background-color: @thumbColor;
+		-webkit-border-radius: 2em;
+		-moz-border-radius: 2em;
+		border-radius: 2em;
+	}
+}
+// 设置文字过长显示省略号
+// line:在当前行显示省略号
+.setEllipsis(@line) {
+	display: -webkit-box;
+	overflow: hidden;
+	white-space: wrap;
+	text-overflow: ellipsis;
+	-webkit-box-orient: vertical; /* -webkit-box-orient 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式 */
+	-webkit-line-clamp: @line; /* -webkit-line-clamp用来限制在一个块元素显示的文本的行数 */
+}
+.fade(@color, @percent) {
+  // 计算颜色的透明度
+  @alpha: @percent / 100;
+  // 返回 rgba 格式的颜色
+  rgba(
+    red(@color),
+    green(@color),
+    blue(@color),
+    @alpha
+  );
+}

+ 47 - 0
src/style/myResource/varibles.less

@@ -0,0 +1,47 @@
+// 主题色       
+@Primary: #409EFF;
+@Success: #67C23A;
+@Warning: #E6A23C;
+@Danger: #F56C6C;
+@Info: #909399;
+// 主题色的 Hover 颜色
+@PrimaryHover: #ecf5ff;
+@SuccessHover: #f0f9eb;
+@WarningHover: #fdf6ec;
+@DangerHover: #fdf6ec;
+@InfoHover: #e9e9eb;
+// 文字颜色
+@PrimaryText: #303133;
+@RegularText: #606266;
+@SecondaryText: #909399;
+@Placeholder: #C0C4CC;
+// 边框颜色
+@BorderBase: #DCDFE6;
+@BorderLight: #E4E7ED;
+@BorderLighter: #EBEEF5;
+@BorderExtralight: #F2F6FC;
+// 背景色
+@tabBackColor: #F5F7FA;
+// 阴影颜色
+@tabBoxShadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+@tabBoxShadowMin: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
+// 其他
+@border-base: #dcdfe6;
+@regular-text: #606266;
+@border-color-base: #dcdfe6;
+@tab-back-color: #f5f7fa;
+@primary-color: #409eff;
+@error-color: #f56c6c;
+@border-color-light : #e4e7ed;
+@text-color-secondary: #909399;
+@text-color: #606266;
+@primary-1: #ecf5ff;
+@secondary-text-color: #909399;
+@hover-color: #ecf5ff;
+@warning-color: #e6a23c;
+@success-color: #67c23a;
+@box-shadow-base: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+@text-secondary-color: #909399;
+@Warning: #E6A23C;
+@scrollbar-thumb-color: #909399;
+@scrollbar-track-color: #EBEEF5;

+ 185 - 0
src/utils/reSourceRequest.js

@@ -0,0 +1,185 @@
+/**
+ *  Copyright [2022] [https://www.xiaonuo.vip]
+ *	Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *	1.请不要删除和修改根目录下的LICENSE文件。
+ *	2.请不要删除和修改Snowy源码头部的版权声明。
+ *	3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ *	4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ *	5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ *	6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+// 统一的请求发送
+import axios from 'axios'
+import qs from 'qs'
+import { Modal, message, notification } from 'ant-design-vue'
+import sysConfig from '@/config/index'
+import tool from '@/utils/tool'
+
+// 以下这些code需要重新登录
+const reloadCodes = [401, 1011007, 1011008]
+const errorCodeMap = {
+	400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
+	401: '用户没有权限(令牌、用户名、密码错误)。',
+	403: '用户得到授权,但是访问是被禁止的。',
+	404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
+	406: '请求的格式不可得。',
+	410: '请求的资源被永久删除,且不会再得到的。',
+	422: '当创建一个对象时,发生一个验证错误。',
+	500: '服务器发生错误,请检查服务器。',
+	502: '网关错误。',
+	503: '服务不可用,服务器暂时过载或维护。',
+	504: '网关超时。'
+}
+// 定义一个重新登录弹出窗的变量
+const loginBack = ref(false)
+// 创建 axios 实例
+const service = axios.create({
+	baseURL: '/api', // api base_url
+	timeout: sysConfig.TIMEOUT // 请求超时时间
+})
+
+// HTTP request 拦截器
+service.interceptors.request.use(
+	(config) => {
+		const token = tool.data.get('TOKEN')
+		if (token) {
+			config.headers[sysConfig.TOKEN_NAME] = sysConfig.TOKEN_PREFIX + token
+		}
+		if (!sysConfig.REQUEST_CACHE && config.method === 'get') {
+			config.params = config.params || {}
+			config.params._ = new Date().getTime()
+		}
+		Object.assign(config.headers, sysConfig.HEADERS)
+		return config
+	},
+	(error) => {
+		return Promise.reject(error)
+	}
+)
+
+// 保持重新登录Modal的唯一性
+const error = () => {
+	loginBack.value = true
+	Modal.error({
+		title: '提示:',
+		okText: '重新登录',
+		content: '登录已失效, 请重新登录',
+		onOk: () => {
+			loginBack.value = false
+			tool.data.remove('TOKEN')
+			tool.data.remove('USER_INFO')
+			tool.data.remove('MENU')
+			tool.data.remove('PERMISSIONS')
+			window.location.reload()
+		}
+	})
+}
+
+// HTTP response 拦截器
+service.interceptors.response.use(
+	(response) => {
+		// 配置了blob,不处理直接返回文件流
+		if (response.config.responseType === 'blob') {
+			if (response.status === 200 || response.status === 0) {
+				return response
+			} else {
+				message.warning('文件下载失败或此文件不存在')
+				return
+			}
+		}
+		const data = response.data
+		const code = data.code
+		if (reloadCodes.includes(code)) {
+			if (!loginBack.value) {
+				error()
+			}
+			return
+		}
+		if (code !== 200 && code !== 0) {
+			const customErrorMessage = response.config.customErrorMessage
+			message.error(customErrorMessage || data.msg)
+			return Promise.reject(data)
+			// 自定义错误提示,覆盖后端返回的message
+			// 使用示例:
+			// export function customerList (data) {
+			//   return request('list', data, 'get', {
+			//     customErrorMessage: '自定义错误消息提示'
+			//   });
+			// }
+		} else {
+			// 统一成功提示
+			const responseUrl = response.config.url
+			const apiNameArray = [
+				'add',
+				'edit',
+				'delete',
+				'update',
+				'grant',
+				'reset',
+				'stop',
+				'pass',
+				'disable',
+				'enable',
+				'revoke',
+				'suspend',
+				'active',
+				'turn',
+				'adjust',
+				'reject',
+				'saveDraft'
+			]
+			apiNameArray.forEach((apiName) => {
+				if (responseUrl.includes(apiName)) {
+					message.success(data.msg)
+				}
+			})
+		}
+		return Promise.resolve(data)
+	},
+	(error) => {
+		if (error) {
+			const status = 503
+			const description = errorCodeMap[status]
+			notification.error({
+				message: '请求错误',
+				description
+			})
+			return Promise.reject(status)
+		}
+	}
+)
+
+// 适配器, 用于适配不同的请求方式
+export const baseRequest = (url, value = {}, method = 'post', options = {}) => {
+	url = sysConfig.API_URL + url
+	if (method === 'post') {
+		return service.post(url, value, options)
+	} else if (method === 'get') {
+		return service.get(url, { params: value, ...options })
+	} else if (method === 'formdata') {
+		// form-data表单提交的方式
+		return service.post(url, qs.stringify(value), {
+			headers: {
+				'Content-Type': 'multipart/form-data'
+			},
+			...options
+		})
+	} else {
+		// 其他请求方式,例如:put、delete
+		return service({
+			method: method,
+			url: url,
+			data: value,
+			...options
+		})
+	}
+}
+
+// 模块内的请求, 会自动加上模块的前缀
+export const moduleRequest =
+	(moduleUrl) =>
+	(url, ...arg) => {
+		return baseRequest(moduleUrl + url, ...arg)
+	}
+
+export default service

+ 188 - 0
src/views/myResource/common/BreadCrumb.vue

@@ -0,0 +1,188 @@
+<template>
+	<div class="breadcrumb-wrapper">
+		<div class="title">当前位置:</div>
+		<a-input
+			class="file-path-input"
+			ref="filePathInputRef"
+			placeholder="请输入路径"
+			v-model:value="inputFilePath"
+			size="small"
+			:autoFocus="true"
+			v-if="isShowInput"
+			@blur="handleInputBlurEnter"
+			@change="handleInputBlurEnter"
+		></a-input>
+		<div
+			class="breadcrumb-box"
+			:class="{ 'able-input': fileType === 0 }"
+			v-if="!isShowInput"
+			@click.self="handleClickBreadCrumbSelf"
+		>
+			<a-breadcrumb v-if="![0, 8].includes(fileType) && !['Share'].includes(route.name)">
+				<a-breadcrumb-item>{{ fileTypeMap[fileType] }}</a-breadcrumb-item>
+			</a-breadcrumb>
+			<a-breadcrumb v-else>
+				<a-breadcrumb-item v-for="(item, index) in breadCrumbList" :key="index">
+					<router-link :to="getRouteQuery(item)">{{ item.name }}</router-link>
+				</a-breadcrumb-item>
+			</a-breadcrumb>
+		</div>
+	</div>
+</template>
+
+<script setup>
+	import { ref, computed, nextTick } from 'vue'
+	import { useRoute, useRouter } from 'vue-router'
+
+	const props = defineProps({
+		// 文件类型
+		fileType: {
+			required: true,
+			type: Number
+		},
+		// 文件路径
+		filePath: {
+			require: true,
+			type: String
+		}
+	})
+
+	const route = useRoute()
+	const router = useRouter()
+
+	const fileTypeMap = {
+		1: '全部图片',
+		2: '全部文档',
+		3: '全部视频',
+		4: '全部音乐',
+		5: '其他',
+		6: '回收站',
+		// 7: '群文件',
+		// 9: '团队文件',
+		10: '正在传输',
+		11: '传输完成',
+		12: '敏感词',
+		13: '敏感词过滤记录',
+		14: '跨文件传输记录',
+		15: '企业云盘'
+	}
+
+	const isShowInput = ref(false) //  是否展示路径输入框
+	const inputFilePath = ref('') //  路径输入
+	const filePathInputRef = ref(null)
+
+	/**
+	 * 面包屑导航栏数组
+	 */
+	const breadCrumbList = computed(() => {
+		let filePath = route.query.filePath
+		let filePathList = filePath ? filePath.split('/') : []
+		let res = [] //  返回结果数组
+		let _path = [] //  存放祖先路径
+		for (let i = 0; i < filePathList.length; i++) {
+			if (filePathList[i]) {
+				_path.push(filePathList[i])
+				res.push({
+					path: _path.join('/'),
+					name: filePathList[i]
+				})
+			} else if (i === 0) {
+				//  根目录
+				filePathList[i] = ''
+				_path.push(filePathList[i])
+				res.push({
+					path: '/',
+					name:
+						props.fileType === 0
+							? '全部文件'
+							: props.fileType === 8
+							? '我的分享'
+							: route.name === 'Share'
+							? '全部分享'
+							: ''
+				})
+			}
+		}
+		return res
+	})
+
+	/**
+	 * 点击面包屑导航栏空白处
+	 */
+	const handleClickBreadCrumbSelf = () => {
+		if (props.fileType === 0) {
+			inputFilePath.value = props.filePath
+			isShowInput.value = true
+			nextTick(() => {
+				filePathInputRef.value?.focus()
+			})
+		}
+	}
+
+	/**
+	 * 路径输入框失去焦点或用户按下回车时触发
+	 */
+	const handleInputBlurEnter = () => {
+		isShowInput.value = false
+		if (inputFilePath.value !== props.filePath) {
+			const fixInputFilePath = inputFilePath.value.endsWith('/')
+				? inputFilePath.value.slice(0, -1)
+				: inputFilePath.value
+			router.push({
+				query: { filePath: `${fixInputFilePath}`, fileType: 0 }
+			})
+		}
+	}
+
+	// 获取文件参数
+	const getRouteQuery = (item) => {
+		let routeName = route.name
+		if (routeName === 'Share') {
+			// 当前是查看他人分享列表的页面
+			return { query: { filePath: item.path } }
+		} else if (props.fileType === 8) {
+			// 当前是我的已分享列表页面
+			return {
+				query: {
+					fileType: 8,
+					filePath: item.path,
+					shareBatchNum: item.path === '/' ? undefined : route.query.shareBatchNum //  当查看的是根目录,批次号置空
+				}
+			}
+		} else {
+			// 网盘页面
+			return { query: { filePath: item.path, fileType: 0 } }
+		}
+	}
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+
+	.breadcrumb-wrapper {
+		padding: 0;
+		height: 30px;
+		line-height: 30px;
+		display: flex;
+		.title,
+		:deep(.ant-breadcrumb) {
+			height: 30px;
+			line-height: 30px;
+		}
+		.file-path-input {
+			flex: 1;
+			font-size: 14px;
+		}
+		.breadcrumb-box {
+			padding: 0 8px;
+			flex: 1;
+			display: flex;
+			&.able-input {
+				cursor: pointer;
+				&:hover {
+					// background: @tabBackColor; // 假设 $tabBackColor 转换为 @tabBackColor
+				}
+			}
+		}
+	}
+</style>

+ 340 - 0
src/views/myResource/common/DragVerify.vue

@@ -0,0 +1,340 @@
+<template>
+	<!-- 滑动解锁插件 http://www.jq22.com/jquery-info22779 -->
+	<div
+		ref="dragVerify"
+		class="drag_verify"
+		:style="dragVerifyStyle"
+		@mousemove="dragMoving"
+		@mouseup="dragFinish"
+		@mouseleave="dragFinish"
+		@touchmove="dragMoving"
+		@touchend="dragFinish"
+	>
+		<div class="dv_progress_bar" ref="progressBar" :class="{ goFirst2: isOk }" :style="progressBarStyle"></div>
+		<div class="dv_text" :style="textStyle" ref="message">
+			<slot name="textBefore" v-if="$slots.textBefore"></slot>
+			{{ message }}
+			<slot name="textAfter" v-if="$slots.textAfter"></slot>
+		</div>
+
+		<div
+			class="dv_handler dv_handler_bg"
+			:class="{ goFirst: isOk }"
+			@mousedown="dragStart"
+			@touchstart="dragStart"
+			ref="handler"
+			:style="handlerStyle"
+		>
+			<component :is="handlerIcon"></component>
+		</div>
+	</div>
+</template>
+<script setup>
+	import { ref, computed, onMounted } from 'vue'
+	import { CheckCircleOutlined } from '@ant-design/icons-vue'
+
+	const props = defineProps({
+		// 是否通过
+		isPassing: {
+			type: Boolean,
+			default: false
+		},
+		// 宽度
+		width: {
+			type: Number,
+			default: 250
+		},
+		// 高度
+		height: {
+			type: Number,
+			default: 40
+		},
+		// 组件文案
+		text: {
+			type: String,
+			default: 'swiping to the right side'
+		},
+		// 成功文案
+		successText: {
+			type: String,
+			default: 'success'
+		},
+		// 背景色
+		background: {
+			type: String,
+			default: '#eee'
+		},
+		// 解锁中背景色
+		progressBarBg: {
+			type: String,
+			default: '#76c61d'
+		},
+		// 解锁成功背景色
+		completedBg: {
+			type: String,
+			default: '#76c61d'
+		},
+		circle: {
+			type: Boolean,
+			default: false
+		},
+		radius: {
+			type: String,
+			default: '4px'
+		},
+		handlerIcon: {
+			type: String
+		},
+		successIcon: {
+			type: String
+		},
+		handlerBg: {
+			type: String,
+			default: '#fff'
+		},
+		textSize: {
+			type: String,
+			default: '14px'
+		},
+		textColor: {
+			type: String,
+			default: '#333'
+		}
+	})
+
+	const emit = defineEmits(['update:isPassing', 'handlerMove', 'passcallback'])
+
+	const dragVerify = ref(null)
+	const progressBar = ref(null)
+	const messageRef = ref(null)
+	const handler = ref(null)
+
+	const isMoving = ref(false)
+	const x = ref(0)
+	const isOk = ref(false)
+
+	onMounted(() => {
+		if (dragVerify.value) {
+			dragVerify.value.style.setProperty('--textColor', props.textColor)
+			dragVerify.value.style.setProperty('--width', Math.floor(props.width / 2) + 'px')
+			dragVerify.value.style.setProperty('--pwidth', -Math.floor(props.width / 2) + 'px')
+		}
+	})
+
+	const handlerStyle = computed(() => {
+		return {
+			left: '0px',
+			width: props.height + 'px',
+			height: props.height + 'px',
+			background: props.handlerBg
+		}
+	})
+
+	const message = computed(() => {
+		return props.isPassing ? props.successText : props.text
+	})
+
+	const dragVerifyStyle = computed(() => {
+		return {
+			width: props.width + 'px',
+			height: props.height + 'px',
+			lineHeight: props.height + 'px',
+			background: props.background,
+			borderRadius: props.circle ? props.height / 2 + 'px' : props.radius
+		}
+	})
+
+	const progressBarStyle = computed(() => {
+		return {
+			background: props.progressBarBg,
+			height: props.height + 'px',
+			borderRadius: props.circle ? props.height / 2 + 'px 0 0 ' + props.height / 2 + 'px' : props.radius
+		}
+	})
+
+	const textStyle = computed(() => {
+		return {
+			height: props.height + 'px',
+			width: props.width + 'px',
+			fontSize: props.textSize
+		}
+	})
+
+	const dragStart = (e) => {
+		if (!props.isPassing) {
+			isMoving.value = true
+			const handlerEl = handler.value
+			x.value = (e.pageX || e.touches[0].pageX) - parseInt(handlerEl.style.left.replace('px', ''), 10)
+		}
+		emit('handlerMove')
+	}
+
+	const dragMoving = (e) => {
+		if (isMoving.value && !props.isPassing) {
+			let _x = (e.pageX || e.touches[0].pageX) - x.value
+			const handlerEl = handler.value
+			const progressBarEl = progressBar.value
+			if (_x > 0 && _x <= props.width - props.height) {
+				handlerEl.style.left = _x + 'px'
+				progressBarEl.style.width = _x + props.height / 2 + 'px'
+			} else if (_x > props.width - props.height) {
+				handlerEl.style.left = props.width - props.height + 'px'
+				progressBarEl.style.width = props.width - props.height / 2 + 'px'
+				passVerify()
+			}
+		}
+	}
+
+	const dragFinish = (e) => {
+		if (isMoving.value && !props.isPassing) {
+			let _x = (e.pageX || e.changedTouches[0].pageX) - x.value
+			if (_x < props.width - props.height) {
+				isOk.value = true
+				setTimeout(() => {
+					handler.value.style.left = '0'
+					progressBar.value.style.width = '0'
+					isOk.value = false
+				}, 500)
+			} else {
+				const handlerEl = handler.value
+				handlerEl.style.left = props.width - props.height + 'px'
+				progressBar.value.style.width = props.width - props.height / 2 + 'px'
+				passVerify()
+			}
+			isMoving.value = false
+		}
+	}
+
+	const passVerify = () => {
+		emit('update:isPassing', true)
+		isMoving.value = false
+		const handlerEl = handler.value
+		const messageEl = messageRef.value
+
+		if (handlerEl && handlerEl.children[0]) {
+			handlerEl.children[0].className = props.successIcon
+		}
+		if (progressBar.value) {
+			progressBar.value.style.background = props.completedBg
+		}
+		if (messageEl) {
+			messageEl.style['-webkit-text-fill-color'] = 'unset'
+			messageEl.style.animation = 'slidetounlock2 3s infinite'
+			messageEl.style.color = '#fff'
+		}
+		emit('passcallback')
+	}
+
+	const reset = () => {
+		isMoving.value = false
+		x.value = 0
+		isOk.value = false
+
+		const handlerEl = handler.value
+		const messageEl = messageRef.value
+
+		if (handlerEl) {
+			handlerEl.style.left = '0'
+			if (handlerEl.children[0]) {
+				handlerEl.children[0].className = props.handlerIcon
+			}
+		}
+		if (progressBar.value) {
+			progressBar.value.style.width = '0'
+		}
+		if (messageEl) {
+			messageEl.style['-webkit-text-fill-color'] = 'transparent'
+			messageEl.style.animation = 'slidetounlock 3s infinite'
+			messageEl.style.color = props.background
+		}
+	}
+</script>
+<style lang="less" scoped>
+	.drag_verify {
+		position: relative;
+		background-color: #e8e8e8;
+		text-align: center;
+		overflow: hidden;
+
+		.dv_handler {
+			position: absolute;
+			top: 0px;
+			left: 0px;
+			cursor: move;
+
+			i {
+				color: #666;
+				padding-left: 0;
+				font-size: 16px;
+			}
+
+			.anticon-check-circle {
+				color: #6c6;
+				margin-top: 9px;
+			}
+		}
+
+		.dv_progress_bar {
+			position: absolute;
+			height: 34px;
+			width: 0px;
+		}
+
+		.dv_text {
+			position: absolute;
+			top: 0px;
+			color: transparent;
+			-moz-user-select: none;
+			-webkit-user-select: none;
+			user-select: none;
+			-o-user-select: none;
+			-ms-user-select: none;
+			background: -webkit-gradient(
+				linear,
+				left top,
+				right top,
+				color-stop(0, var(--textColor)),
+				color-stop(0.4, var(--textColor)),
+				color-stop(0.5, #fff),
+				color-stop(0.6, var(--textColor)),
+				color-stop(1, var(--textColor))
+			);
+			-webkit-background-clip: text;
+			-webkit-text-fill-color: transparent;
+			-webkit-text-size-adjust: none;
+			animation: slidetounlock 3s infinite;
+
+			* {
+				-webkit-text-fill-color: var(--textColor);
+			}
+		}
+
+		.goFirst {
+			left: 0px !important;
+			transition: left 0.5s;
+		}
+
+		.goFirst2 {
+			width: 0px !important;
+			transition: width 0.5s;
+		}
+	}
+</style>
+<style>
+	@keyframes slidetounlock {
+		0% {
+			background-position: var(--pwidth) 0;
+		}
+		100% {
+			background-position: var(--width) 0;
+		}
+	}
+	@keyframes slidetounlock2 {
+		0% {
+			background-position: var(--pwidth) 0;
+		}
+		100% {
+			background-position: var(--pwidth) 0;
+		}
+	}
+</style>

+ 497 - 0
src/views/myResource/common/FileTable.vue

@@ -0,0 +1,497 @@
+<template>
+	<div class="file-table-wrapper">
+		<!-- 文件表格 -->
+		<a-table
+			class="file-table"
+			:class="['file-type-' + fileType, routeName === 'Share' ? 'share' : '']"
+			ref="multipleTableRef"
+			:loading="loading"
+			:data-source="fileList"
+			:columns="columns"
+			:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
+			:pagination="false"
+			:custom-row="customRow"
+			@change="handleTableChange"
+		>
+			<template #bodyCell="{ column, record, index }">
+				<template v-if="column.key === 'isDir'">
+					<video
+						style="width: 30px; max-height: 30px; cursor: pointer"
+						v-if="$file.isVideoFile(record)"
+						:src="$file.setFileImg(record)"
+					></video>
+					<img
+						:src="$file.setFileImg(record)"
+						:title="`${record.isDir || fileType == '6' ? '' : '点击预览'}`"
+						style="width: 30px; max-height: 30px; cursor: pointer"
+						@click="$file.handleFileNameClick(record, index, sortedFileList)"
+						v-else
+					/>
+				</template>
+				<template v-else-if="column.key === 'fileName'">
+					<span @click="$file.handleFileNameClick(record, index, sortedFileList)">
+						<span
+							class="file-name"
+							style="cursor: pointer"
+							:title="`${record.isDir || fileType == '6' ? '' : '点击预览'}`"
+							v-html="$file.getFileNameComplete(record, true)"
+						></span>
+						<div class="file-info" v-if="screenWidth <= 768">
+							{{ record.uploadTime }}
+							<span class="file-size">
+								{{ record.isDir === 0 ? $file.calculateFileSize(record.fileSize) : '' }}
+							</span>
+						</div>
+					</span>
+				</template>
+				<template v-else-if="column.key === 'filePath'">
+					<span
+						style="cursor: pointer"
+						title="点击跳转"
+						@click="
+							router.push({
+								query: { filePath: record.filePath, fileType: 0 }
+							})
+						"
+						>{{ record.filePath }}</span
+					>
+				</template>
+				<template v-else-if="column.key === 'extendName'">
+					<span>{{ $file.getFileType(record) }}</span>
+				</template>
+				<template v-else-if="column.key === 'fileSize'">
+					{{ record.isDir === 0 ? $file.calculateFileSize(record.fileSize) : '' }}
+				</template>
+				<template v-else-if="column.key === 'shareType'">
+					{{ record.shareType === 1 ? '私密' : '公共' }}
+				</template>
+				<template v-else-if="column.key === 'endTime'">
+					<div>
+						<WarningOutlined v-if="$file.getFileShareStatus(record.endTime)" />
+						<ClockCircleOutlined v-else />
+						{{ record.endTime }}
+					</div>
+				</template>
+				<template v-else-if="column.key === 'operation'">
+					<MoreOutlined
+						class="file-operate"
+						:class="`operate-more-${index}`"
+						@click="handleClickMore(record, $event)"
+					/>
+				</template>
+			</template>
+		</a-table>
+	</div>
+</template>
+
+<script setup>
+	import { ref, computed, watch, onMounted } from 'vue'
+	import { useRoute, useRouter } from 'vue-router'
+	import { storeToRefs } from 'pinia'
+	import { useMyResourceStore } from '@/store/myResource'
+	import {
+		WarningOutlined,
+		ClockCircleOutlined,
+		MoreOutlined,
+		PlusCircleOutlined,
+		MinusCircleOutlined
+	} from '@ant-design/icons-vue'
+	const props = defineProps({
+		// 文件类型
+		fileType: {
+			required: true,
+			type: Number
+		},
+		// 文件路径
+		filePath: {
+			required: true,
+			type: String
+		},
+		// 文件列表
+		fileList: {
+			required: true,
+			type: Array
+		},
+		// 文件加载状态
+		loading: {
+			required: true,
+			type: Boolean
+		}
+	})
+	console.log('fileList-----: ', props)
+	const emit = defineEmits(['getTableDataByType'])
+
+	const route = useRoute()
+	const router = useRouter()
+	const myResourceStore = useMyResourceStore()
+	const { proxy } = getCurrentInstance()
+
+	const multipleTableRef = ref(null)
+	const officeFileType = ref(['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx'])
+	const sortedFileList = ref([]) //  排序后的表格数据
+	const selectedRowKeys = ref([])
+	console.log('1111111111', route.query)
+	console.log('props.fileType', props.fileType)
+	const { getSelectedColumnList } = storeToRefs(myResourceStore)
+	console.log('getSelectedColumnList:---- ', getSelectedColumnList.value)
+	const selectedColumnList = computed({
+		get: () => getSelectedColumnList.value
+	})
+
+	// 路由名称
+	const routeName = computed(() => route.name)
+	// 屏幕宽度
+	const screenWidth = computed(() => myResourceStore.screenWidth)
+
+	// 定义表格列
+	const columns = computed(() => {
+		const cols = [
+			{
+				dataIndex: 'isDir',
+				key: 'isDir',
+				width: screenWidth.value <= 768 ? 40 : 56,
+				align: 'center',
+				className: 'file-icon-column'
+			},
+			{
+				title: '文件名',
+				dataIndex: 'fileName',
+				key: 'fileName',
+				sorter: (a, b) => {
+					if (a.isDir !== b.isDir) return b.isDir - a.isDir
+					return a.fileName.localeCompare(b.fileName)
+				},
+				showOverflowTooltip: true
+			}
+		]
+
+		if (![0, 8].includes(Number(route.query.fileType)) && routeName.value !== 'Share' && screenWidth.value > 768) {
+			cols.push({
+				title: props.fileType === 6 ? '原路径' : '路径',
+				dataIndex: 'filePath',
+				key: 'filePath',
+				showOverflowTooltip: true
+			})
+		}
+
+		if (selectedColumnList.value.includes('extendName') && screenWidth.value > 768) {
+			cols.push({
+				title: '类型',
+				dataIndex: 'extendName',
+				key: 'extendName',
+				width: 80,
+				sorter: (a, b) => {
+					if (a.isDir !== b.isDir) return b.isDir - a.isDir
+					return a.extendName.localeCompare(b.extendName)
+				},
+				showOverflowTooltip: true
+			})
+		}
+
+		if (selectedColumnList.value.includes('fileSize') && screenWidth.value > 768) {
+			cols.push({
+				title: '大小',
+				dataIndex: 'fileSize',
+				key: 'fileSize',
+				width: 100,
+				align: 'right',
+				sorter: (a, b) => {
+					if (a.isDir !== b.isDir) return b.isDir - a.isDir
+					return a.fileSize - b.fileSize
+				}
+			})
+		}
+
+		if (
+			selectedColumnList.value.includes('uploadTime') &&
+			![7, 8].includes(props.fileType) &&
+			!['Share'].includes(routeName.value) &&
+			screenWidth.value > 768
+		) {
+			cols.push({
+				title: '修改日期',
+				dataIndex: 'uploadTime',
+				key: 'uploadTime',
+				width: 160,
+				align: 'center',
+				sorter: (a, b) => {
+					if (a.isDir !== b.isDir) return b.isDir - a.isDir
+					return new Date(a.uploadTime).getTime() - new Date(b.uploadTime).getTime()
+				}
+			})
+		}
+
+		if (props.fileType === 6 && selectedColumnList.value.includes('deleteTime') && screenWidth.value > 768) {
+			cols.push({
+				title: '删除日期',
+				dataIndex: 'deleteTime',
+				key: 'deleteTime',
+				width: 160,
+				align: 'center',
+				sorter: (a, b) => {
+					if (a.isDir !== b.isDir) return b.isDir - a.isDir
+					return new Date(a.deleteTime).getTime() - new Date(b.deleteTime).getTime()
+				}
+			})
+		}
+
+		if (props.fileType === 8 && screenWidth.value > 768) {
+			cols.push({
+				title: '分享类型',
+				dataIndex: 'shareType',
+				key: 'shareType',
+				width: 100,
+				align: 'center'
+			})
+			cols.push({
+				title: '分享时间',
+				dataIndex: 'shareTime',
+				key: 'shareTime',
+				width: 160,
+				showOverflowTooltip: true,
+				align: 'center',
+				sorter: (a, b) => {
+					if (a.isDir !== b.isDir) return b.isDir - a.isDir
+					return new Date(a.shareTime).getTime() - new Date(b.shareTime).getTime()
+				}
+			})
+			cols.push({
+				title: '过期时间',
+				dataIndex: 'endTime',
+				key: 'endTime',
+				width: 190,
+				showOverflowTooltip: true,
+				align: 'center',
+				sorter: (a, b) => {
+					if (a.isDir !== b.isDir) return b.isDir - a.isDir
+					return new Date(a.endTime).getTime() - new Date(b.endTime).getTime()
+				}
+			})
+		}
+
+		if (screenWidth.value <= 768) {
+			cols.push({
+				title: '',
+				dataIndex: 'operation',
+				key: 'operation',
+				width: 48
+			})
+		}
+
+		return cols
+	})
+
+	watch(
+		() => props.filePath,
+		() => {
+			clearSelectedTable()
+			// Ant Design Vue Table 没有 clearSort 方法,需要手动处理排序状态
+			// multipleTableRef.value.clearSort();
+		}
+	)
+
+	watch(
+		() => props.fileType,
+		() => {
+			clearSelectedTable()
+			// multipleTableRef.value.clearSort();
+		}
+	)
+
+	watch(
+		() => props.fileList,
+		() => {
+			clearSelectedTable()
+			// multipleTableRef.value.clearSort();
+			sortedFileList.value = props.fileList
+		}
+	)
+
+	// 当表格的排序条件发生变化的时候会触发该事件
+	const handleTableChange = (pagination, filters, sorter) => {
+		// Ant Design Vue 的 sorter 返回的是当前排序的列信息,需要根据实际情况更新 sortedFileList
+		// 这里简化处理,如果需要前端排序,可以根据 sorter.field 和 sorter.order 对 fileList 进行排序
+		// sortedFileList.value = multipleTableRef.value.tableData;
+	}
+
+	// 表格某一行右键事件
+	const customRow = (record, index) => {
+		return {
+			onContextmenu: (event) => {
+				// 阻止右键事件冒泡
+				event.cancelBubble = true
+				// xs 以上的屏幕
+				if (screenWidth.value > 768) {
+					event.preventDefault()
+					// Ant Design Vue Table 没有 setCurrentRow 方法,需要手动处理选中状态
+					// multipleTableRef.value.setCurrentRow(record);
+					proxy.$openBox
+						.contextMenu({
+							selectedFile: record,
+							domEvent: event
+						})
+						.then((res) => {
+							// multipleTableRef.value.setCurrentRow(); //  取消当前选中行
+							if (res === 'confirm') {
+								emit('getTableDataByType') //  刷新文件列表
+								myResourceStore.showStorage() //  刷新存储容量
+							}
+						})
+				}
+			}
+		}
+	}
+
+	// 清空表格已选行
+	const clearSelectedTable = () => {
+		selectedRowKeys.value = []
+		myResourceStore.changeSelectedFiles([])
+		myResourceStore.changeIsBatchOperation(false)
+	}
+
+	// 表格选择项发生变化时的回调函数
+	const onSelectChange = (selectedKeys, selectedRows) => {
+		selectedRowKeys.value = selectedKeys
+		myResourceStore.changeSelectedFiles(selectedRows)
+		myResourceStore.changeIsBatchOperation(selectedRows.length !== 0)
+	}
+
+	// 更多图标点击事件
+	const handleClickMore = (record, event) => {
+		// multipleTableRef.value.setCurrentRow(record); //  选中当前行
+		proxy.$openBox
+			.contextMenu({
+				selectedFile: record,
+				domEvent: event
+			})
+			.then((res) => {
+				// multipleTableRef.value.setCurrentRow(); //  取消当前选中行
+				if (res === 'confirm') {
+					emit('getTableDataByType') //  刷新文件列表
+					myResourceStore.showStorage() //  刷新存储容量
+				}
+			})
+	}
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+	@import '@/style/myResource/mixins.less';
+
+	.file-table-wrapper {
+		margin-top: 2px;
+		border: 1px solid red;
+		height: 80vh;
+		.file-type-0 {
+			height: calc(100vh - 206px) !important;
+			:deep(.ant-table-body) {
+				height: calc(100vh - 262px) !important;
+			}
+		}
+		.file-type-6 {
+			height: calc(100vh - 211px) !important;
+			:deep(.ant-table-body) {
+				height: calc(100vh - 263px) !important;
+			}
+		}
+		.file-table.share {
+			height: calc(100vh - 109px) !important;
+			:deep(.ant-table-body) {
+				height: calc(100vh - 161px) !important;
+			}
+		}
+		.file-table {
+			width: 100% !important;
+			height: calc(100vh - 203px);
+			:deep(.ant-table-thead) {
+				th {
+					// background: @tabBackColor; // 需要在 varibles.less 中定义
+					padding: 4px 0;
+					color: @RegularText; // 需要在 varibles.less 中定义
+				}
+				.anticon-plus-circle,
+				.anticon-minus-circle {
+					margin-left: 6px;
+					cursor: pointer;
+					font-size: 16px;
+					&:hover {
+						color: @Primary; // 需要在 varibles.less 中定义
+					}
+				}
+			}
+			:deep(.ant-table-body) {
+				height: calc(100vh - 255px);
+				overflow-y: auto;
+				// setScrollbar(6px, transparent, #C0C4CC); // 需要在 mixins.less 中定义
+				td {
+					padding: 8px 0;
+					.file-name {
+						.keyword {
+							color: @Danger; // 需要在 varibles.less 中定义
+						}
+					}
+				}
+				.anticon-warning {
+					font-size: 16px;
+					color: @Warning; // 需要在 varibles.less 中定义
+				}
+				.anticon-clock-circle {
+					font-size: 16px;
+					color: @Success; // 需要在 varibles.less 中定义
+				}
+			}
+		}
+	}
+	.right-menu-list {
+		position: fixed;
+		display: flex;
+		flex-direction: column;
+		background: #fff;
+		border: 1px solid @BorderLighter; // 需要在 varibles.less 中定义
+		border-radius: 4px;
+		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+		z-index: 2;
+		padding: 4px 0;
+		color: @RegularText; // 需要在 varibles.less 中定义
+		.right-menu-item,
+		.unzip-item {
+			padding: 0 16px;
+			height: 36px;
+			line-height: 36px;
+			cursor: pointer;
+			&:hover {
+				background: @PrimaryHover; // 需要在 varibles.less 中定义
+				color: @Primary; // 需要在 varibles.less 中定义
+			}
+			i {
+				margin-right: 8px;
+			}
+		}
+		.unzip-menu-item {
+			position: relative;
+			&:hover {
+				.unzip-list {
+					display: block;
+				}
+			}
+			.unzip-list {
+				position: absolute;
+				display: none;
+				.unzip-item {
+					width: 200px;
+					// setEllipsis(1); // 需要在 mixins.less 中定义
+				}
+			}
+		}
+	}
+	.right-menu-list,
+	.unzip-list {
+		background: #fff;
+		border: 1px solid @BorderLighter; // 需要在 varibles.less 中定义
+		border-radius: 4px;
+		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+		z-index: 2;
+		padding: 4px 0;
+		color: @RegularText; // 需要在 varibles.less 中定义
+	}
+</style>

+ 18 - 0
src/views/myResource/common/MarkdownPreview.vue

@@ -0,0 +1,18 @@
+<template>
+	<!-- 类型 markdown-body 不可更改或删除,markdown 样式均在此类中生效 -->
+	<div class="markdown-body" v-html="value"></div>
+</template>
+
+<script setup>
+	// 代码高亮样式表
+	// import '_public/mavonEditor/css/tomorrow-night.css'
+	// import '_public/mavonEditor/css/github-markdown.css'
+
+	const props = defineProps({
+		// markdown 转义后的 html 字符串
+		value: {
+			type: String,
+			default: ''
+		}
+	})
+</script>

+ 270 - 0
src/views/myResource/components/EnterpriseDisk.vue

@@ -0,0 +1,270 @@
+<template>
+	<!-- 文件平铺 -->
+	<div class="file-grid-wrapper">
+		<ul class="file-list" v-loading="loading" :element-loading-text="'文件加载中……'">
+			<li
+				class="file-item"
+				v-for="(item, index) in fileListSorted"
+				:key="index"
+				:title="$file.getFileNameComplete(item)"
+				:style="`width: ${gridSize + 40}px; `"
+				:class="item.userFileId === selectedFile.userFileId ? 'active' : ''"
+				@click="$file.handleFileNameClick(item, index, fileListSorted)"
+				@contextmenu.prevent="handleContextMenu(item, index, $event)"
+			>
+				<video
+					:style="`height:${gridSize}px; width:${gridSize}px`"
+					v-if="$file.isVideoFile(item)"
+					:src="$file.setFileImg(item)"
+				></video>
+				<a-image
+					class="file-img"
+					:src="$file.setFileImg(item)"
+					:style="`width: ${gridSize}px; height: ${gridSize}px;`"
+					fit="cover"
+					v-else
+				/>
+				<div class="file-name" v-html="$file.getFileNameComplete(item, true)"></div>
+				<a-button
+					class="file-operate"
+					type="text"
+					:class="`operate-more-${index}`"
+					v-if="screenWidth <= 768"
+					@click.stop="handleClickMore(item, $event)"
+				>
+					<template #icon><MoreOutlined /></template>
+				</a-button>
+				<div
+					class="file-checked-wrapper"
+					:class="{ checked: item.checked }"
+					v-show="isBatchOperation"
+					@click.stop.self="item.checked = !item.checked"
+				>
+					<a-checkbox
+						class="file-checked"
+						v-model:checked="item.checked"
+						@click.stop="item.checked = !item.checked"
+					></a-checkbox>
+				</div>
+			</li>
+		</ul>
+	</div>
+</template>
+
+<script setup>
+	import { computed, ref, watch } from 'vue'
+	import { useMyResourceStore } from '@/store/myResource'
+	import { MoreOutlined } from '@ant-design/icons-vue'
+
+	const props = defineProps({
+		// 文件类型
+		fileType: {
+			required: true,
+			type: Number
+		},
+		// 文件路径
+		filePath: {
+			required: true,
+			type: String
+		},
+		fileList: Array, // 文件列表
+		loading: Boolean
+	})
+
+	const emit = defineEmits(['getTableDataByType'])
+
+	const myResourceStore = useMyResourceStore()
+	const fileListSorted = ref([])
+	const selectedFile = ref({})
+	const officeFileType = ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx']
+
+	// 计算属性
+	const gridSize = computed(() => myResourceStore.gridSize)
+	const isBatchOperation = computed(() => myResourceStore.isBatchOperation)
+	const selectedFileList = computed(() => {
+		return fileListSorted.value.filter((item) => item.checked)
+	})
+	const screenWidth = computed(() => myResourceStore.screenWidth)
+
+	// 监听器
+	watch(
+		() => props.fileList,
+		(newValue) => {
+			fileListSorted.value = [...newValue]
+				.sort((pre, next) => {
+					return next.isDir - pre.isDir
+				})
+				.map((item) => {
+					return {
+						...item,
+						checked: false
+					}
+				})
+		}
+	)
+
+	watch(selectedFileList, (newValue) => {
+		myResourceStore.changeSelectedFiles(newValue)
+		myResourceStore.changeIsBatchOperation(newValue.length !== 0)
+	})
+
+	// 方法
+	const handleContextMenu = (item, index, event) => {
+		// 阻止右键事件冒泡
+		event.cancelBubble = true
+		// xs 以上的屏幕
+		if (screenWidth.value > 768) {
+			selectedFile.value = item
+			if (!isBatchOperation.value) {
+				event.preventDefault()
+				$openBox
+					.contextMenu({
+						selectedFile: item,
+						domEvent: event
+					})
+					.then((res) => {
+						selectedFile.value = {}
+						if (res === 'confirm') {
+							emit('getTableDataByType') // 刷新文件列表
+							myResourceStore.showStorage() // 刷新存储容量
+						}
+					})
+			}
+		}
+	}
+
+	const handleClickMore = (item, event) => {
+		selectedFile.value = item
+		if (!isBatchOperation.value) {
+			event.preventDefault()
+			$openBox
+				.contextMenu({
+					selectedFile: item,
+					domEvent: event
+				})
+				.then((res) => {
+					selectedFile.value = {}
+					if (res === 'confirm') {
+						emit('getTableDataByType') // 刷新文件列表
+						myResourceStore.showStorage() // 刷新存储容量
+					}
+				})
+		}
+	}
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+	@import '@/style/myResource/mixins.less';
+
+	.file-grid-wrapper {
+		border-top: 1px solid @border-color-base;
+		.file-list {
+			height: calc(100vh - 206px);
+			overflow-y: auto;
+			display: flex;
+			flex-wrap: wrap;
+			align-items: flex-start;
+			align-content: flex-start;
+			list-style: none;
+			.setScrollbar(6px, transparent, #C0C4CC);
+			.file-item {
+				margin: 0 16px 16px 0;
+				position: relative;
+				padding: 8px;
+				text-align: center;
+				cursor: pointer;
+				z-index: 1;
+				&:hover {
+					background: @tab-back-color;
+					.file-name {
+						font-weight: 550;
+					}
+				}
+				.file-name {
+					margin-top: 8px;
+					height: 44px;
+					line-height: 22px;
+					font-size: 12px;
+					word-break: break-all;
+					.setEllipsis(2);
+					:deep(.keyword) {
+						color: @error-color;
+					}
+				}
+				.file-checked-wrapper {
+					position: absolute;
+					top: 0;
+					left: 0;
+					z-index: 2;
+					background: rgba(245, 247, 250, 0.5);
+					width: 100%;
+					height: 100%;
+					.file-checked {
+						position: absolute;
+						top: 16px;
+						left: 24px;
+					}
+				}
+				.file-checked-wrapper.checked {
+					background: rgba(245, 247, 250, 0);
+				}
+			}
+			.file-item.active {
+				background: @tab-back-color;
+			}
+		}
+		.right-menu-list {
+			position: fixed;
+			display: flex;
+			flex-direction: column;
+			background: #fff;
+			border: 1px solid @border-color-light;
+			border-radius: 4px;
+			box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+			z-index: 2;
+			padding: 4px 0;
+			color: @text-color;
+			.right-menu-item,
+			.unzip-item {
+				padding: 0 16px;
+				height: 36px;
+				line-height: 36px;
+				cursor: pointer;
+				&:hover {
+					background: @primary-1;
+					color: @primary-color;
+				}
+				i {
+					margin-right: 8px;
+				}
+			}
+			.unzip-menu-item {
+				position: relative;
+				&:hover {
+					.unzip-list {
+						display: block;
+					}
+				}
+				.unzip-list {
+					position: absolute;
+					display: none;
+					.unzip-item {
+						width: 200px;
+						.setEllipsis(1);
+					}
+				}
+			}
+		}
+		.right-menu-list,
+		.unzip-list {
+			background: #fff;
+			border: 1px solid @border-color-light;
+			border-radius: 4px;
+			box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+			z-index: 2;
+			padding: 4px 0;
+			color: @text-color;
+		}
+	}
+</style>

+ 299 - 0
src/views/myResource/components/FileGrid.vue

@@ -0,0 +1,299 @@
+<template>
+	<!-- 文件平铺 -->
+	<div class="file-grid-wrapper">
+		<a-spin :spinning="loading" tip="文件加载中……">
+			<ul class="file-list">
+				<li
+					class="file-item"
+					v-for="(item, index) in fileListSorted"
+					:key="index"
+					:title="$file.getFileNameComplete(item)"
+					:style="`width: ${gridSize + 40}px; `"
+					:class="item.userFileId === selectedFile.userFileId ? 'active' : ''"
+					@click="$file.handleFileNameClick(item, index, fileListSorted)"
+					@contextmenu.prevent="handleContextMenu(item, index, $event)"
+				>
+					<video
+						:style="`height:${gridSize}px; width:${gridSize}px`"
+						v-if="$file.isVideoFile(item)"
+						:src="$file.setFileImg(item)"
+					></video>
+					<a-image
+						class="file-img"
+						:src="$file.setFileImg(item)"
+						:style="`width: ${gridSize}px; height: ${gridSize}px;`"
+						fit="cover"
+						v-else
+					/>
+					<div class="file-name" v-html="$file.getFileNameComplete(item, true)"></div>
+					<MoreOutlined
+						class="file-operate"
+						:class="`operate-more-${index}`"
+						v-if="screenWidth <= 768"
+						@click.stop="handleClickMore(item, $event)"
+					/>
+					<div
+						class="file-checked-wrapper"
+						:class="{ checked: item.checked }"
+						v-show="isBatchOperation"
+						@click.stop.self="item.checked = !item.checked"
+					>
+						<a-checkbox
+							class="file-checked"
+							v-model:checked="item.checked"
+							@click.stop="item.checked = !item.checked"
+						></a-checkbox>
+					</div>
+				</li>
+			</ul>
+		</a-spin>
+	</div>
+</template>
+
+<script setup>
+	import { ref, computed, watch, inject } from 'vue'
+	import { useMyResourceStore } from '@/store/myResource'
+	import { MoreOutlined } from '@ant-design/icons-vue'
+
+	const props = defineProps({
+		// 文件类型
+		fileType: {
+			required: true,
+			type: Number
+		},
+		// 文件路径
+		filePath: {
+			required: true,
+			type: String
+		},
+		fileList: Array, // 文件列表
+		loading: Boolean
+	})
+
+	const emit = defineEmits(['getTableDataByType'])
+
+	const myResourceStore = useMyResourceStore()
+
+	const fileListSorted = ref([])
+	const officeFileType = ref(['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx'])
+	const selectedFile = ref({})
+
+	// 注入全局方法,如果这些方法是通过 provide 提供的
+	const $file = inject('$file')
+	const $openBox = inject('$openBox')
+
+	// 计算属性
+	const gridSize = computed(() => myResourceStore.gridSize)
+	const isBatchOperation = computed(() => myResourceStore.isBatchOperation)
+	const screenWidth = computed(() => myResourceStore.screenWidth)
+
+	// 文件平铺模式 排序-文件夹在前
+	watch(
+		() => props.fileList,
+		(newValue) => {
+			fileListSorted.value = [...newValue]
+				.sort((pre, next) => {
+					return next.isDir - pre.isDir
+				})
+				.map((item) => {
+					return {
+						...item,
+						checked: false
+					}
+				})
+		},
+		{ immediate: true }
+	)
+
+	// 批量操作模式 - 监听被选中的文件
+	watch(
+		() => fileListSorted.value.filter((item) => item.checked),
+		(newValue) => {
+			myResourceStore.changeSelectedFiles(newValue) // 假设 Pinia store 有 changeSelectedFiles 方法
+			myResourceStore.changeIsBatchOperation(newValue.length !== 0) // 假设 Pinia store 有 changeIsBatchOperation 方法
+		}
+	)
+
+	/**
+	 * 文件鼠标右键事件
+	 * @param {object} item 文件信息
+	 * @param {number} index 文件索引
+	 * @param {object} event 鼠标事件信息
+	 */
+	const handleContextMenu = (item, index, event) => {
+		// 阻止右键事件冒泡
+		event.cancelBubble = true
+		// xs 以上的屏幕
+		if (screenWidth.value > 768) {
+			selectedFile.value = item
+			if (!isBatchOperation.value) {
+				event.preventDefault()
+				$openBox
+					.contextMenu({
+						selectedFile: item,
+						domEvent: event
+					})
+					.then((res) => {
+						selectedFile.value = {}
+						if (res === 'confirm') {
+							emit('getTableDataByType') // 刷新文件列表
+							myResourceStore.showStorage() // 刷新存储容量,假设在 common 模块
+						}
+					})
+			}
+		}
+	}
+
+	/**
+	 * 更多图标点击事件
+	 * @description 打开右键菜单
+	 * @param {object} item 当前行数据
+	 * @param {object} event 当前右键元素
+	 */
+	const handleClickMore = (item, event) => {
+		selectedFile.value = item
+		if (!isBatchOperation.value) {
+			event.preventDefault()
+			$openBox
+				.contextMenu({
+					selectedFile: item,
+					domEvent: event
+				})
+				.then((res) => {
+					selectedFile.value = {}
+					if (res === 'confirm') {
+						emit('getTableDataByType') // 刷新文件列表
+						myResourceStore.showStorage() // 刷新存储容量,假设在 common 模块
+					}
+				})
+		}
+	}
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+	@import '@/style/myResource/mixins.less';
+	.file-grid-wrapper {
+		border-top: 1px solid @BorderBase;
+
+		.file-list {
+			height: calc(100vh - 206px);
+			overflow-y: auto;
+			display: flex;
+			flex-wrap: wrap;
+			align-items: flex-start;
+			align-content: flex-start;
+			list-style: none;
+			.setScrollbar(6px, transparent, #C0C4CC); // 假设 setScrollbar 混合宏已转换为 less
+
+			.file-item {
+				margin: 0 16px 16px 0;
+				position: relative;
+				padding: 8px;
+				text-align: center;
+				cursor: pointer;
+				z-index: 1;
+
+				&:hover {
+					background: @tabBackColor;
+
+					.file-name {
+						font-weight: 550;
+					}
+				}
+
+				.file-name {
+					margin-top: 8px;
+					height: 44px;
+					line-height: 22px;
+					font-size: 12px;
+					word-break: break-all;
+					.setEllipsis(2); // 假设 setEllipsis 混合宏已转换为 less
+
+					:deep(.keyword) {
+						// 使用 :deep() 或 ::v-deep 替代 >>>
+						color: @Danger;
+					}
+				}
+
+				.file-checked-wrapper {
+					position: absolute;
+					top: 0;
+					left: 0;
+					z-index: 2;
+					background: rgba(245, 247, 250, 0.5);
+					width: 100%;
+					height: 100%;
+
+					.file-checked {
+						position: absolute;
+						top: 16px;
+						left: 24px;
+					}
+				}
+
+				.file-checked-wrapper.checked {
+					background: rgba(245, 247, 250, 0);
+				}
+			}
+			.file-item.active {
+				background: @tabBackColor;
+			}
+		}
+
+		.right-menu-list {
+			position: fixed;
+			display: flex;
+			flex-direction: column;
+			background: #fff;
+			border: 1px solid @BorderLighter;
+			border-radius: 4px;
+			box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+			z-index: 2;
+			padding: 4px 0;
+			color: @RegularText;
+
+			.right-menu-item,
+			.unzip-item {
+				padding: 0 16px;
+				height: 36px;
+				line-height: 36px;
+				cursor: pointer;
+				&:hover {
+					background: @PrimaryHover;
+					color: @Primary;
+				}
+				i {
+					margin-right: 8px;
+				}
+			}
+
+			.unzip-menu-item {
+				position: relative;
+				&:hover {
+					.unzip-list {
+						display: block;
+					}
+				}
+				.unzip-list {
+					position: absolute;
+					display: none;
+					.unzip-item {
+						width: 200px;
+						.setEllipsis(1);
+					}
+				}
+			}
+		}
+		.right-menu-list,
+		.unzip-list {
+			background: #fff;
+			border: 1px solid @BorderLighter;
+			border-radius: 4px;
+			box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+			z-index: 2;
+			padding: 4px 0;
+			color: @RegularText;
+		}
+	}
+</style>

+ 173 - 0
src/views/myResource/components/FileTimeLine.vue

@@ -0,0 +1,173 @@
+<template>
+	<!-- 时间线模式 -->
+	<div class="image-timeline-wrapper">
+		<div class="radio">
+			排序:
+			<a-radio-group v-model:value="reverse">
+				<a-radio :value="true">倒序</a-radio>
+				<a-radio :value="false">正序</a-radio>
+			</a-radio-group>
+		</div>
+		<a-timeline class="image-timeline-list" :reverse="reverse" v-if="imageTimelineData.length">
+			<a-timeline-item
+				class="image-timeline-item"
+				v-for="(item, index) in imageTimelineData"
+				:key="index"
+				:label="item.uploadDate"
+				color="blue"
+				position="top"
+			>
+				<ul class="image-list">
+					<li
+						class="image-item"
+						v-for="(image, imageIndex) in item.imageList"
+						:key="`${index}-${imageIndex}`"
+						:style="`width: ${gridSize + 40}px; `"
+						@click="$file.handleImgPreview(imageIndex, {}, item.imageList)"
+						@contextmenu.prevent="handleContextMenu(item, imageIndex, $event)"
+					>
+						<img
+							class="image"
+							:src="$file.getMinImgStream(image)"
+							:alt="$file.getFileNameComplete(image)"
+							:style="`width: ${gridSize}px; height: ${gridSize}px;`"
+						/>
+						<div class="image-name" v-html="$file.getFileNameComplete(image, true)"></div>
+					</li>
+				</ul>
+			</a-timeline-item>
+		</a-timeline>
+	</div>
+</template>
+
+<script setup>
+	import { ref, computed, inject } from 'vue'
+	import { useMyResourceStore } from '@/store/myResource'
+
+	const props = defineProps({
+		// 文件列表
+		fileList: {
+			required: true,
+			type: Array
+		}
+	})
+
+	const emit = defineEmits(['getTableDataByType'])
+
+	const myResourceStore = useMyResourceStore()
+
+	const reverse = ref(true)
+	const selectedFile = ref({})
+
+	// 注入全局方法,如果这些方法是通过 provide 提供的
+	const $file = inject('$file')
+	const $openBox = inject('$openBox')
+
+	// 按年-月-日分组排序
+	const imageTimelineData = computed(() => {
+		let res = []
+		// 去重,获取返回的所有日期年-月-日
+		let uploadTimeSet = new Set(props.fileList.map((item) => item.uploadTime.split(' ')[0]))
+		let uploadDate = [...uploadTimeSet]
+		uploadDate.sort((a, b) => {
+			const dateA = new Date(a)
+			const dateB = new Date(b)
+			return dateB - dateA
+		})
+		// 分组
+		uploadDate.forEach((element) => {
+			res.push({
+				uploadDate: element,
+				imageList: props.fileList.filter((item) => item.uploadTime.split(' ')[0] === element) // 过滤
+			})
+		})
+		return res
+	})
+
+	const gridSize = computed(() => myResourceStore.gridSize)
+	const screenWidth = computed(() => myResourceStore.screenWidth)
+	const isBatchOperation = computed(() => myResourceStore.isBatchOperation)
+
+	/**
+	 * 文件鼠标右键事件
+	 * @param {object} item 文件信息
+	 * @param {number} index 文件索引
+	 * @param {object} event 鼠标事件信息
+	 */
+	const handleContextMenu = (item, index, event) => {
+		// 阻止右键事件冒泡
+		event.cancelBubble = true
+		// xs 以上的屏幕
+		if (screenWidth.value > 768) {
+			selectedFile.value = item
+			if (!isBatchOperation.value) {
+				event.preventDefault()
+				$openBox
+					.contextMenu({
+						selectedFile: item,
+						domEvent: event
+					})
+					.then((res) => {
+						selectedFile.value = {}
+						if (res === 'confirm') {
+							emit('getTableDataByType') // 刷新文件列表
+							myResourceStore.showStorage() // 刷新存储容量
+						}
+					})
+			}
+		}
+	}
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+	@import '@/style/myResource/mixins.less';
+
+	.image-timeline-wrapper {
+		margin-top: 20px;
+		height: calc(100vh - 215px);
+		overflow-y: auto;
+		.setScrollbar(6px, transparent, #C0C4CC); // 假设 setScrollbar 混合宏已转换为 less
+
+		.image-timeline-list {
+			margin-top: 10px;
+
+			.image-timeline-item {
+				.image-list {
+					display: flex;
+					flex-wrap: wrap;
+					list-style: none;
+
+					.image-item {
+						margin: 0 16px 16px 0;
+						padding: 8px;
+						text-align: center;
+						cursor: pointer;
+
+						&:hover {
+							background: @tabBackColor;
+
+							.file-name {
+								font-weight: 550;
+							}
+						}
+
+						.image-name {
+							margin-top: 8px;
+							height: 44px;
+							line-height: 22px;
+							font-size: 12px;
+							word-break: break-all;
+							.setEllipsis(2); // 假设 setEllipsis 混合宏已转换为 less
+
+							:deep(.keyword) {
+								// 使用 :deep() 或 ::v-deep 替代 >>>
+								color: @Danger;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+</style>

+ 585 - 0
src/views/myResource/components/OperationMenu.vue

@@ -0,0 +1,585 @@
+<template>
+	<div class="operation-menu-wrapper" :class="'file-type-' + fileType" ref="operationMenuRef">
+		<a-space
+			class="create-operate-group"
+			v-if="
+				(!selectedFiles.length || !isBatchOperation) &&
+				fileType !== 8 &&
+				fileType !== 6 &&
+				fileType !== 12 &&
+				fileType !== 13
+			"
+		>
+			<a-dropdown class="upload-drop">
+				<a-button type="primary" id="uploadFileId" class="btnsy"> <UploadOutlined />上传<DownOutlined /> </a-button>
+				<template #overlay>
+					<a-menu class="scwj">
+						<a-menu-item @click="handleUploadFileBtnClick(1)">上传文件</a-menu-item>
+						<a-menu-item @click="handleUploadFileBtnClick(2)" v-if="isandiord">上传文件夹</a-menu-item>
+						<a-menu-item @click="handleUploadFileBtnClick(3)" title="截图粘贴或拖拽上传" :disabled="screenWidth <= 520"
+							>拖拽上传</a-menu-item
+						>
+					</a-menu>
+				</template>
+			</a-dropdown>
+
+			<a-dropdown class="create-drop">
+				<a-button type="primary" id="uploadFileId" class="btnsy"> <PlusOutlined />新建<DownOutlined /> </a-button>
+				<template #overlay>
+					<a-menu>
+						<a-menu-item @click="handleClickAddFolderBtn">
+							<div class="img-text-wrapper"><img :src="dirImg" /> 新建文件夹</div>
+						</a-menu-item>
+					</a-menu>
+				</template>
+			</a-dropdown>
+		</a-space>
+		<div class="batch-operate-group">
+			<a-space v-if="isBatchOperation">
+				<a-button type="primary" v-if="selectedFiles.length" @click="handleBatchDeleteBtnClick">
+					<DeleteOutlined />批量删除
+				</a-button>
+				<a-button
+					type="primary"
+					v-if="selectedFiles.length && !fileType && fileType !== 6"
+					@click="handleBatchMoveBtnClick"
+				>
+					批量移动
+				</a-button>
+				<a-button type="primary" v-if="selectedFiles.length && fileType !== 6" @click="handleBatchDownloadBtnClick">
+					<DownloadOutlined />批量下载
+				</a-button>
+				<a-button
+					type="primary"
+					v-if="selectedFiles.length && fileType !== 6 && $route.name !== 'Share'"
+					@click="handleBatchShareBtnClick"
+				>
+					<ShareAltOutlined />批量分享
+				</a-button>
+			</a-space>
+		</div>
+
+		<!-- 全局搜索文件 -->
+		<a-input-search
+			v-if="fileType === 0"
+			class="select-file-input"
+			v-model:value="searchFile.fileName"
+			placeholder="搜索您的文件"
+			allow-clear
+			@change="handleSearchInputChange"
+			@clear="$emit('getTableDataByType')"
+			@search="handleSearchInputChange"
+		>
+		</a-input-search>
+
+		<!-- 批量操作 -->
+		<CheckCircleOutlined
+			class="batch-icon"
+			:class="isBatchOperation ? 'active' : ''"
+			:title="isBatchOperation ? '取消批量操作' : '批量操作'"
+			v-if="fileModel === 1 && fileType !== 8"
+			@click="handleBatchOperationChange()"
+		/>
+		<ReloadOutlined class="refresh-icon" title="刷新文件列表" @click="$emit('getTableDataByType')" />
+		<a-divider type="vertical"></a-divider>
+		<template v-if="screenWidth > 768">
+			<!-- 文件展示模式 -->
+			<UnorderedListOutlined
+				class="model-icon"
+				:class="{ active: fileGroupLable === 0 }"
+				title="列表模式"
+				@click="handleFileDisplayModelChange(0)"
+			/>
+			<AppstoreOutlined
+				class="model-icon"
+				:class="{ active: fileGroupLable === 1 }"
+				title="网格模式"
+				@click="handleFileDisplayModelChange(1)"
+			/>
+			<CalendarOutlined
+				class="model-icon"
+				title="时间线模式"
+				v-if="fileType === 1"
+				@click="handleFileDisplayModelChange(2)"
+			/>
+			<a-divider type="vertical"></a-divider>
+		</template>
+
+		<!-- 操作栏收纳 -->
+		<a-popover
+			v-model:open="operatePopoverVisible"
+			placement="bottom"
+			:trigger="screenWidth <= 768 ? 'click' : 'hover'"
+		>
+			<template #content>
+				<!-- 选择表格列 -->
+				<SelectColumn></SelectColumn>
+				<!-- 文件展示模式 -->
+				<div class="change-file-model" v-if="screenWidth <= 768">
+					<div class="title">查看模式</div>
+					<a-radio-group v-model:value="fileGroupLable" button-style="solid" @change="handleFileDisplayModelChange">
+						<a-radio-button :value="0" class="btn1"> <UnorderedListOutlined /> 列表 </a-radio-button>
+						<a-radio-button :value="1"> <AppstoreOutlined /> 网格 </a-radio-button>
+						<a-radio-button :value="2" v-if="fileType === 1"> <CalendarOutlined /> 时间线 </a-radio-button>
+					</a-radio-group>
+				</div>
+				<template v-if="fileGroupLable === 1 || fileGroupLable === 2">
+					<a-divider class="split-line"></a-divider>
+					<!-- 图标大小调整 -->
+					<div class="change-grid-size">
+						<div class="title">调整图标大小</div>
+						<a-slider
+							v-model:value="gridSize"
+							:min="20"
+							:max="150"
+							:step="10"
+							:tip-formatter="formatTooltip"
+						></a-slider>
+					</div>
+				</template>
+			</template>
+			<SettingOutlined class="setting-icon" @click="operatePopoverVisible = !operatePopoverVisible" />
+		</a-popover>
+
+		<!-- 多选文件下载,页面隐藏 -->
+		<a target="_blank" :href="batchDownloadLink" ref="batchDownloadRef"></a>
+	</div>
+</template>
+
+<script setup>
+	import { ref, computed, watch, onMounted } from 'vue'
+	import { useRoute } from 'vue-router'
+	import { message } from 'ant-design-vue'
+	import config from '@/config/reSource'
+	import { storeToRefs } from 'pinia'
+	const { proxy } = getCurrentInstance()
+	console.log('proxy===', proxy)
+
+	// 定义组件可以触发的事件
+	const emit = defineEmits(['getTableDataByType', 'getSearchFileList'])
+
+	import {
+		UploadOutlined,
+		PlusOutlined,
+		DeleteOutlined,
+		DownloadOutlined,
+		ShareAltOutlined,
+		CheckCircleOutlined,
+		ReloadOutlined,
+		UnorderedListOutlined,
+		AppstoreOutlined,
+		CalendarOutlined,
+		SettingOutlined,
+		DownOutlined
+	} from '@ant-design/icons-vue'
+	import SelectColumn from './SelectColumn.vue'
+	import { useMyResourceStore } from '@/store/myResource'
+
+	const route = useRoute()
+	const myResourceStore = useMyResourceStore()
+
+	const props = defineProps({
+		// 文件类型
+		fileType: {
+			required: true,
+			type: Number
+		},
+		// 文件路径
+		filePath: {
+			required: true,
+			type: String
+		}
+	})
+
+	// 文件搜索数据
+	const searchFile = ref({
+		fileName: ''
+	})
+	const operatePopoverVisible = ref(false) //  收纳栏是否显示
+	const fileGroupLable = ref(0) //  文件展示模式
+	const dirImg = ref(new URL('@/assets/images/myResource/file/dir.png', import.meta.url).href)
+	// const wordImg = ref(require('@/assets/images/file/file_word.svg'))
+	// const excelImg = ref(require('@/assets/images/file/file_excel.svg'))
+	// const pptImg = ref(require('@/assets/images/file/file_ppt.svg'))
+	const isandiord = ref(true)
+	const batchDownloadRef = ref(null)
+
+	// 上传文件组件参数
+	const uploadFileParams = computed(() => {
+		return {
+			filePath: props.filePath,
+			isDir: 0
+		}
+	})
+
+	// 文件查看模式 0 列表模式 1 网格模式 2 时间线模式
+	const { getFileModel } = storeToRefs(myResourceStore)
+	const fileModel = computed({
+		get: () => getFileModel.value
+	})
+	// 图标大小
+	const gridSize = computed({
+		get: () => myResourceStore.gridSize,
+		set: (val) => myResourceStore.changeGridSize(val)
+	})
+
+	// 被选中的文件列表
+	const selectedFiles = computed(() => myResourceStore.selectedFiles)
+
+	// 是否批量操作
+	const isBatchOperation = computed(() => myResourceStore.isBatchOperation)
+
+	// 屏幕宽度
+	const screenWidth = computed(() => myResourceStore.screenWidth)
+
+	// 批量下载文件链接
+	const batchDownloadLink = computed(() => {
+		console.log('批量下载')
+		console.log(
+			`${config.baseContext}/filetransfer/batchDownloadFile?userFileIds=${selectedFiles.value
+				.map((item) => item.userFileId)
+				.join(',')}`
+		)
+		return `${config.baseContext}/filetransfer/batchDownloadFile?userFileIds=${selectedFiles.value
+			.map((item) => item.userFileId)
+			.join(',')}`
+	})
+
+	// 显示拖拽上传文件遮罩
+	watch(
+		() => myResourceStore.showUploadMask,
+		() => {
+			handleUploadFileBtnClick(3)
+		}
+	)
+	console.log('fileType', props.fileType)
+	watch(
+		() => props.fileType,
+		(newValue, oldValue) => {
+			if (oldValue === 1 && fileModel.value === 2) {
+				console.log('时间线模式')
+				myResourceStore.changeFileModel(0)
+				fileGroupLable.value = 0
+			}
+		}
+	)
+
+	/**
+	 * 监听收纳栏状态
+	 * @description 打开时,body 添加点击事件的监听
+	 */
+	watch(operatePopoverVisible, (newValue) => {
+		if (newValue === true) {
+			document.body.addEventListener('click', closeOperatePopover)
+		} else {
+			document.body.removeEventListener('click', closeOperatePopover)
+		}
+	})
+
+	onMounted(() => {
+		fileGroupLable.value = fileModel.value
+		getisshow()
+	})
+
+	const getisshow = () => {
+		let isAndroid = navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('ios') > -1
+		if (isAndroid) {
+			isandiord.value = false
+		}
+	}
+
+	/**
+	 * 新建文件夹按钮点击事件
+	 * @description 调用新建文件夹服务,并在弹窗确认回调事件中刷新文件列表
+	 */
+	const handleClickAddFolderBtn = () => {
+		proxy.$openDialog
+			.addFolder({
+				filePath: route.query.filePath || '/'
+			})
+			.then((res) => {
+				if (res === 'confirm') {
+					emit('getTableDataByType')
+				}
+			})
+	}
+
+	/**
+	 * 新建 office 文件
+	 * @description 调用新建 office 文件服务,并在弹窗确认回调事件中刷新文件列表
+	 * @param {string} 文件扩展名 docx xlsx pptx
+	 */
+	const handleCreateFile = (extendName) => {
+		proxy.$openDialog
+			.addFile({
+				extendName: extendName
+			})
+			.then((res) => {
+				if (res === 'confirm') {
+					emit('getTableDataByType')
+				}
+			})
+	}
+
+	/**
+	 * 上传文件按钮点击事件
+	 * @description 通过Bus通信,开启全局上传文件流程
+	 * @param {boolean} uploadWay 上传方式 0-文件上传 1-文件夹上传 2-粘贴图片或拖拽上传
+	 */
+	const handleUploadFileBtnClick = async (uploadWay) => {
+		proxy.$openDialog.authWeChat().then((res) => {
+			switch (res) {
+				case 'confirm': {
+					// window.$common.goAccount('/settings/account')
+					break
+				}
+				case 'go': {
+					console.log('uploadWay', uploadWay)
+					console.log('params', uploadFileParams.value)
+					console.log('uploadWay', uploadWay)
+					proxy.$openBox.uploadFile({
+						params: uploadFileParams.value,
+						uploadWay,
+						serviceEl: proxy, // 使用proxy替代this
+						callType: 1 //  callType 调用此服务的方式:1 - 顶部栏,2 - 右键菜单
+					})
+					break
+				}
+			}
+		})
+	}
+
+	/**
+	 * 批量删除按钮点击事件
+	 * @description 区分 删除到回收站中 | 在回收站中彻底删除,调用相应的删除文件接口
+	 */
+	const handleBatchDeleteBtnClick = () => {
+		proxy.$openDialog
+			.deleteFile({
+				isBatchOperation: true,
+				fileInfo: selectedFiles.value,
+				deleteMode: props.fileType === 6 ? 2 : 1 //  删除模式:1-删除到回收站 2-彻底删除
+			})
+			.then((res) => {
+				if (res === 'confirm') {
+					emit('getTableDataByType')
+					myResourceStore.showStorage()
+				}
+			})
+	}
+
+	/**
+	 * 批量移动按钮点击事件
+	 */
+	const handleBatchMoveBtnClick = () => {
+		if (selectedFiles.value.length > 0) {
+			proxy.$openDialog
+				.moveFile({
+					isBatchOperation: true,
+					fileInfo: selectedFiles.value
+				})
+				.then((res) => {
+					if (res === 'confirm') {
+						emit('getTableDataByType')
+					}
+				})
+		} else {
+			message.warning('请先勾选文件')
+		}
+	}
+
+	/**
+	 * 批量分享按钮点击事件
+	 */
+	const handleBatchShareBtnClick = () => {
+		proxy.$openDialog.shareFile({
+			fileInfo: selectedFiles.value.map((item) => {
+				return {
+					userFileId: item.userFileId
+				}
+			})
+		})
+	}
+
+	/**
+	 * 打包批量下载按钮点击事件
+	 */
+	const handleBatchDownloadBtnClick = () => {
+		console.log(222, window.location.host)
+		// var cookies = window.$common.getCookies(window.config.tokenKeyName)
+		var url = `${
+			'http://' + window.location.host + config.baseContext
+		}/filetransfer/batchDownloadFile?userFileIds=${selectedFiles.value.map((item) => item.userFileId).join(',')}`
+		console.log(222, url)
+
+		var newname1 = '.zip'
+		let isios = navigator.userAgent.indexOf('iPhone') > -1
+		let isAndroid = navigator.userAgent.indexOf('Android') > -1
+		if (isios) {
+			console.log(1231231)
+			window.webkit.messageHandlers.Download.postMessage({
+				params: {
+					url,
+					cookies,
+					newname1
+				}
+			})
+		} else if (isAndroid) {
+			window.Download.downList(url, cookies, newname1)
+		}
+		batchDownloadRef.value.click()
+	}
+
+	/**
+	 * 搜索输入框搜索事件
+	 * @param {string} value 搜索内容
+	 */
+	const handleSearchInputChange = (value) => {
+		if (value === '') {
+			emit('getTableDataByType')
+		} else {
+			emit('getSearchFileList', value)
+		}
+	}
+
+	/**
+	 * 网格模式下,批量操作状态切换
+	 */
+	const handleBatchOperationChange = () => {
+		myResourceStore.changeIsBatchOperation(!isBatchOperation.value)
+	}
+
+	/**
+	 * 文件查看模式切换
+	 * @param {number} label 0 列表 1 网格 2 时间线
+	 */
+	const handleFileDisplayModelChange = (label) => {
+		fileGroupLable.value = label
+		// 关闭右键菜单事件
+		proxy.$openBox.contextMenu.close()
+		myResourceStore.changeFileModel(label)
+		handleSearchInputChange(searchFile.value.fileName)
+		// 关闭收纳栏
+		operatePopoverVisible.value = false
+	}
+
+	/**
+	 * 格式化图标大小显示
+	 * @param {number} val 改变后的数值
+	 */
+	const formatTooltip = (val) => {
+		return `${val}px`
+	}
+
+	/**
+	 * 关闭收纳栏
+	 */
+	const closeOperatePopover = (event) => {
+		// 检查点击事件是否发生在设置图标上,如果是,则不关闭 Popover
+		if (!event.target.closest('.setting-icon')) {
+			operatePopoverVisible.value = false
+		}
+	}
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+
+	.operation-menu-wrapper.file-type-6 {
+		margin: 8px 0;
+		justify-content: flex-end;
+	}
+	.ant-btn-primary {
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+	.btnsy {
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+	.operation-menu-wrapper {
+		padding: 16px 0 0 0;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		.create-operate-group {
+			.upload-drop {
+				float: left;
+				.ant-btn {
+					border-radius: 4px 0 0 4px;
+				}
+			}
+			.create-drop {
+				float: left;
+			}
+		}
+		.batch-operate-group {
+			flex: 1;
+		}
+		.select-file-input {
+			margin-right: 8px;
+			width: 250px;
+			.anticon-search {
+				cursor: pointer;
+				font-size: 16px;
+				&:hover {
+					color: @primary-color;
+				}
+			}
+		}
+		.batch-icon,
+		.model-icon {
+			margin-right: 8px;
+			&:last-of-type {
+				margin-right: 0;
+			}
+		}
+		.model-icon.active {
+			color: @primary-color;
+		}
+		.refresh-icon,
+		.batch-icon,
+		.model-icon,
+		.setting-icon {
+			font-size: 20px;
+			cursor: pointer;
+			color: @secondary-text-color;
+			&:hover {
+				color: @primary-color;
+			}
+		}
+		.batch-icon.active {
+			color: @primary-color;
+		}
+	}
+	.split-line {
+		margin: 8px 0;
+	}
+	.img-text-wrapper {
+		display: flex;
+		align-items: center;
+		img {
+			margin-right: 4px;
+			height: 24px;
+		}
+	}
+	.operation-menu-wrapper .refresh-icon:hover,
+	.operation-menu-wrapper .batch-icon:hover,
+	.operation-menu-wrapper .model-icon:hover,
+	.operation-menu-wrapper .setting-icon:hover {
+		color: #29175b;
+	}
+	.ant-dropdown-menu-item:focus,
+	.ant-dropdown-menu-item:not(.ant-dropdown-menu-item-disabled):hover {
+		background-color: #ecf5ff;
+		color: #29175b;
+	}
+	.operation-menu-wrapper .model-icon.active {
+		color: #29175b;
+	}
+	.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
+		color: #fff;
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+</style>

+ 121 - 0
src/views/myResource/components/SelectColumn.vue

@@ -0,0 +1,121 @@
+<template>
+	<div class="select-column">
+		<div class="text" @click="handleSetShowColumnBtnClick">
+			<setting-outlined class="icon1" />
+			设置显示字段
+		</div>
+		<!-- 对话框 当点击"设置显示列"按钮时弹出对话框 -->
+		<a-modal
+			title="设置表格列显隐"
+			width="700px"
+			:getContainer="() => document.body"
+			v-model:visible="dialogVisible"
+			@ok="dialogOk"
+			@cancel="dialogVisible = false"
+		>
+			<!-- 多选框组件 勾选需要在表格中显示的列 -->
+			<a-checkbox-group v-model:value="selectedColumn">
+				<a-checkbox v-for="item in columnOptions" :key="item.value" :value="item.value">{{ item.label }}</a-checkbox>
+			</a-checkbox-group>
+			<template #footer>
+				<a-button @click="dialogVisible = false">取 消</a-button>
+				<a-button type="primary" @click="dialogOk()">确 定</a-button>
+			</template>
+		</a-modal>
+	</div>
+</template>
+
+<script setup>
+	import { ref, onMounted } from 'vue'
+	import { useMyResourceStore } from '@/store/myResource'
+
+	import { SettingOutlined } from '@ant-design/icons-vue'
+
+	const myResourceStore = useMyResourceStore()
+
+	const dialogVisible = ref(false)
+	const selectedColumn = ref([]) // 被选中的表格需要显示的列
+	const columnOptions = ref([
+		{
+			value: 'extendName',
+			label: '类型'
+		},
+		{
+			value: 'fileSize',
+			label: '大小'
+		},
+		{
+			value: 'uploadTime',
+			label: '修改日期'
+		},
+		{
+			value: 'deleteTime',
+			label: '删除日期'
+		}
+	])
+
+	/**
+	 * 设置显示列按钮点击事件
+	 * @description 获取 Vuex 中存储的表格显示列
+	 *              并打开对话框
+	 */
+	const handleSetShowColumnBtnClick = () => {
+		selectedColumn.value = myResourceStore.selectedColumnList
+		dialogVisible.value = true
+	}
+
+	/**
+	 * 对话框 确定按钮点击事件
+	 * @description 通过提交 mutation 更新表格显示列
+	 *              并关闭对话框
+	 */
+	const dialogOk = () => {
+		myResourceStore.changeSelectedColumnList(selectedColumn.value)
+		dialogVisible.value = false
+	}
+
+	onMounted(() => {
+		// 可以在这里进行一些初始化操作,例如从本地存储加载列设置
+	})
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+
+	.select-column {
+		.text {
+			padding-top: 8px;
+			cursor: pointer;
+			&:hover {
+				color: #29175b;
+			}
+		}
+	}
+
+	:deep(.ant-checkbox-wrapper-checked) {
+		.ant-checkbox-inner {
+			background-color: #29175b;
+			border-color: #29175b;
+		}
+		.ant-checkbox + span {
+			color: #29175b;
+		}
+	}
+
+	:deep(.ant-btn-primary) {
+		color: #fff;
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+
+	.icon1:hover {
+		color: #29175b;
+	}
+
+	:deep(.ant-btn:focus),
+	:deep(.ant-btn:hover) {
+		color: white;
+		border-color: #a093c3;
+		background-color: #a093c3;
+	}
+</style>

+ 155 - 0
src/views/myResource/components/SensitiveList.vue

@@ -0,0 +1,155 @@
+<template>
+	<div>
+		<a-button-group class="create-operate-group">
+			<a-button size="small" type="primary" id="uploadFileId" class="btnsy" @click="add">新增</a-button>
+		</a-button-group>
+
+		<a-table :columns="columns" :data-source="tableData" :pagination="false" style="width: 100%">
+			<template #bodyCell="{ column, record }">
+				<template v-if="column.key === 'action'">
+					<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
+					<a-button type="link" size="small" @click="handleDelete(record)" style="color: #f56c6c">删除</a-button>
+				</template>
+			</template>
+		</a-table>
+	</div>
+</template>
+
+<script setup>
+	import { ref, onMounted } from 'vue'
+	import { Modal, message } from 'ant-design-vue'
+	import { addSensitiveList, updateSensitiveList, delSensitiveList } from '@/api/myResource/file' // 假设路径为 '@/api/myResource/file'
+
+	const props = defineProps({
+		tableData: {
+			required: true,
+			type: Array
+		}
+	})
+
+	const emit = defineEmits(['addSensitiveSuccess'])
+
+	const columns = [
+		{
+			title: '敏感词',
+			dataIndex: 'word',
+			key: 'word'
+		},
+		{
+			title: '操作',
+			key: 'action',
+			fixed: 'right',
+			width: 150
+		}
+	]
+
+	const add = () => {
+		Modal.confirm({
+			title: '请输入敏感词',
+			content: '敏感词不能为空',
+			maskClosable: true,
+			onOk(inputValue) {
+				if (!inputValue) {
+					message.error('敏感词不能为空')
+					return Promise.reject()
+				}
+				return addSensitiveList({
+					word: inputValue
+				})
+					.then((res) => {
+						if (res.code === 0) {
+							message.success('新增敏感词成功')
+							emit('addSensitiveSuccess')
+						} else {
+							message.error(res.message || '新增失败')
+							return Promise.reject()
+						}
+					})
+					.catch((err) => {
+						message.error(JSON.stringify(err))
+						return Promise.reject()
+					})
+			},
+			onCancel() {
+				// 用户取消操作
+			}
+		})
+	}
+
+	const handleEdit = (row) => {
+		Modal.confirm({
+			title: '请输入敏感词',
+			content: '敏感词不能为空',
+			maskClosable: true,
+			defaultInputValue: row.word,
+			onOk(inputValue) {
+				if (!inputValue) {
+					message.error('敏感词不能为空')
+					return Promise.reject()
+				}
+				return updateSensitiveList({
+					id: row.id,
+					word: inputValue
+				})
+					.then((res) => {
+						if (res.code === 0) {
+							message.success('修改敏感词成功')
+							emit('addSensitiveSuccess')
+						} else {
+							message.error(res.message || '修改失败')
+							return Promise.reject()
+						}
+					})
+					.catch((err) => {
+						message.error(JSON.stringify(err))
+						return Promise.reject()
+					})
+			},
+			onCancel() {
+				// 用户取消操作
+			}
+		})
+	}
+
+	const handleDelete = (row) => {
+		Modal.confirm({
+			title: '确定删除该敏感词吗?',
+			onOk() {
+				return delSensitiveList(row.id)
+					.then((res) => {
+						if (res.code === 0) {
+							message.success('删除敏感词成功')
+							emit('addSensitiveSuccess')
+						} else {
+							message.error(res.message || '删除失败')
+							return Promise.reject()
+						}
+					})
+					.catch((err) => {
+						message.error(JSON.stringify(err))
+						return Promise.reject()
+					})
+			},
+			onCancel() {
+				// 用户取消操作
+			}
+		})
+	}
+
+	onMounted(() => {
+		// 初始化逻辑
+	})
+</script>
+
+<style lang="less" scoped>
+	.btnsy {
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+
+	:deep(.ant-modal-content) {
+		.sensitive-prompt-input {
+			max-width: calc(100% + 0.1px);
+		}
+	}
+</style>

+ 142 - 0
src/views/myResource/components/SensitiveListManage.vue

@@ -0,0 +1,142 @@
+<template>
+	<a-table :data-source="tableData" :columns="columns" :pagination="false" style="width: 100%">
+		<template #bodyCell="{ column, record }">
+			<template v-if="column.key === 'operation'">
+				<a
+					@click="geta1(record)"
+					ref="downloadLink"
+					target="_blank"
+					:style="{ fontSize: '12px', color: '#409EFF', marginRight: '10px' }"
+					:href="$file?.getDownloadFilePath2(record)"
+					:download="record.fileName"
+				>
+					<DownloadOutlined /> 下载
+				</a>
+				<a-button type="link" danger size="small" @click="handleDelete(record)">删除</a-button>
+			</template>
+		</template>
+	</a-table>
+</template>
+
+<script setup>
+	import { ref, onMounted, getCurrentInstance } from 'vue'
+	import { delSensitiveRecord } from '@/api/myResource/file.js' // 假设路径调整为相对路径
+	import { message } from 'ant-design-vue'
+	import { DownloadOutlined } from '@ant-design/icons-vue'
+
+	const props = defineProps({
+		tableData: {
+			type: Array,
+			required: true
+		}
+	})
+
+	const emit = defineEmits(['delRecords'])
+
+	const newname = ref('')
+	const downloadLink = ref(null)
+
+	// 获取全局属性,例如 $common 和 $config
+	const instance = getCurrentInstance()
+	const globalProperties = instance?.appContext.config.globalProperties
+
+	const columns = [
+		{ title: '文件名', dataIndex: 'fileName', key: 'fileName' },
+		{ title: '敏感词', dataIndex: 'sensitiveWord', key: 'sensitiveWord' },
+		{ title: '过滤时间', dataIndex: 'createTime', key: 'createTime' },
+		{ title: '文件所属者', dataIndex: 'createUser', key: 'createUser' },
+		{ title: '操作', key: 'operation', fixed: 'right', width: 150 }
+	]
+
+	const handleDelete = (row) => {
+		delSensitiveRecord(row.id).then(
+			(res) => {
+				if (res.code === 0) {
+					message.success('删除过滤记录成功')
+					emit('delRecords')
+				} else {
+					message.error(res.message || '删除失败')
+				}
+			},
+			(err) => {
+				message.error(`删除失败: ${JSON.stringify(err)}`)
+			}
+		)
+	}
+
+	const geta1 = (record) => {
+		const src = globalProperties?.$file?.getDownloadFilePath2(record)
+		console.log('src', src)
+
+		const cookies = globalProperties?.$common?.getCookies(globalProperties?.$config?.tokenKeyName)
+
+		let name1 = ''
+		let name2 = ''
+
+		if (downloadLink.value && downloadLink.value.download) {
+			const parts = downloadLink.value.download.split('.')
+			name1 = parts[0]
+			name2 = parts.length > 1 ? parts[parts.length - 1] : ''
+		} else {
+			// Fallback if ref is not available or download attribute is missing
+			name1 = record.fileName.split('.')[0]
+			name2 =
+				record.fileName.split('.').length > 1 ? record.fileName.split('.')[record.fileName.split('.').length - 1] : ''
+		}
+
+		if (name2) {
+			newname.value = name1 + '.' + name2
+		} else {
+			newname.value = name1 + '.' + 'zip'
+		}
+
+		console.log('this.$config.baseContext', window.location.host)
+		const newname1 = newname.value
+
+		let url = ''
+		if (downloadLink.value && downloadLink.value.href) {
+			const hrefParts = downloadLink.value.href.split('/api')
+			if (hrefParts.length > 1) {
+				url = `${
+					'http://' + window.location.host + globalProperties?.$config?.baseContext + hrefParts[1] + '&' + cookies
+				}`
+			} else {
+				// Fallback if /api is not in href
+				url = `${'http://' + window.location.host + globalProperties?.$config?.baseContext + src + '&' + cookies}`
+			}
+		} else {
+			// Fallback if ref is not available
+			url = `${'http://' + window.location.host + globalProperties?.$config?.baseContext + src + '&' + cookies}`
+		}
+
+		let isios = navigator.userAgent.indexOf('iPhone') > -1
+		let isAndroid = navigator.userAgent.indexOf('Android') > -1
+
+		console.log('url', url)
+		console.log('cookies', cookies)
+		console.log('newname1', newname1)
+
+		if (isios) {
+			window.webkit.messageHandlers.Download.postMessage({
+				params: {
+					url,
+					cookies,
+					newname1
+				}
+			})
+		} else if (isAndroid) {
+			window.Download.downList(url, cookies, newname1)
+		}
+	}
+
+	// onMounted(() => {
+	//     // 如果需要,可以在这里执行一些初始化逻辑
+	// });
+</script>
+
+<style lang="less" scoped>
+	.btnsy {
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+</style>

+ 58 - 0
src/views/myResource/components/TransferList.vue

@@ -0,0 +1,58 @@
+<template>
+	<a-table :data-source="tableData" :columns="columns" :pagination="false" style="width: 100%">
+		<template #bodyCell="{ column, record }">
+			<template v-if="column.key === 'ferryType'">
+				<span>{{ record.ferryType === 0 ? '复制' : '移动' }}</span>
+			</template>
+			<template v-else-if="column.key === 'operation'">
+				<a-button type="link" danger size="small" @click="handleDelete(record)">删除</a-button>
+			</template>
+		</template>
+	</a-table>
+</template>
+
+<script setup>
+	import { ferryRecordDelete } from '@/api/myResource/file' // 假设路径调整为相对路径
+	import { message } from 'ant-design-vue'
+
+	const props = defineProps({
+		tableData: {
+			type: Array,
+			required: true
+		}
+	})
+
+	const emit = defineEmits(['delTransferList'])
+
+	const columns = [
+		{ title: '文件名', dataIndex: 'fileName', key: 'fileName' },
+		{ title: '文件类型', dataIndex: 'fileType', key: 'fileType' },
+		{ title: '文件大小', dataIndex: 'fileSize', key: 'fileSize' },
+		{ title: '传输类型', dataIndex: 'ferryType', key: 'ferryType' },
+		{ title: '时间', dataIndex: 'createTime', key: 'createTime' },
+		{ title: '操作', key: 'operation', fixed: 'right', width: 100 }
+	]
+
+	const handleDelete = (row) => {
+		ferryRecordDelete(row.recordId).then(
+			(res) => {
+				if (res.code === 0) {
+					message.success('删除记录成功')
+					emit('delTransferList')
+				} else {
+					message.error(res.message || '删除失败')
+				}
+			},
+			(err) => {
+				message.error(`删除失败: ${JSON.stringify(err)}`)
+			}
+		)
+	}
+</script>
+
+<style lang="less" scoped>
+	.btnsy {
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+</style>

+ 785 - 0
src/views/myResource/file/box/audioPreview/BoxMask.vue

@@ -0,0 +1,785 @@
+<template>
+	<a-modal
+		class="audio-preview-wrapper"
+		v-model:visible="visible"
+		:footer="null"
+		:closable="false"
+		:maskClosable="false"
+		:width="'100%'"
+		:bodyStyle="{ padding: 0 }"
+		:centered="true"
+	>
+		<img class="audio-background" :src="musicImgUrl" alt="背景图" />
+
+		<!-- 右上角操作 -->
+		<div class="operate-box">
+			<a-tooltip placement="bottom">
+				<template #title>
+					<div style="line-height: 2">
+						操作提示: <br />
+						1. 按 Esc 键可退出查看;<br />
+						2. 支持键盘控制:<br />
+						&nbsp;&nbsp;空格 - 暂停/播放<br />
+						&nbsp;&nbsp;左方向键 - 播放上一个<br />
+						&nbsp;&nbsp;右方向键 - 播放下一个<br />
+						&nbsp;&nbsp;上方向键 - 音量调大<br />
+						&nbsp;&nbsp;下方向键 - 音量减小<br />
+					</div>
+				</template>
+				<question-circle-outlined class="tip-icon" />
+			</a-tooltip>
+			<close-outlined class="close-icon" title="关闭(Escape)" @click="handleClosePreview" />
+		</div>
+
+		<audio
+			ref="audioRef"
+			:src="activeFileObj.fileUrl"
+			controls
+			style="position: fixed; top: 0; left: 0; display: none"
+			@loadedmetadata="handleLoadedmetadata"
+			@timeupdate="handleTimeUpdate"
+			@ended="handleChangeAudioIndex('next')"
+		/>
+
+		<div class="audio-list-wrapper">
+			<!-- 音频列表 -->
+			<ul class="audio-list">
+				<li class="audio-list-header">
+					<span class="name">音频名称</span>
+					<span class="audio-size">大小</span>
+					<span class="path">路径</span>
+				</li>
+				<div class="audio-list-body">
+					<li
+						v-for="(item, index) in audioList"
+						:key="index"
+						class="audio-item"
+						:class="{ active: activeIndex === index }"
+						:title="isPlay ? '暂停' : '播放'"
+						@click="handleChangeAudioIndex('manual', index)"
+					>
+						<span class="name">
+							<span class="sequence" v-show="activeIndex !== index">
+								{{ index + 1 }}
+							</span>
+							<img class="wave" :src="activePlayIcon" alt="波浪动图" v-show="activeIndex === index && isPlay" />
+							<i class="no-wave el-icon-s-data" v-show="activeIndex === index && !isPlay"></i>
+							<span class="text">{{ item.fileName }}.{{ item.extendName }}</span>
+						</span>
+						<i class="play-icon iconfont icon-icon-7" v-show="activeIndex === index && !isPlay"></i>
+						<i class="pause-icon iconfont icon-icon-3" v-show="activeIndex === index && isPlay"></i>
+						<a class="download" :href="$file.getDownloadFilePath(item)" target="_blank" title="下载">
+							<i class="download-icon el-icon-download"></i>
+						</a>
+						<i
+							class="share-icon el-icon-share"
+							title="分享"
+							@click.stop="
+								$openDialog.shareFile({
+									fileInfo: [
+										{
+											userFileId: item.userFileId
+										}
+									]
+								})
+							"
+						></i>
+						<span class="audio-size">{{ $file.calculateFileSize(item.fileSize) }}</span>
+						<span class="path">{{ item.filePath }}</span>
+					</li>
+				</div>
+			</ul>
+
+			<!-- 歌曲图片和歌词 -->
+			<div class="img-and-lyrics">
+				<img class="audio-img" :src="musicImgUrl" alt="歌曲图片" />
+				<div class="audio-name">{{ activeFileObj.fileName }}.{{ activeFileObj.extendName }}</div>
+				<div class="album-artist" v-show="audioInfo.artist">歌手:{{ audioInfo.artist }}</div>
+				<div class="album-name" v-if="audioInfo.album">专辑:{{ audioInfo.album }}</div>
+				<ul class="lyrics-list" ref="lyricsListRef" :class="{ one: lyricsList.length === 1 }" v-if="lyricsList.length">
+					<li
+						class="lyrics-item"
+						ref="lyricsLineRef"
+						v-for="(item, index) in lyricsList"
+						:key="index"
+						:class="{
+							active: currentLyricsLineIndex === index
+						}"
+						@click="handleChangeProgress(transferTimeToSeconds(item.time))"
+					>
+						{{ item.text }}
+					</li>
+				</ul>
+			</div>
+		</div>
+
+		<!-- 底部音乐控件 -->
+		<div class="control-wrapper">
+			<div class="control-left">
+				<step-backward-outlined
+					class="operate-icon"
+					title="上一个(按左方向键)"
+					@click="handleChangeAudioIndex('pre')"
+				/>
+				<play-circle-outlined
+					v-if="!isPlay"
+					class="operate-icon"
+					title="播放(按空格键)"
+					@click="handleClickPlayIcon"
+				/>
+				<pause-circle-outlined v-else class="operate-icon" title="暂停(按空格键)" @click="handleClickPauseIcon" />
+				<step-forward-outlined
+					class="operate-icon"
+					title="下一个(按右方向键)"
+					@click="handleChangeAudioIndex('next')"
+				/>
+				<a-slider
+					class="progress-bar control-item"
+					v-model:value="currentTime"
+					:step="progressStep"
+					:max="audioInfo.duration"
+					:tooltip-visible="true"
+					:tip-formatter="(val) => transferSecondsToTime(val)"
+					@mousedown="isDrop = true"
+					@mouseup="isDrop = false"
+					@change="handleChangeProgress"
+				/>
+				<span class="time control-item">
+					{{ transferSecondsToTime(currentTime) }} / {{ transferSecondsToTime(audioInfo.duration) }}
+				</span>
+			</div>
+
+			<div class="control-right">
+				<i
+					class="operate-icon cycle-type iconfont"
+					:class="cycleTypeMap[String(cycleType)].icon"
+					:title="cycleTypeMap[String(cycleType)].text"
+					@click="handleChangeCycleType"
+				></i>
+				<a
+					class="operate-icon download-link"
+					:href="$file.getDownloadFilePath(activeFileObj)"
+					target="_blank"
+					title="下载"
+				>
+					<i class="download-icon el-icon-download"></i>
+				</a>
+				<i
+					class="operate-icon share-icon el-icon-share"
+					title="分享"
+					@click.stop="
+						$openDialog.shareFile({
+							fileInfo: [
+								{
+									userFileId: item.userFileId
+								}
+							]
+						})
+					"
+				></i>
+				<i
+					class="operate-icon volume-icon control-item iconfont"
+					:class="volume === 0 ? 'icon-jingyin01' : 'icon-yinliang101'"
+					@click="handleClickVolumeIcon"
+				></i>
+				<el-slider
+					class="volume-bar control-item"
+					v-model="volume"
+					:step="0.01"
+					:max="1"
+					:format-tooltip="(val) => Math.floor(val * 100)"
+					height="100px"
+					title="可按上下方向键调节音量"
+					@input="handleChangeVolumeBar"
+				></el-slider>
+			</div>
+		</div>
+	</a-modal>
+</template>
+
+<script setup>
+	import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
+	import { message } from 'ant-design-vue'
+	import {
+		QuestionCircleOutlined,
+		CloseOutlined,
+		StepBackwardOutlined,
+		StepForwardOutlined,
+		PlayCircleOutlined,
+		PauseCircleOutlined
+	} from '@ant-design/icons-vue'
+	import { getFileDetail } from '@/api/myResource/file'
+	import { Base64 } from 'js-base64'
+
+	const props = defineProps({
+		visible: {
+			type: Boolean,
+			default: false
+		},
+		audioList: {
+			type: Array,
+			default: () => []
+		},
+		defaultIndex: {
+			type: Number,
+			default: 0
+		},
+		callback: {
+			type: Function,
+			default: () => {}
+		}
+	})
+
+	const emit = defineEmits(['update:visible'])
+
+	const visible = ref(props.visible)
+	const activeIndex = ref(0)
+	const isPlay = ref(false)
+	const currentTime = ref(0)
+	const isDrop = ref(false)
+	const volume = ref(0)
+	const audioInfo = ref({})
+	const lyricsList = ref([])
+	const currentLyricsLineIndex = ref(0)
+
+	const activeFileObj = computed(() => {
+		const res = props.audioList.length ? props.audioList[activeIndex.value] : {}
+		return res
+	})
+
+	const audioElement = computed(() => {
+		return refs.audioRef
+	})
+
+	const musicImgUrl = computed(() => {
+		return audioInfo.value.albumImage
+			? `data:image/jpeg;base64,${audioInfo.value.albumImage}`
+			: require('_a/images/file/file_music.png')
+	})
+
+	const progressStep = computed(() => {
+		return audioInfo.value.duration / 100
+	})
+
+	watch(
+		() => props.visible,
+		(newValue) => {
+			visible.value = newValue
+			if (newValue) {
+				activeIndex.value = props.defaultIndex
+				getFileDetailData()
+				document.addEventListener('keyup', handleAddKeyupEvent)
+			} else {
+				document.removeEventListener('keyup', handleAddKeyupEvent)
+			}
+		}
+	)
+
+	watch(activeIndex, () => {
+		getFileDetailData()
+	})
+
+	function handleAddKeyupEvent(event) {
+		switch (event.code) {
+			// 关闭预览
+			case 'Escape': {
+				handleClosePreview()
+				break
+			}
+			// 切换到上一个
+			case 'ArrowLeft': {
+				handleChangeAudioIndex('pre')
+				break
+			}
+			// 切换到下一个
+			case 'ArrowRight': {
+				handleChangeAudioIndex('next')
+				break
+			}
+			// 音量调大
+			case 'ArrowUp': {
+				volume.value = volume.value === 1 ? 1 : volume.value + 0.1
+				volume.value = Number(volume.value.toFixed(1))
+				handleChangeVolumeBar(volume.value)
+				break
+			}
+			// 音量调小
+			case 'ArrowDown': {
+				volume.value = volume.value === 0 ? 0 : volume.value - 0.1
+				volume.value = Number(volume.value.toFixed(1))
+				handleChangeVolumeBar(volume.value)
+				break
+			}
+			// 暂停/播放
+			case 'Space': {
+				handleChangeAudioIndex('manual', activeIndex.value)
+				break
+			}
+		}
+	}
+
+	function getFileDetailData() {
+		handleClickPauseIcon()
+		loading.value = true
+		getFileDetail({ userFileId: activeFileObj.value.userFileId })
+			.then((res) => {
+				loading.value = false
+				if (res.success) {
+					audioInfo.value = {
+						...res.data.music,
+						duration: res.data.music.trackLength
+					}
+					// Base64 解码为 lrc 格式的歌词文件
+					let lyricsStr = Base64.decode(audioInfo.value.lyrics)
+					if (lyricsStr.includes('[offset:0]')) {
+						// 有歌词,从标志位 [offset:0] 下一行开始截取
+						lyricsStr = lyricsStr.split('[offset:0]\n')[1]
+					}
+					lyricsList.value = lyricsStr
+						.split('\n')
+						.map((item) => {
+							const line = item.split('[')[1].split(']')
+							return {
+								time: line[0], //  当前行歌词开始播放的秒数
+								text: line[1] //  当前歌词文本
+							}
+						})
+						.filter((item) => item.text !== '')
+					lyricsList.value = lyricsList.value.map((item, index) => {
+						return {
+							...item,
+							// 当前行歌词起始秒数
+							startSeconds: transferTimeToSeconds(item.time),
+							// 当前行歌词结束秒数
+							endSeconds:
+								index < lyricsList.value.length - 1
+									? transferTimeToSeconds(lyricsList.value[index + 1].time)
+									: audioInfo.value.duration
+						}
+					})
+					// 当切换完歌曲时,歌词重新滚动到顶部
+					refs.lyricsListRef.scrollTo({
+						top: 0,
+						behavior: 'smooth'
+					})
+					currentLyricsLineIndex.value = 0
+				}
+			})
+			.catch(() => {
+				loading.value = false
+			})
+	}
+
+	function handleLoadedmetadata(event) {
+		const audioDom = event.target
+		volume.value = audioDom.volume || 0.5
+		currentTime.value = audioDom.currentTime
+		handleClickPlayIcon()
+	}
+
+	function transferSecondsToTime(duration) {
+		const hour = Math.floor(duration / 3600)
+		const minutes = Math.floor(duration / 60)
+		const seconds = Math.ceil(duration % 60)
+		return `${hour < 10 ? `0${hour}` : hour}:${minutes < 10 ? `0${minutes}` : minutes}:${
+			seconds < 10 ? `0${seconds}` : seconds
+		}`
+	}
+
+	function transferTimeToSeconds(time) {
+		const timeList = time.split('.')[0].split(':')
+		return Number(timeList[1]) + Number(timeList[0]) * 60
+	}
+
+	function handleTimeUpdate(event) {
+		// 如果正在拖拽进度滑块,函数结束,不计算当前时间
+		if (isDrop.value) return
+		currentTime.value = event.target.currentTime
+		if (lyricsList.value.length) {
+			// 遍历歌词,当前秒对应的歌词整行添加高亮效果
+			lyricsList.value.forEach((item, index) => {
+				if (
+					item.startSeconds <= currentTime.value &&
+					currentTime.value < item.endSeconds &&
+					currentLyricsLineIndex.value !== index
+				) {
+					// 确定高亮歌词行索引
+					currentLyricsLineIndex.value = index
+					// 使高亮歌词行永远保持在第二行
+					if (currentLyricsLineIndex.value > 2) {
+						// 平滑滚动
+						refs.lyricsListRef.scrollTo({
+							top: refs.lyricsLineRef[index].clientHeight * (index - 2),
+							behavior: 'smooth'
+						})
+					}
+				}
+			})
+		}
+	}
+
+	function handleChangeProgress(progress) {
+		audioElement.value.currentTime = progress
+		isDrop.value = false
+	}
+
+	function handleChangeCycleType() {
+		if (cycleType.value === 3) {
+			cycleType.value = 1
+		} else if (cycleType.value >= 1) {
+			cycleType.value++
+		}
+	}
+
+	function handleClickPlayIcon() {
+		isPlay.value = true
+		audioElement.value.play()
+	}
+
+	function handleClickPauseIcon() {
+		isPlay.value = false
+		audioElement.value.pause()
+	}
+
+	function handleChangeAudioIndex(type, index) {
+		// 如果当前手动切换
+		if (type === 'manual') {
+			if (activeIndex.value === index) {
+				if (isPlay.value) {
+					handleClickPauseIcon()
+				} else {
+					handleClickPlayIcon()
+				}
+			} else {
+				activeIndex.value = index
+			}
+		} else {
+			handleClickPauseIcon()
+			// 判断当前循环播放类型
+			switch (cycleType.value) {
+				case 3: {
+					let activeIndex = 0
+					do {
+						activeIndex = Math.floor(Math.random() * (props.audioList.length - 1)) + 1
+					} while (activeIndex === activeIndex.value)
+					activeIndex.value = activeIndex
+					break
+				}
+				default: {
+					if (type === 'pre') {
+						if (activeIndex.value === 0) {
+							activeIndex.value = props.audioList.length - 1
+						} else {
+							activeIndex.value--
+						}
+					} else if (type === 'next') {
+						if (activeIndex.value === props.audioList.length - 1) {
+							activeIndex.value = 0
+						} else {
+							activeIndex.value++
+						}
+					}
+					break
+				}
+			}
+		}
+	}
+
+	function handleClickVolumeIcon() {
+		volume.value = volume.value === 0 ? 0.5 : 0
+		handleChangeVolumeBar(volume.value)
+	}
+
+	function handleChangeVolumeBar(volume) {
+		audioElement.value.volume = Number(volume.toFixed(1))
+	}
+
+	// 关闭音频预览
+	function handleClosePreview() {
+		visible.value = false
+		props.callback('cancel')
+	}
+
+	onMounted(() => {
+		visible.value = props.visible
+		activeIndex.value = props.defaultIndex
+	})
+
+	onUnmounted(() => {
+		document.removeEventListener('keyup', handleAddKeyupEvent)
+	})
+
+	defineExpose({
+		visible
+	})
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+	@import '@/style/myResource/mixins.less';
+
+	.audio-preview-wrapper {
+		background: @PrimaryText;
+		position: fixed;
+		top: 0;
+		left: 0;
+		width: 100vw;
+		height: 100vh;
+		z-index: 3;
+		color: @BorderBase;
+		.audio-background {
+			position: fixed;
+			top: -50%;
+			left: 0;
+			width: 100vw;
+			height: auto;
+			filter: blur(65px);
+			opacity: 0.6;
+			z-index: -1;
+		}
+		.operate-box {
+			position: fixed;
+			top: 16px;
+			right: 32px;
+			display: flex;
+			align-items: center;
+			.tip-icon,
+			.close-icon {
+				margin-left: 16px;
+				cursor: pointer;
+				&:hover {
+					color: @Warning;
+				}
+			}
+			.tip-icon {
+				font-size: 26px;
+			}
+			.close-icon {
+				font-size: 30px;
+			}
+		}
+		.audio-list-wrapper {
+			margin: 0 auto;
+			width: 85%;
+			height: calc(100vh - 120px);
+			padding-top: 32px;
+			display: flex;
+			justify-content: space-between;
+			.audio-list {
+				flex: 1;
+				list-style: none;
+				.audio-list-body {
+					height: calc(100% - 56px);
+					overflow: auto;
+					.setScrollbar(8px, transparent, rgba(0, 0, 0, 0.3));
+				}
+				.audio-list-header,
+				.audio-item {
+					border-radius: 8px;
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					height: 56px;
+					cursor: pointer;
+					padding: 0 16px;
+					&:hover {
+						background: rgba(0, 0, 0, 0.1);
+					}
+					&.active {
+						background: rgba(0, 0, 0, 0.1);
+						color: @Warning;
+					}
+					.name {
+						flex: 1;
+						.sequence {
+							display: inline-block;
+							margin-right: 8px;
+							width: 14px;
+							text-align: center;
+						}
+						.wave {
+							margin-right: 10px;
+							width: 12px;
+							height: 12px;
+						}
+						.no-wave {
+							margin-right: 6px;
+							font-size: 16px;
+						}
+					}
+					.play-icon,
+					.pause-icon,
+					.download-icon,
+					.share-icon {
+						margin-right: 16px;
+						font-size: 22px;
+						cursor: pointer;
+						&:hover {
+							color: @Warning;
+						}
+					}
+					.download {
+						color: inherit;
+						&:hover {
+							color: @Warning;
+						}
+					}
+					.audio-size {
+						width: 120px;
+						padding-right: 24px;
+						text-align: right;
+					}
+					.path {
+						min-width: 120px;
+					}
+				}
+				.audio-list-header {
+					padding-right: 24px;
+					.name {
+						padding-left: 18px;
+					}
+				}
+			}
+			.img-and-lyrics {
+				padding: 8px 0 0 16px;
+				width: 340px;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				text-align: center;
+				.audio-img {
+					margin-bottom: 16px;
+					width: 160px;
+					height: 160px;
+				}
+				.audio-name {
+					margin-bottom: 8px;
+					font-size: 18px;
+					line-height: 2;
+				}
+				.album-artist,
+				.album-name {
+					margin-bottom: 8px;
+				}
+				.lyrics-list {
+					width: 100%;
+					flex: 1;
+					overflow: auto;
+					.setScrollbar(6px, transparent, rgba(0, 0, 0, 0.3));
+					-webkit-mask-image: linear-gradient(
+						180deg,
+						hsla(0, 0%, 100%, 0) 0,
+						hsla(0, 0%, 100%, 0.6) 15%,
+						#fff 25%,
+						#fff 75%,
+						hsla(0, 0%, 100%, 0.6) 85%,
+						hsla(0, 0%, 100%, 0)
+					);
+					&.one {
+						.lyrics-item {
+							margin-top: 40px;
+						}
+					}
+					.lyrics-item {
+						line-height: 40px;
+						cursor: pointer;
+						&:not(.active):hover {
+							color: #fff;
+						}
+						&.active {
+							color: @Warning;
+						}
+					}
+				}
+			}
+		}
+		.control-wrapper {
+			margin: 0 auto;
+			width: 85%;
+			height: 120px;
+			padding: 24px 0 32px 0;
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			.control-left {
+				flex: 1;
+				height: 100%;
+				display: flex;
+				align-items: center;
+				text-align: center;
+				padding-left: 8px;
+				.operate-icon {
+					margin-right: 16px;
+					font-size: 40px;
+					cursor: pointer;
+					&:hover {
+						color: @Warning;
+					}
+				}
+				.progress-bar {
+					margin-right: 16px;
+					flex: 1;
+					// >>> .el-slider__runway {
+					//   height: 2px;
+					//   .el-slider__button-wrapper {
+					//     top: -17px;
+					//     .el-slider__button {
+					//       border: none;
+					//     }
+					//   }
+					//   .el-slider__bar {
+					//     height: 100%;
+					//     background: @Warning;
+					//   }
+					// }
+				}
+			}
+			.control-right {
+				width: 340px;
+				font-size: 24px;
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				font-size: 32px;
+				.operate-icon {
+					margin-right: 16px;
+					cursor: pointer;
+					&:nth-last-of-type {
+						margin-right: 0;
+					}
+					&:hover {
+						color: @Warning;
+					}
+					&.download-link {
+						font-size: 32px;
+						color: inherit;
+						&:hover {
+							.download-icon {
+								color: @Warning;
+							}
+						}
+					}
+				}
+				.volume-icon {
+					margin-right: 8px;
+				}
+				.volume-bar {
+					width: 100px;
+					// >>> .el-slider__runway {
+					//   height: 2px;
+					//   .el-slider__button-wrapper {
+					//     top: -19px;
+					//     .el-slider__button {
+					//       border: none;
+					//     }
+					//   }
+					//   .el-slider__bar {
+					//     height: 100%;
+					//     background: @Warning;
+					//   }
+					// }
+				}
+			}
+		}
+	}
+</style>

+ 64 - 0
src/views/myResource/file/box/audioPreview/index.js

@@ -0,0 +1,64 @@
+import { createApp } from 'vue'
+import Antd from 'ant-design-vue'
+// 导入组件
+import AudioPreview from './BoxMask.vue'
+
+let audioPreviewInstance = null
+let audioPreviewApp = null
+
+/**
+ * 初始化音频预览实例
+ * @param {Array} audioList 音频列表
+ * @param {Number} defaultIndex 当前查看的音频索引
+ * @param {Function} callback 回调函数
+ */
+const initInstanceAudioPreview = (audioList, defaultIndex, callback) => {
+	const mountNode = document.createElement('div')
+	audioPreviewApp = createApp(AudioPreview, {
+		audioList,
+		defaultIndex,
+		callback
+	})
+	// 注册 Ant Design Vue 组件
+	audioPreviewApp.use(Antd)
+	audioPreviewInstance = audioPreviewApp.mount(mountNode)
+	return mountNode
+}
+
+/**
+ * 音频预览 Promise 函数
+ * @param {Object} obj 包含音频列表和默认索引的对象
+ * @returns {Promise} 抛出确认和取消回调函数
+ */
+const showAudioPreviewBox = (obj) => {
+	// 非首次调用服务时,在 DOM 中移除上个实例
+	if (audioPreviewInstance !== null && audioPreviewApp !== null) {
+		audioPreviewApp.unmount()
+		audioPreviewInstance = null
+		audioPreviewApp = null
+	}
+
+	let { audioList, defaultIndex } = obj
+	return new Promise((resolve) => {
+		const mountNode = initInstanceAudioPreview(audioList, defaultIndex, (res) => {
+			resolve(res)
+			// 服务取消时卸载 DOM
+			if (res === 'cancel' && audioPreviewInstance !== null) {
+				audioPreviewApp.unmount()
+				document.body.removeChild(mountNode)
+				audioPreviewInstance = null
+				audioPreviewApp = null
+			}
+		})
+
+		document.body.appendChild(mountNode) // 挂载 DOM
+		// 使用 setTimeout 确保组件已完全挂载
+		setTimeout(() => {
+			if (audioPreviewInstance) {
+				audioPreviewInstance.visible = true // 打开音频预览遮罩层
+			}
+		}, 0)
+	})
+}
+
+export default showAudioPreviewBox

+ 443 - 0
src/views/myResource/file/box/codePreview/BoxMask.vue

@@ -0,0 +1,443 @@
+<template>
+	<transition
+		name="fade"
+		enter-active-class="ant-fade-enter ant-fade-enter-active"
+		leave-active-class="ant-fade-leave ant-fade-leave-active"
+	>
+		<div class="code-preview-wrapper" v-show="visible" @keydown.ctrl.s.prevent="handleModifyFileContent">
+			<!-- 顶部信息栏 -->
+			<div class="tip-wrapper" v-if="visible">
+				<div class="name" :title="$getFileNameComplete(fileInfo)">
+					{{ $getFileNameComplete(fileInfo) }}
+					<span class="un-save" v-show="isModify && !codeMirrorOptions.readOnly">(未保存)</span>
+				</div>
+				<div class="editor-preview">在线预览{{ codeMirrorOptions.readOnly ? '' : ' & 编辑' }}</div>
+				<div class="tool-wrapper">
+					<a
+						class="item download-link"
+						target="_blank"
+						:href="$getDownloadFilePath(fileInfo)"
+						:download="$getFileNameComplete(fileInfo)"
+					>
+						<download-outlined title="下载" />
+					</a>
+					<a-tooltip placement="bottom">
+						<template #title>
+							<div>
+								操作提示:<br />
+								1. 按 Esc 键可退出查看;<br />
+								2. 支持在线编辑、保存、下载
+							</div>
+						</template>
+						<div class="item text-wrapper">
+							<span class="text">操作提示</span>
+							<bulb-outlined />
+						</div>
+					</a-tooltip>
+					<close-outlined class="item" title="关闭预览" @click="closeCodePreview" />
+				</div>
+			</div>
+
+			<!-- 代码编辑区域 -->
+			<div class="code-editor-wrapper">
+				<div class="operate-wrapper">
+					<save-outlined
+						v-show="isModify && !codeMirrorOptions.readOnly"
+						class="save-icon"
+						title="保存(ctrl+s)"
+						@click="handleModifyFileContent"
+					/>
+					<a-form class="editor-set-form" :model="codeMirrorOptions" layout="inline" size="small">
+						<a-form-item>
+							<a-checkbox v-model:checked="codeMirrorOptions.lineWrapping" @change="handleChangeCodeMirrorOption">
+								自动换行
+							</a-checkbox>
+						</a-form-item>
+
+						<a-form-item>
+							<a-select v-model:value="codeMirrorCustomOptions.fontSize" style="width: 96px" show-search>
+								<a-select-option v-for="size in fontSizeList" :key="size" :value="size">
+									{{ size }} px
+								</a-select-option>
+							</a-select>
+						</a-form-item>
+
+						<a-form-item label="代码语言">
+							<a-select
+								v-model:value="codeMirrorOptions.mode"
+								style="width: 120px"
+								show-search
+								@change="handleChangeCodeMirrorOption"
+							>
+								<a-select-option
+									v-for="[key, value] in Object.entries(fileSuffixCodeModeMap)"
+									:key="key"
+									:value="value[1].mime"
+								>
+									{{ value[1].language }}
+								</a-select-option>
+							</a-select>
+						</a-form-item>
+
+						<a-form-item label="主题">
+							<a-select
+								v-model:value="codeMirrorOptions.theme"
+								style="width: 190px"
+								show-search
+								@change="handleChangeCodeMirrorOption"
+							>
+								<a-select-option value="default">default</a-select-option>
+								<a-select-option v-for="theme in codeMirrorThemeList" :key="theme" :value="theme">
+									{{ theme }}
+								</a-select-option>
+							</a-select>
+						</a-form-item>
+					</a-form>
+				</div>
+
+				<a-spin :spinning="codeMirrorLoading">
+					<!-- <Codemirror
+						v-if="isShow"
+						ref="codemirrorRef"
+						v-model:value="codeMirrorText"
+						:options="codeMirrorOptions"
+						class="code-editor"
+						:style="{ fontSize: `${codeMirrorCustomOptions.fontSize}px` }"
+					/> -->
+				</a-spin>
+			</div>
+		</div>
+	</transition>
+</template>
+
+<script setup>
+	import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
+	import { message } from 'ant-design-vue'
+	// import { Codemirror } from 'codemirror-editor-vue3'
+	// 启用基础样式
+	// import 'codemirror-editor-vue3/dist/style.css'
+	// // 启用编程语言
+	// import 'codemirror/mode/javascript/javascript.js'
+	// import 'codemirror/mode/xml/xml.js'
+	// import 'codemirror/mode/css/css.js'
+	// // 启用主题
+	// import 'codemirror/lib/codemirror.css'
+	// import 'codemirror/theme/monokai.css'
+	// import 'codemirror/theme/material.css'
+	import { DownloadOutlined, BulbOutlined, CloseOutlined, SaveOutlined } from '@ant-design/icons-vue'
+	import { useMyResourceStore } from '@/store/myResource'
+	import { getFilePreview, modifyFileContent } from '@/api/myResource/file'
+	import { fontSizeList, fileSuffixCodeModeMap, codeMirrorThemeList } from '@/libs/map'
+
+	// import 'codemirror/lib/codemirror.css'
+	// import './theme.js'
+	// import './mode.js'
+	// import './fold.js'
+
+	// 状态管理
+	const store = useMyResourceStore()
+
+	const visible = ref(false)
+	const originalCodeText = ref('')
+	const codeMirrorText = ref('')
+	const codeMirrorLoading = ref(false)
+	const codeMirrorOptions = ref({
+		tabSize: 4,
+		mode: 'text/html',
+		theme: 'default',
+		readOnly: true,
+		lineNumbers: true,
+		line: true,
+		autoCloseBrackets: true,
+		foldGutter: true,
+		lineWrapping: true,
+		gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers']
+	})
+	const codeMirrorCustomOptions = ref({
+		fontSize: 14
+	})
+	const isShow = ref(true)
+
+	// 计算属性
+	const screenWidth = computed(() => store.screenWidth)
+	const isModify = computed(() => originalCodeText.value !== codeMirrorText.value)
+
+	// 方法
+	function getCodeText() {
+		codeMirrorLoading.value = true
+		getFilePreview({
+			userFileId: fileInfo.userFileId,
+			isMin: false,
+			shareBatchNum: fileInfo.shareBatchNum,
+			extractionCode: fileInfo.extractionCode,
+			token: $common.getCookies($config.tokenKeyName)
+		}).then((res) => {
+			codeMirrorLoading.value = false
+			originalCodeText.value = typeof res === 'object' ? JSON.stringify(res) : res
+			codeMirrorText.value = originalCodeText.value + ''
+		})
+	}
+
+	function handleModifyFileContent() {
+		if (!isModify.value || codeMirrorOptions.value.readOnly) {
+			return false
+		}
+		codeMirrorLoading.value = true
+		modifyFileContent({
+			userFileId: fileInfo.userFileId,
+			fileContent: codeMirrorText.value
+		})
+			.then((res) => {
+				codeMirrorLoading.value = false
+				if (res.success) {
+					message.success('已保存')
+					getCodeText()
+				} else {
+					message.error(res.message)
+				}
+			})
+			.catch((err) => {
+				codeMirrorLoading.value = false
+				message.error(err.message)
+			})
+	}
+
+	function handleChangeCodeMirrorOption() {
+		isShow.value = false
+		isShow.value = true
+	}
+
+	function closeCodePreview() {
+		visible.value = false
+		callback('cancel')
+	}
+
+	// 监听
+	watch(visible, (val) => {
+		if (val) {
+			let fileSuffix = fileInfo.extendName.toLowerCase()
+			if (fileSuffix === 'yaml') {
+				fileSuffix = 'yml'
+			}
+			if (fileSuffixCodeModeMap.has(fileSuffix)) {
+				codeMirrorOptions.value.mode = fileSuffixCodeModeMap.get(fileSuffix).mime
+			}
+			codeMirrorOptions.value.readOnly = !isEdit //  设置编辑器是否只读
+			codeMirrorOptions.value.theme = localStorage.getItem('qiwen_file_codemirror_theme') || 'default'
+			getCodeText()
+			// 添加键盘 Esc 事件
+			nextTick(() => {
+				document.addEventListener('keyup', (e) => {
+					if (e.keyCode === 27) {
+						closeCodePreview()
+					}
+				})
+			})
+		} else {
+			document.removeEventListener('keyup', (e) => {
+				if (e.keyCode === 27) {
+					closeCodePreview()
+				}
+			})
+		}
+	})
+
+	watch(
+		() => codeMirrorOptions.value.theme,
+		(val) => {
+			localStorage.setItem('qiwen_file_codemirror_theme', val)
+		}
+	)
+
+	// 生命周期钩子
+	onMounted(() => {})
+
+	onUnmounted(() => {})
+
+	defineExpose({
+		visible
+	})
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+
+	.code-preview-wrapper {
+		position: fixed;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		left: 0;
+		overflow: auto;
+		width: 100%;
+		height: 100vh;
+		z-index: 2;
+		display: flex;
+		align-items: center;
+		animation: imgPreviewAnimation 0.3s;
+		-webkit-animation: imgPreviewAnimation 0.3s; /* Safari and Chrome */
+		animation-iteration-count: 0.3;
+		-webkit-animation-iteration-count: 0.3;
+		animation-fill-mode: forwards;
+		-webkit-animation-fill-mode: forwards; /* Safari 和 Chrome */
+		@keyframes imgPreviewAnimation {
+			0% {
+				background: transparent;
+			}
+			100% {
+				background: rgba(0, 0, 0, 0.8);
+			}
+		}
+		@keyframes imgPreviewAnimation {
+			0% {
+				background: transparent;
+			}
+			100% {
+				background: rgba(0, 0, 0, 0.8);
+			}
+		}
+		.tip-wrapper {
+			position: fixed;
+			top: 0;
+			left: 0;
+			z-index: 2;
+			background: rgba(0, 0, 0, 0.5);
+			padding: 0 48px;
+			width: 100%;
+			height: 48px;
+			line-height: 48px;
+			color: #fff;
+			font-size: 16px;
+			display: flex;
+			justify-content: space-between;
+			.name {
+				flex: 1;
+				padding-right: 16px;
+				text-align: left;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				.un-save {
+					color: @Warning;
+					font-size: 14px;
+				}
+			}
+			.tool-wrapper {
+				flex: 1;
+				display: flex;
+				justify-content: flex-end;
+				.item {
+					margin-left: 16px;
+					height: 48px;
+					line-height: 48px;
+					cursor: pointer;
+					&:hover {
+						opacity: 0.7;
+					}
+				}
+				.download-link {
+					color: inherit;
+					font-size: 18px;
+				}
+				.text-wrapper {
+					.text {
+						margin-right: 8px;
+					}
+				}
+			}
+		}
+		.code-editor-wrapper {
+			margin: 56px auto 0 auto;
+			width: 90vw;
+			height: calc(100vh - 80px);
+			.operate-wrapper {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				border-radius: 8px 8px 0 0;
+				border-bottom: 1px solid @BorderBase;
+				padding: 8px 16px;
+				background: #fff;
+				.save-icon {
+					font-size: 20px;
+					cursor: pointer;
+					color: @Info;
+					font-weight: 550;
+					&:hover {
+						opacity: 0.5;
+					}
+				}
+				.editor-set-form {
+					flex: 1;
+					text-align: right;
+					// >>> .el-form-item {
+					//   margin-bottom: 0;
+					//   &.font-size {
+					//     .el-form-item__content {
+					//       .el-select {
+					//         width: 96px;
+					//       }
+					//     }
+					//   }
+					//   &.lanaguage {
+					//     .el-form-item__content {
+					//       .el-select {
+					//         width: 120px;
+					//       }
+					//     }
+					//   }
+					//   &.theme {
+					//     .el-form-item__content {
+					//       .el-select {
+					//         width: 190px;
+					//       }
+					//     }
+					//   }
+					// }
+				}
+			}
+			.code-editor {
+				height: calc(100vh - 129px);
+				//   >>> .CodeMirror {
+				//     border-radius: 0 0 8px 8px;
+				//     height: inherit;
+				//     font-size: inherit;
+				//     * {
+				//       font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace !important;
+				//     }
+				//     .CodeMirror-vscrollbar, .CodeMirror-hscrollbar {
+				//       display: none !important;
+				//     }
+				//     .CodeMirror-scroll {
+				//       width: 100%;
+				//       padding: 8px 0 0 0;
+				//       line-height: 1.5;
+				//       font-size: inherit;
+				//       setScrollbar(12px, transparent, #C0C4CC);
+				//     }
+				//   }
+			}
+		}
+	}
+	.editor-preveiw {
+		margin-left: -102px;
+	}
+	.editor-preveiw[data-v-8897ab08] {
+		margin-left: -102px;
+	}
+	/deep/ .el-checkbox__input.is-checked + .el-checkbox__label {
+		color: #29175b;
+	}
+	/deep/ .el-checkbox__input.is-checked .el-checkbox__inner,
+	.el-checkbox__input.is-indeterminate .el-checkbox__inner {
+		background-color: #29175b;
+		border-color: #29175b;
+	}
+	/deep/ .el-select .el-input.is-focus .el-input__inner {
+		border-color: #29175b;
+	}
+	/deep/ .el-select-dropdown__item.selected {
+		color: #29175b !important;
+		font-weight: 700;
+	}
+</style>

+ 7 - 0
src/views/myResource/file/box/codePreview/fold.js

@@ -0,0 +1,7 @@
+// 折叠
+import 'codemirror/addon/fold/foldgutter.css'
+import 'codemirror/addon/fold/foldcode'
+import 'codemirror/addon/fold/foldgutter'
+import 'codemirror/addon/fold/brace-fold'
+import 'codemirror/addon/fold/comment-fold'
+import 'codemirror/addon/fold/indent-fold'

+ 64 - 0
src/views/myResource/file/box/codePreview/index.js

@@ -0,0 +1,64 @@
+import { createApp } from 'vue'
+import Antd from 'ant-design-vue'
+// 导入组件
+import CodePreview from './BoxMask.vue'
+
+let codePreviewInstance = null
+let codePreviewApp = null
+
+/**
+ * 初始化代码预览实例
+ * @param {Object} fileInfo 文件信息
+ * @param {Boolean} isEdit 是否可编辑
+ * @param {Function} callback 回调函数
+ */
+const initInstanceCodePreview = (fileInfo, isEdit, callback) => {
+	const mountNode = document.createElement('div')
+	codePreviewApp = createApp(CodePreview, {
+		fileInfo,
+		isEdit,
+		callback
+	})
+	// 注册 Ant Design Vue 组件
+	codePreviewApp.use(Antd)
+	codePreviewInstance = codePreviewApp.mount(mountNode)
+	return mountNode
+}
+
+/**
+ * 代码预览 Promise 函数
+ * @param {Object} obj 包含文件信息和编辑状态的对象
+ * @returns {Promise} 抛出确认和取消回调函数
+ */
+const showCodePreviewBox = (obj) => {
+	// 非首次调用服务时,在 DOM 中移除上个实例
+	if (codePreviewInstance !== null && codePreviewApp !== null) {
+		codePreviewApp.unmount()
+		codePreviewInstance = null
+		codePreviewApp = null
+	}
+
+	let { fileInfo, isEdit } = obj
+	return new Promise((resolve) => {
+		const mountNode = initInstanceCodePreview(fileInfo, isEdit, (res) => {
+			resolve(res)
+			// 服务取消时卸载 DOM
+			if (res === 'cancel' && codePreviewInstance !== null) {
+				codePreviewApp.unmount()
+				document.body.removeChild(mountNode)
+				codePreviewInstance = null
+				codePreviewApp = null
+			}
+		})
+
+		document.body.appendChild(mountNode) // 挂载 DOM
+		// 使用 setTimeout 确保组件已完全挂载
+		setTimeout(() => {
+			if (codePreviewInstance) {
+				codePreviewInstance.visible = true // 打开代码预览遮罩层
+			}
+		}, 0)
+	})
+}
+
+export default showCodePreviewBox

+ 24 - 0
src/views/myResource/file/box/codePreview/mode.js

@@ -0,0 +1,24 @@
+/**
+ * 这里引入几个常用的语言解析模式
+ * 全量语言模式参考,https://codemirror.net/mode/
+ */
+import 'codemirror/mode/clike/clike.js' //  C | C++ | Objective-C | Scala | Ceylon | Java 语言
+import 'codemirror/mode/javascript/javascript.js' //  JavaScript 语言
+import 'codemirror/mode/css/css.js' //  css 语言 | less 预编译器 | scss 预编译器
+import 'codemirror/mode/go/go.js' //  Go 语言
+import 'codemirror/mode/nginx/nginx.js' //  Nginx 语言
+import 'codemirror/mode/php/php.js' //  PHP 语言
+import 'codemirror/mode/powershell/powershell.js' //  Bat 文件
+import 'codemirror/mode/properties/properties.js' //  properties 文件
+import 'codemirror/mode/python/python.js' //  Python 语言
+import 'codemirror/mode/r/r.js' //  R 语言
+import 'codemirror/mode/rust/rust.js' //  R 语言
+import 'codemirror/mode/sass/sass.js' //  sass 预编译器
+import 'codemirror/mode/shell/shell.js' //  sass 预编译器
+import 'codemirror/mode/stylus/stylus.js' //  stylus 预编译器
+import 'codemirror/mode/swift/swift.js' //  Swift 语言
+import 'codemirror/mode/vue/vue.js' //  vue.js 框架
+import 'codemirror/mode/sql/sql.js' //  SQL 语言
+import 'codemirror/mode/xml/xml.js' //  xml 语言
+import 'codemirror/mode/yaml/yaml' //  YAML 语言
+import 'codemirror/mode/htmlmixed/htmlmixed.js' //  html 标记语言

+ 69 - 0
src/views/myResource/file/box/codePreview/theme.js

@@ -0,0 +1,69 @@
+/**
+ * codemirror 所有的高亮代码主题
+ */
+import 'codemirror/theme/3024-day.css'
+import 'codemirror/theme/3024-night.css'
+import 'codemirror/theme/abbott.css'
+import 'codemirror/theme/abcdef.css'
+import 'codemirror/theme/ambiance-mobile.css'
+import 'codemirror/theme/ambiance.css'
+import 'codemirror/theme/ayu-dark.css'
+import 'codemirror/theme/ayu-mirage.css'
+import 'codemirror/theme/base16-dark.css'
+import 'codemirror/theme/base16-light.css'
+import 'codemirror/theme/bespin.css'
+import 'codemirror/theme/blackboard.css'
+import 'codemirror/theme/cobalt.css'
+import 'codemirror/theme/colorforth.css'
+import 'codemirror/theme/darcula.css'
+import 'codemirror/theme/dracula.css'
+import 'codemirror/theme/duotone-dark.css'
+import 'codemirror/theme/duotone-light.css'
+import 'codemirror/theme/eclipse.css'
+import 'codemirror/theme/elegant.css'
+import 'codemirror/theme/erlang-dark.css'
+import 'codemirror/theme/gruvbox-dark.css'
+import 'codemirror/theme/hopscotch.css'
+import 'codemirror/theme/icecoder.css'
+import 'codemirror/theme/idea.css'
+import 'codemirror/theme/isotope.css'
+import 'codemirror/theme/juejin.css'
+import 'codemirror/theme/lesser-dark.css'
+import 'codemirror/theme/liquibyte.css'
+import 'codemirror/theme/lucario.css'
+import 'codemirror/theme/material-darker.css'
+import 'codemirror/theme/material-ocean.css'
+import 'codemirror/theme/material-palenight.css'
+import 'codemirror/theme/material.css'
+import 'codemirror/theme/mbo.css'
+import 'codemirror/theme/mdn-like.css'
+import 'codemirror/theme/midnight.css'
+import 'codemirror/theme/monokai.css'
+import 'codemirror/theme/moxer.css'
+import 'codemirror/theme/neat.css'
+import 'codemirror/theme/neo.css'
+import 'codemirror/theme/night.css'
+import 'codemirror/theme/nord.css'
+import 'codemirror/theme/oceanic-next.css'
+import 'codemirror/theme/panda-syntax.css'
+import 'codemirror/theme/paraiso-dark.css'
+import 'codemirror/theme/paraiso-light.css'
+import 'codemirror/theme/pastel-on-dark.css'
+import 'codemirror/theme/railscasts.css'
+import 'codemirror/theme/rubyblue.css'
+import 'codemirror/theme/seti.css'
+import 'codemirror/theme/shadowfox.css'
+import 'codemirror/theme/solarized.css'
+import 'codemirror/theme/ssms.css'
+import 'codemirror/theme/the-matrix.css'
+import '_public/codemirror/css/tomorrow-night.css' //  自定义高亮样式文件
+import 'codemirror/theme/tomorrow-night-bright.css'
+import 'codemirror/theme/tomorrow-night-eighties.css'
+import 'codemirror/theme/ttcn.css'
+import 'codemirror/theme/twilight.css'
+import 'codemirror/theme/vibrant-ink.css'
+import 'codemirror/theme/xq-dark.css'
+import 'codemirror/theme/xq-light.css'
+import 'codemirror/theme/yeti.css'
+import 'codemirror/theme/yonce.css'
+import 'codemirror/theme/zenburn.css'

+ 667 - 0
src/views/myResource/file/box/contextMenu/Box.vue

@@ -0,0 +1,667 @@
+<template>
+	<!-- 右键列表 -->
+	<transition name="el-fade-in-linear">
+		<!-- 在某个文件上右键 -->
+		<ul
+			class="right-menu-list"
+			id="rightMenuList"
+			v-show="visible"
+			v-if="selectedFile !== undefined"
+			:style="`top: ${rightMenu.top};right: ${rightMenu.right};bottom: ${rightMenu.bottom};left: ${rightMenu.left};`"
+		>
+			<li
+				class="right-menu-item"
+				@click="fileHelper.handleFileNameClick(selectedFile, 0, [selectedFile])"
+				v-if="seeBtnShow"
+			>
+				<i class="el-icon-view"></i> 查看
+			</li>
+			<li class="right-menu-item" @click="handleDeleteFileBtnClick(selectedFile)" v-if="deleteBtnShow">
+				<i class="el-icon-delete"></i> 删除
+			</li>
+			<li class="right-menu-item" @click="handleRestoreFileBtnClick(selectedFile)" v-if="restoreBtnShow">
+				<i class="el-icon-refresh-left"></i> 还原
+			</li>
+			<li class="right-menu-item" @click="handleCopyFileBtnClick(selectedFile)" v-if="copyBtnShow">
+				<i class="el-icon-copy-document"></i> 复制到
+			</li>
+			<li class="right-menu-item" @click="handleMoveFileBtnClick(selectedFile)" v-if="moveBtnShow">
+				<i class="el-icon-s-promotion"></i> 移动
+			</li>
+			<li class="right-menu-item" @click="handleAddMyFileList(selectedFile)" v-if="addMyFileListBtnShow">
+				<i class="el-icon-edit-outline"></i> 添加至我的云盘
+			</li>
+
+			<li class="right-menu-item" @click="handleEnterpriseDisk(selectedFile)" v-if="addEnterpriseDiskBtnShow">
+				<i class="el-icon-edit-outline"></i> 添加至企业云盘
+			</li>
+
+			<li class="right-menu-item" @click="handleRenameFileBtnClick(selectedFile)" v-if="renameBtnShow">
+				<i class="el-icon-edit-outline"></i> 重命名
+			</li>
+			<li class="right-menu-item" @click="handleShareFileBtnClick(selectedFile)" v-if="shareBtnShow">
+				<i class="el-icon-share"></i> 分享
+			</li>
+			<li class="right-menu-item" @click="visible = false" v-if="downloadBtnShow">
+				<a
+					@click="geta1(fileHelper.getDownloadFilePath(selectedFile))"
+					id="aaaa"
+					target="_blank"
+					style="display: block; color: inherit"
+					:href="fileHelper.getDownloadFilePath(selectedFile)"
+					:download="selectedFile.fileName + '.' + selectedFile.extendName"
+				>
+					<i class="el-icon-download"></i> 下载
+				</a>
+			</li>
+			<!-- 0-解压到当前文件夹, 1-自动创建该文件名目录,并解压到目录里, 3-手动选择解压目录 -->
+			<li class="right-menu-item unzip-menu-item" v-if="unzipBtnShow">
+				<i class="el-icon-files"></i> 解压缩
+				<i class="el-icon-arrow-right"></i>
+				<ul
+					class="unzip-list"
+					:style="`top: ${unzipMenu.top};bottom: ${unzipMenu.bottom};left: ${unzipMenu.left};right: ${unzipMenu.right};`"
+				>
+					<li class="unzip-item" @click="handleUnzipFileBtnClick(selectedFile, 0)">
+						<i class="el-icon-files"></i> 解压到当前文件夹
+					</li>
+					<li
+						class="unzip-item"
+						@click="handleUnzipFileBtnClick(selectedFile, 1)"
+						:title="`解压到&quot;${selectedFile.fileName}&quot;`"
+					>
+						<i class="el-icon-files"></i> 解压到"{{ selectedFile.fileName }}"
+					</li>
+					<li class="unzip-item" @click="handleUnzipFileBtnClick(selectedFile, 2)">
+						<i class="el-icon-files"></i> 解压到目标文件夹
+					</li>
+				</ul>
+			</li>
+			<li
+				class="right-menu-item"
+				@click="fileHelper.copyShareLink(selectedFile.shareBatchNum, selectedFile.extractionCode)"
+				v-if="copyLinkBtnShow"
+			>
+				<i class="el-icon-edit"></i> 复制链接
+			</li>
+			<li class="right-menu-item" @click="handleShowDetailInfo(selectedFile)" v-if="detailInfoBtnShow">
+				<i class="el-icon-document"></i> 文件详情
+			</li>
+		</ul>
+		<!-- 在空白处右键,右键列表展示新建文件夹、新建文件等操作按钮 -->
+		<ul
+			class="right-menu-list add"
+			id="rightMenuList"
+			v-show="visible"
+			v-else
+			:style="`top: ${rightMenu.top};right: ${rightMenu.right};bottom: ${rightMenu.bottom};left: ${rightMenu.left};`"
+		>
+			<li class="right-menu-item" @click="callback('confirm')"><i class="el-icon-refresh"></i> 刷新</li>
+			<template v-if="fileType === 0">
+				<a-divider />
+				<li class="right-menu-item" @click="handleClickAddFolderBtn"><i class="el-icon-folder-add"></i> 新建文件夹</li>
+				<!-- 图片路径需要修改 -->
+				<li class="right-menu-item" @click="handleCreateFile('docx')"><img :src="wordImg" />新建 Word 文档</li>
+				<li class="right-menu-item" @click="handleCreateFile('xlsx')"><img :src="excelImg" />新建 Excel 工作表</li>
+				<li class="right-menu-item" @click="handleCreateFile('pptx')"><img :src="pptImg" />新建 PPT 演示文稿</li>
+				<a-divider />
+				<li class="right-menu-item" @click="handleUploadFileBtnClick(1)"><i class="el-icon-upload2"></i> 上传文件</li>
+				<li class="right-menu-item" @click="handleUploadFileBtnClick(2)">
+					<i class="el-icon-folder-opened"></i> 上传文件夹
+				</li>
+				<li class="right-menu-item" @click="handleUploadFileBtnClick(3)"><i class="el-icon-thumb"></i> 拖拽上传</li>
+			</template>
+		</ul>
+	</transition>
+</template>
+
+<script setup>
+	import { ref, computed, watch, onMounted, getCurrentInstance } from 'vue'
+	import { useRouter, useRoute } from 'vue-router'
+	import { message as antMessage } from 'ant-design-vue' // 重命名以避免与 window.message 冲突
+	import { officeFileType, fileSuffixCodeModeMap, markdownFileType } from '@/libs/map.js'
+	import { addMyFileListApi, addEnterpriseDiskApi } from '@/api/myResource/file'
+	import axios from 'axios' // 如果需要
+	import fileOperationPlugins from '@/libs/fileOperationPlugins.js'
+	import globalFunction from '@/libs/globalFunction/index.js'
+	import appConfig from '@/config/index.js'
+
+	// 从插件或模块中获取方法
+	const { openDialog } = fileOperationPlugins
+	const { openBox } = fileOperationPlugins
+	const fileHelper = globalFunction.file // $file
+
+	// $common 的处理: common.js 未找到,暂时保留 proxy.$common
+	// 如果 $common 是必须的,你需要找到它的定义并导入
+	const { proxy } = getCurrentInstance() // 保留 proxy 以便访问 $common (如果确实需要且未找到其来源)
+
+	const props = defineProps({
+		selectedFile: Object,
+		domEvent: Object,
+		serviceEl: Object,
+		callType: String,
+		callback: Function
+	})
+
+	const router = useRouter()
+	const route = useRoute()
+
+	const visible = ref(false)
+
+	const rightMenu = ref({
+		top: 0,
+		left: 0,
+		bottom: 'auto',
+		right: 'auto'
+	})
+
+	const unzipMenu = ref({
+		top: 0,
+		bottom: 'auto',
+		left: '126px',
+		right: 'auto'
+	})
+
+	// 图片资源: 请将路径修改为实际项目中的正确路径
+	// 例如: import wordIcon from '@/assets/images/file_word.svg'; const wordImg = ref(wordIcon);
+	// 或者如果图片在 public 目录: const wordImg = ref('/images/file_word.svg');
+	const dirImg = ref('/img/file/dir.png') // 示例: 假设在 public/img/file/dir.png
+	const wordImg = ref('/img/file/file_word.svg') // 示例: 假设在 public/img/file/file_word.svg
+	const excelImg = ref('/img/file/file_excel.svg') // 示例: 假设在 public/img/file/file_excel.svg
+	const pptImg = ref('/img/file/file_ppt.svg') // 示例: 假设在 public/img/file/file_ppt.svg
+
+	const newname = ref('')
+
+	// 计算属性
+	const routeName = computed(() => route.name)
+	const fileType = computed(() => (route.query.fileType ? Number(route.query.fileType) : 0))
+	const filePath = computed(() => route.query.filePath)
+
+	const seeBtnShow = computed(() => {
+		const PIC = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp']
+		return props.selectedFile && fileType.value !== 6 && PIC.includes(props.selectedFile.extendName?.toLowerCase())
+	})
+
+	const deleteBtnShow = computed(() => {
+		return fileType.value !== 8 && !['Share'].includes(routeName.value)
+	})
+
+	const restoreBtnShow = computed(() => {
+		return fileType.value === 6 && !['Share'].includes(routeName.value)
+	})
+
+	const copyBtnShow = computed(() => {
+		return ![6, 8, 15].includes(fileType.value) && !['Share'].includes(routeName.value)
+	})
+
+	const moveBtnShow = computed(() => {
+		return ![6, 8, 15].includes(fileType.value) && !['Share'].includes(routeName.value)
+	})
+
+	const addMyFileListBtnShow = computed(() => {
+		return fileType.value == 15 && !['Share'].includes(routeName.value)
+	})
+
+	const addEnterpriseDiskBtnShow = computed(() => {
+		return ![6, 8, 15].includes(fileType.value) && !['Share'].includes(routeName.value)
+	})
+
+	const renameBtnShow = computed(() => {
+		return ![6, 8].includes(fileType.value) && !['Share'].includes(routeName.value)
+	})
+
+	const shareBtnShow = computed(() => {
+		return ![6, 8, 15].includes(fileType.value) && !['Share'].includes(routeName.value)
+	})
+
+	const downloadBtnShow = computed(() => {
+		return ![6, 8].includes(fileType.value)
+	})
+
+	const unzipBtnShow = computed(() => {
+		return (
+			props.selectedFile &&
+			![6, 8].includes(fileType.value) &&
+			!['Share'].includes(routeName.value) &&
+			['zip', 'rar', '7z', 'tar', 'gz'].includes(props.selectedFile.extendName)
+		)
+	})
+
+	const folderEditBtnShow = computed(() => {
+		return (
+			props.selectedFile &&
+			![6, 8].includes(fileType.value) &&
+			props.selectedFile.isDir === 1 &&
+			!['Share'].includes(routeName.value)
+		)
+	})
+
+	const onlineEditBtnShow = computed(() => {
+		if (!props.selectedFile) return false
+		return (
+			![6, 8].includes(fileType.value) &&
+			(officeFileType.includes(props.selectedFile.extendName) ||
+				markdownFileType.includes(props.selectedFile.extendName) ||
+				fileSuffixCodeModeMap.has(props.selectedFile.extendName)) &&
+			!['Share'].includes(routeName.value)
+		)
+	})
+
+	const copyLinkBtnShow = computed(() => {
+		return fileType.value === 8
+	})
+
+	const detailInfoBtnShow = computed(() => {
+		return true
+	})
+
+	const uploadFileParams = computed(() => {
+		return {
+			filePath: filePath.value,
+			isDir: 0
+		}
+	})
+
+	// Watcher
+	watch(visible, (newValue) => {
+		if (newValue === true) {
+			document.body.addEventListener('click', closeRightMenu)
+			handleOpenContextMenu()
+		} else {
+			document.body.removeEventListener('click', closeRightMenu)
+		}
+	})
+
+	onMounted(() => {
+		// onMounted logic if any
+	})
+
+	// Methods
+	const geta1 = (src) => {
+		console.log('src', src)
+		// $common.getCookies 依赖 proxy.$common,如果 $common 无法解析,这里会报错
+		const cookies = proxy.$common?.getCookies(appConfig.TOKEN_NAME)
+		const aElement = document.getElementById('aaaa')
+		if (!aElement) return
+
+		const downloadNameParts = aElement.download.split('.')
+		const name1 = downloadNameParts[0]
+		const name2 = downloadNameParts[1]
+
+		if (name2) {
+			newname.value = name1 + '.' + name2
+		} else {
+			newname.value = name1 + '.' + 'zip'
+		}
+
+		console.log('appConfig.API_URL', appConfig.API_URL) // 使用导入的 appConfig
+		const newname1 = newname.value
+		// 确保 aElement.href 是完整的 URL 或者正确拼接
+		const apiUrlPart = aElement.href.includes('/api') ? aElement.href.split('/api')[1] : aElement.href
+		const url = `${
+			(appConfig.API_URL.startsWith('http') ? '' : 'http://' + window.location.host) +
+			appConfig.API_URL + // 使用导入的 appConfig
+			apiUrlPart +
+			'&' +
+			cookies
+		}`
+
+		let isios = navigator.userAgent.indexOf('iPhone') > -1
+		let isAndroid = navigator.userAgent.indexOf('Android') > -1
+		console.log('url', url)
+		console.log('cookies', cookies)
+		console.log('newname1', newname1)
+		if (isios) {
+			window.webkit.messageHandlers.Download.postMessage({
+				params: {
+					url,
+					cookies,
+					newname1
+				}
+			})
+		} else if (isAndroid) {
+			window.Download.downList(url, cookies, newname1)
+		}
+	}
+
+	const downloadPost = (src) => {
+		console.log(window.location.origin)
+		console.log(33333, src)
+		return new Promise((resolve, reject) => {
+			axios({
+				url: window.location.origin + src, // 确保 src 是正确的相对路径
+				method: 'get',
+				responseType: 'blob'
+			})
+				.then((res) => {
+					fileDownload(res, props.selectedFile?.fileName || 'download')
+					console.log(res)
+					resolve(res)
+				})
+				.catch((err) => {
+					reject(err)
+				})
+		})
+	}
+
+	const fileDownload = (res, filename) => {
+		let blob = new Blob([res.data])
+		if ('msSaveOrOpenBlob' in navigator) {
+			window.navigator.msSaveOrOpenBlob(blob, filename)
+		} else {
+			let objectUrl = (window.URL || window.webkitURL).createObjectURL(blob)
+			let downFile = document.createElement('a')
+			downFile.style.display = 'none'
+			downFile.href = objectUrl
+			downFile.download = filename
+			document.body.appendChild(downFile)
+			downFile.click()
+			document.body.removeChild(downFile)
+			window.URL.revokeObjectURL(objectUrl)
+		}
+	}
+
+	const handleOpenContextMenu = () => {
+		console.log('handleOpenContextMenu', props)
+		if (!props.domEvent) return
+		if (
+			document.body.clientHeight - props.domEvent.clientY <
+			document.querySelectorAll('#rightMenuList > .right-menu-item').length * 36 + 10
+		) {
+			rightMenu.value.top = 'auto'
+			rightMenu.value.bottom = `${document.body.clientHeight - props.domEvent.clientY}px`
+			unzipMenu.value.top = 'auto'
+			unzipMenu.value.bottom = '0px'
+		} else {
+			rightMenu.value.top = `${props.domEvent.clientY}px`
+			rightMenu.value.bottom = 'auto'
+			unzipMenu.value.top = '0px'
+			unzipMenu.value.bottom = 'auto'
+		}
+		if (document.body.clientWidth - props.domEvent.clientX < 138) {
+			rightMenu.value.left = 'auto'
+			rightMenu.value.right = `${document.body.clientWidth - props.domEvent.clientX}px`
+			unzipMenu.value.left = '-200px'
+			unzipMenu.value.right = 'auto'
+		} else {
+			rightMenu.value.left = `${props.domEvent.clientX + 8}px`
+			rightMenu.value.right = 'auto'
+			unzipMenu.value.left = '126px'
+			unzipMenu.value.right = 'auto'
+		}
+		visible.value = true
+	}
+
+	const closeRightMenu = (event) => {
+		if (!event.target.className.includes('operate-more-') && !event.target.className.includes('unzip-menu-item')) {
+			visible.value = false
+			if (props.selectedFile !== undefined) {
+				props.callback?.('cancel')
+			}
+		}
+	}
+
+	const handleCopyFileBtnClick = (fileInfo) => {
+		visible.value = false
+		openDialog
+			.copyFile({
+				fileInfo
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleMoveFileBtnClick = (fileInfo) => {
+		visible.value = false
+		openDialog
+			.moveFile({
+				isBatchMove: false,
+				fileInfo
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleUnzipFileBtnClick = (fileInfo, unzipMode) => {
+		visible.value = false
+		openDialog
+			.unzipFile({
+				unzipMode: unzipMode,
+				userFileId: fileInfo.userFileId
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleDeleteFileBtnClick = (fileInfo) => {
+		visible.value = false
+		openDialog
+			.deleteFile({
+				isBatchOperation: false,
+				fileInfo,
+				deleteMode: fileType.value === 6 ? 2 : 1
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleRestoreFileBtnClick = (fileInfo) => {
+		visible.value = false
+		openDialog
+			.restoreFile({
+				deleteBatchNum: fileInfo.deleteBatchNum,
+				filePath: fileInfo.filePath
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleRenameFileBtnClick = (fileInfo) => {
+		visible.value = false
+		openDialog
+			.renameFile({
+				oldFileName: fileInfo.fileName,
+				userFileId: fileInfo.userFileId
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleShareFileBtnClick = (fileInfo) => {
+		visible.value = false
+		openDialog.shareFile({
+			fileInfo: [
+				{
+					userFileId: fileInfo.userFileId
+				}
+			]
+		})
+	}
+
+	const handleClickFolderEdit = () => {
+		router.push({
+			name: 'WebIDE',
+			query: { filePath: props.selectedFile.filePath }
+		})
+	}
+
+	const handleClickFileEdit = (fileInfo) => {
+		if (officeFileType.includes(fileInfo.extendName)) {
+			fileHelper.getFileOnlineEditPathByOffice(fileInfo)
+		} else if (markdownFileType.includes(fileInfo.extendName)) {
+			openBox.markdownPreview({
+				fileInfo: fileInfo,
+				editable: true
+			})
+		} else {
+			openBox.codePreview({ fileInfo: fileInfo, isEdit: true })
+		}
+	}
+
+	const handleShowDetailInfo = (fileInfo) => {
+		visible.value = false
+		openDialog.showFileDetail({ fileInfo })
+	}
+
+	const handleClickAddFolderBtn = () => {
+		openDialog
+			.addFolder({
+				filePath: route.query.filePath || '/'
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleCreateFile = (fileExt) => {
+		// Renamed parameter to avoid conflict
+		openDialog
+			.createFile({
+				fileType: fileExt, // Use the renamed parameter
+				filePath: route.query.filePath || '/'
+			})
+			.then((res) => {
+				props.callback?.(res)
+			})
+	}
+
+	const handleUploadFileBtnClick = (uploadType) => {
+		if (uploadType === 1 || uploadType === 2) {
+			openDialog?.upload({ type: uploadType, filePath: filePath.value }).then((res) => {
+				props.callback?.(res)
+			})
+		} else if (uploadType === 3) {
+			antMessage.info('请拖拽文件或文件夹到此区域以上传')
+		}
+		visible.value = false
+	}
+
+	const handleAddMyFileList = (fileInfo) => {
+		addMyFileListApi({ userFileId: fileInfo.userFileId, filePath: '/' }).then((res) => {
+			if (res.success) {
+				antMessage.success('添加成功')
+			} else {
+				antMessage.error(res.message)
+			}
+		})
+	}
+
+	const handleEnterpriseDisk = (fileInfo) => {
+		addEnterpriseDiskApi({ userFileId: fileInfo.userFileId, filePath: '/' }).then((res) => {
+			if (res.success) {
+				antMessage.success('添加成功')
+			} else {
+				antMessage.error(res.message)
+			}
+		})
+	}
+
+	// Expose any properties/methods that need to be accessed by the parent component
+	defineExpose({
+		visible, // if parent needs to control visibility directly
+		handleOpenContextMenu // if parent needs to trigger opening context menu
+	})
+</script>
+
+<style lang="less" scoped>
+	/* 保持原有样式 */
+	.right-menu-list {
+		position: fixed;
+		z-index: 9999; // 确保在最上层
+		width: auto;
+		min-width: 128px;
+		max-width: 200px;
+		padding: 4px 0;
+		border: 1px solid #e4e7ed;
+		border-radius: 4px;
+		background-color: #fff;
+		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+		&.add {
+			.right-menu-item {
+				padding: 0 8px;
+				img {
+					width: 14px;
+					margin-right: 8px;
+				}
+			}
+		}
+	}
+
+	.right-menu-item {
+		box-sizing: border-box;
+		position: relative;
+		display: flex;
+		align-items: center;
+		width: 100%;
+		height: 36px;
+		padding: 0 16px;
+		font-size: 14px;
+		line-height: 36px;
+		color: #606266;
+		cursor: pointer;
+		user-select: none;
+
+		&:hover {
+			background: #ecf5ff;
+			color: #409eff;
+		}
+
+		&.unzip-menu-item {
+			justify-content: space-between;
+		}
+
+		.el-icon-arrow-right {
+			position: absolute;
+			right: 8px;
+		}
+
+		.unzip-list {
+			position: absolute;
+			z-index: 1;
+			padding: 4px 0;
+			border: 1px solid #e4e7ed;
+			border-radius: 4px;
+			background-color: #fff;
+			box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+			display: none;
+		}
+
+		&.unzip-menu-item:hover > .unzip-list {
+			display: block;
+		}
+
+		.unzip-item {
+			box-sizing: border-box;
+			display: flex;
+			align-items: center;
+			width: 100%;
+			min-width: max-content;
+			height: 36px;
+			padding: 0 16px;
+			font-size: 14px;
+			line-height: 36px;
+			color: #606266;
+			cursor: pointer;
+
+			&:hover {
+				background: #ecf5ff;
+				color: #409eff;
+			}
+		}
+
+		i {
+			margin-right: 8px;
+		}
+	}
+
+	.el-divider--horizontal {
+		margin: 4px 0;
+	}
+</style>

+ 78 - 0
src/views/myResource/file/box/contextMenu/index.js

@@ -0,0 +1,78 @@
+import { createApp } from 'vue'
+import Antd from 'ant-design-vue'
+import router from '@/router' // 添加这行导入
+// 导入组件
+import ContextMenu from './Box.vue'
+
+let contextMenuInstance = null
+let contextMenuApp = null
+
+/**
+ * 初始化右键菜单实例
+ * @param {string} selectedFile 文件信息,如果此值为 undefined,证明是在空白触右键
+ * @param {object} domEvent 当前右键点击的元素
+ * @param {} serviceEl 调用当前服务的组件实例
+ * @param {} callType 是否组件本身
+ * @param {Function} callback 回调函数
+ */
+const initInstanceContextMenu = (selectedFile, domEvent, serviceEl, callType, callback) => {
+	const mountNode = document.createElement('div')
+	contextMenuApp = createApp(ContextMenu, {
+		selectedFile,
+		domEvent,
+		serviceEl,
+		callType,
+		callback
+	})
+	// 注册 Ant Design Vue 组件
+	contextMenuApp.use(Antd)
+	contextMenuApp.use(router) // 添加这行
+	contextMenuInstance = contextMenuApp.mount(mountNode)
+	return mountNode
+}
+
+/**
+ * 右键菜单 Promise 函数
+ * @returns {Promise} 抛出确认和取消回调函数
+ */
+const showContextMenuBox = (obj) => {
+	// 非首次调用服务时,在 DOM 中移除上个实例
+	if (contextMenuInstance !== null && contextMenuApp !== null) {
+		contextMenuApp.unmount()
+		contextMenuInstance = null
+		contextMenuApp = null
+	}
+
+	let { selectedFile, domEvent, serviceEl, callType } = obj
+	return new Promise((resolve) => {
+		const mountNode = initInstanceContextMenu(selectedFile, domEvent, serviceEl, callType, (res) => {
+			resolve(res)
+			// 服务取消时卸载 DOM
+			if (res === 'cancel' && contextMenuInstance !== null) {
+				contextMenuApp.unmount()
+				document.body.removeChild(mountNode)
+				contextMenuInstance = null
+				contextMenuApp = null
+			}
+		})
+
+		document.body.appendChild(mountNode) //  挂载 DOM
+		// 使用 setTimeout 替代 Vue.nextTick
+		setTimeout(() => {
+			if (contextMenuInstance) {
+				contextMenuInstance.handleOpenContextMenu() //  开始计算右键菜单需要显示的位置
+			}
+		}, 0)
+	})
+}
+
+// 添加关闭事件
+showContextMenuBox.close = () => {
+	if (contextMenuInstance !== null && contextMenuApp !== null) {
+		contextMenuApp.unmount()
+		contextMenuInstance = null
+		contextMenuApp = null
+	}
+}
+
+export default showContextMenuBox

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio