file-selector.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <template>
  2. <view class="main">
  3. <!-- 上传区域 -->
  4. <view class="upload-area" @click="fileRender.createFileInputDom">
  5. <view class="upload-icon">
  6. <text class="icon-plus">+</text>
  7. </view>
  8. <text class="upload-text">上传文件</text>
  9. </view>
  10. <!-- 文件列表 -->
  11. <view class="file-list">
  12. <view v-for="(file, index) in fileList" :key="index" class="file-item">
  13. <text class="file-type-icon">
  14. {{getFileEmoji(file.name)}}
  15. </text>
  16. <view class="file-info">
  17. <text class="file-name">{{file.name}}</text>
  18. </view>
  19. <view class="delete-btn" @click="removeFile(index)">
  20. <text class="delete-icon">×</text>
  21. </view>
  22. </view>
  23. </view>
  24. </view>
  25. </template>
  26. <script>
  27. export default {
  28. name: 'FileSelector',
  29. data() {
  30. return {
  31. fileList: [],
  32. // 文件扩展名映射
  33. fileTypes: {
  34. image: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'],
  35. document: ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'txt'],
  36. audio: ['mp3', 'wav', 'ogg', 'aac'],
  37. video: ['mp4', 'avi', 'mov', 'wmv', 'flv'],
  38. archive: ['zip', 'rar', '7z', 'tar', 'gz'],
  39. json: ['json']
  40. }
  41. }
  42. },
  43. methods: {
  44. // 获取文件类型
  45. getFileType(fileName) {
  46. const extension = fileName.split('.').pop().toLowerCase()
  47. for (let type in this.fileTypes) {
  48. if (this.fileTypes[type].includes(extension)) return type
  49. }
  50. return 'other'
  51. },
  52. // 根据文件类型获取对应emoji
  53. getFileEmoji(fileName) {
  54. const fileType = this.getFileType(fileName)
  55. const emojiMap = {
  56. image: '🖼️',
  57. document: '📄',
  58. audio: '🎵',
  59. video: '🎬',
  60. archive: '📦',
  61. json: '📋',
  62. other: '📎'
  63. }
  64. return emojiMap[fileType] || '📎'
  65. },
  66. // 接收文件
  67. async receiveRenderFile(result) {
  68. let fileInfo = {
  69. name: result.name,
  70. path: ''
  71. }
  72. //#ifdef APP-PLUS
  73. const fileUrl = await this.base64toPath(result.filePath, result.name)
  74. fileInfo.path = fileUrl.localAbsolutePath
  75. //#endif
  76. //#ifdef H5
  77. fileInfo.path = result.filePath
  78. //#endif
  79. this.fileList.push(fileInfo)
  80. this.$emit('filesChanged', this.fileList)
  81. this.$emit('fileSelected', fileInfo)
  82. },
  83. // 删除文件
  84. removeFile(index) {
  85. this.fileList.splice(index, 1)
  86. this.$emit('filesChanged', this.fileList)
  87. },
  88. // APP端base64转本地路径
  89. async base64toPath(base64, attachName) {
  90. return new Promise((resolve, reject) => {
  91. const filePath = `_doc/file/${attachName}`
  92. plus.io.resolveLocalFileSystemURL('_doc', entry => {
  93. entry.getDirectory('file', {
  94. create: true,
  95. exclusive: false
  96. }, entry => {
  97. entry.getFile(attachName, {
  98. create: true,
  99. exclusive: false
  100. }, entry => {
  101. entry.createWriter(writer => {
  102. writer.onwrite = () => {
  103. resolve({
  104. relativePath: filePath,
  105. localAbsolutePath: plus.io.convertLocalFileSystemURL(filePath)
  106. })
  107. }
  108. writer.onerror = reject
  109. writer.seek(0)
  110. writer.writeAsBinary(this.getSymbolAfterString(base64, ','))
  111. }, reject)
  112. }, reject)
  113. }, reject)
  114. }, reject)
  115. })
  116. },
  117. getSymbolAfterString(val, symbolStr) {
  118. return val ? val.toString().split(symbolStr)[1] || val : ''
  119. }
  120. }
  121. }
  122. </script>
  123. <script module="fileRender" lang="renderjs">
  124. export default {
  125. methods: {
  126. createFileInputDom(e, ownerVm) {
  127. const fileInput = document.createElement('input')
  128. fileInput.setAttribute('type', 'file')
  129. fileInput.setAttribute('accept', '*')
  130. fileInput.setAttribute('multiple', 'multiple')
  131. fileInput.click()
  132. fileInput.addEventListener('change', e => {
  133. if (!e.target.files.length) return
  134. Array.from(e.target.files).forEach(file => {
  135. //#ifdef H5
  136. ownerVm.callMethod('receiveRenderFile', {
  137. name: file.name,
  138. filePath: URL.createObjectURL(file)
  139. })
  140. //#endif
  141. //#ifdef APP-PLUS
  142. const reader = new FileReader()
  143. reader.onload = ({target}) => {
  144. target?.result && ownerVm.callMethod('receiveRenderFile', {
  145. name: file.name,
  146. filePath: target.result
  147. })
  148. }
  149. reader.readAsDataURL(file)
  150. //#endif
  151. })
  152. })
  153. }
  154. }
  155. }
  156. </script>
  157. <style>
  158. /* 基础样式 */
  159. .main {
  160. padding: 20rpx;
  161. }
  162. /* 上传区域样式 */
  163. .upload-area {
  164. width: 100%;
  165. height: 160rpx;
  166. background-color: #FFFFFF;
  167. border: 2rpx dashed #E5E5E5;
  168. border-radius: 8rpx;
  169. display: flex;
  170. flex-direction: column;
  171. align-items: center;
  172. justify-content: center;
  173. margin-bottom: 20rpx;
  174. padding: 20rpx;
  175. box-sizing: border-box;
  176. }
  177. .upload-icon {
  178. width: 44rpx;
  179. height: 44rpx;
  180. background-color: #F7F7F7;
  181. border-radius: 4rpx;
  182. display: flex;
  183. align-items: center;
  184. justify-content: center;
  185. margin-bottom: 8rpx;
  186. }
  187. .icon-plus {
  188. color: #C7C7C7;
  189. font-size: 28rpx;
  190. font-weight: 100;
  191. }
  192. .upload-text {
  193. font-size: 22rpx;
  194. color: #A8A8A8;
  195. }
  196. /* 文件列表样式 */
  197. .file-list {
  198. width: 100%;
  199. }
  200. .file-item {
  201. background-color: #FFFFFF;
  202. border-radius: 8rpx;
  203. padding: 20rpx;
  204. margin-bottom: 16rpx;
  205. display: flex;
  206. align-items: center;
  207. }
  208. .file-type-icon {
  209. width: 44rpx;
  210. height: 44rpx;
  211. margin-right: 16rpx;
  212. font-size: 32rpx;
  213. text-align: center;
  214. line-height: 44rpx;
  215. }
  216. .file-info {
  217. flex: 1;
  218. overflow: hidden;
  219. }
  220. .file-name {
  221. font-size: 22rpx;
  222. color: #A8A8A8;
  223. overflow: hidden;
  224. text-overflow: ellipsis;
  225. white-space: nowrap;
  226. }
  227. .delete-btn {
  228. padding: 4rpx 8rpx;
  229. }
  230. .delete-icon {
  231. font-size: 24rpx;
  232. color: #C7C7C7;
  233. line-height: 1;
  234. }
  235. .delete-icon:active {
  236. color: #ff4444;
  237. }
  238. </style>