cop-chooseFile.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <template>
  2. <!-- renderjs 触发器:通过改变 prop 来触发文件选择 -->
  3. <view
  4. style="position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0;"
  5. :change:triggerPicker="renderJS.onTriggerChange"
  6. :triggerPicker="triggerFlag"
  7. :change:acceptProp="renderJS.acceptChanged"
  8. :acceptProp="accept">
  9. </view>
  10. </template>
  11. <script>
  12. export default {
  13. props: {
  14. // 触发标记(外部改变此值可触发文件选择)
  15. trigger: {
  16. type: Number,
  17. default: 0
  18. },
  19. // 接受的文件类型
  20. accept: {
  21. type: String,
  22. default: '*'
  23. }
  24. },
  25. data() {
  26. return {
  27. triggerFlag: 0
  28. }
  29. },
  30. watch: {
  31. // 监听外部 trigger 的变化
  32. trigger(newVal) {
  33. if (newVal) {
  34. this.triggerFlag = newVal
  35. }
  36. }
  37. },
  38. onLoad(res) {},
  39. methods: {
  40. // plus.io选择文件
  41. // 选择完文件后,拿到的是base64字符串,转成对应的数据
  42. parseJSONData(base64Str) {
  43. console.log('选择的文件base64Str', base64Str)
  44. let jsonStr = this.convertBase64ToUTF8(base64Str)
  45. if (base64Str.includes('application/json')) {
  46. let jsonData = JSON.parse(jsonStr)
  47. this.$emit('readJSONFinish', { jsonData })
  48. } else {
  49. this.$emit('readJSONFinish', { jsonStr })
  50. }
  51. },
  52. convertBase64ToUTF8(base64Str) {
  53. let base64Content = atob(base64Str.split(',')[1])
  54. base64Content = base64Content
  55. .split('')
  56. .map(function (c) {
  57. return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
  58. })
  59. .join('')
  60. try {
  61. let jsonStr = decodeURIComponent(base64Content)
  62. return jsonStr
  63. } catch (error) {
  64. console.error('读取失败', error)
  65. uni.showToast({
  66. title: '读取失败,不支持此格式文件',
  67. icon: 'none'
  68. })
  69. }
  70. },
  71. async receiveRenderFile(result) {
  72. console.log('receiveRenderFile 被调用,文件信息:', result)
  73. // 直接 emit 原始文件数据(包含 base64),不进行转换
  74. this.$emit('receiveRenderFile', result)
  75. // #ifdef APP-PLUS
  76. // 如果需要本地路径,可以进行转换(可选)
  77. // const fileUrl = await this.base64toPath(result.filePath, result.name)
  78. // this.fileName = fileUrl.relativePath
  79. // this.filePath = fileUrl.localAbsolutePath
  80. // #endif
  81. // #ifdef H5
  82. this.fileName = result.name
  83. this.filePath = result.filePath
  84. // #endif
  85. },
  86. //将base64转成路径
  87. async base64toPath(base64, attachName) {
  88. console.log('base64开始转化成文件')
  89. let _that = this
  90. return new Promise(function (resolve, reject) {
  91. const filePath = `_doc/yourFilePath/${attachName}`
  92. plus.io.resolveLocalFileSystemURL(
  93. '_doc',
  94. function (entry) {
  95. entry.getDirectory(
  96. 'yourFilePath',
  97. {
  98. create: true,
  99. exclusive: false
  100. },
  101. function (entry) {
  102. entry.getFile(
  103. attachName,
  104. {
  105. create: true,
  106. exclusive: false
  107. },
  108. function (entry) {
  109. entry.createWriter(function (writer) {
  110. writer.onwrite = function (res) {
  111. console.log('base64转化文件完成')
  112. const obj = {
  113. relativePath: filePath,
  114. localAbsolutePath:
  115. plus.io.convertLocalFileSystemURL(filePath)
  116. }
  117. resolve(obj)
  118. }
  119. writer.onerror = reject
  120. writer.seek(0)
  121. writer.writeAsBinary(
  122. _that.getSymbolAfterString(base64, ',')
  123. )
  124. }, reject)
  125. },
  126. reject
  127. )
  128. },
  129. reject
  130. )
  131. },
  132. reject
  133. )
  134. })
  135. },
  136. // 取某个符号后面的字符
  137. getSymbolAfterString(val, symbolStr) {
  138. if (val == undefined || val == null || val == '') {
  139. return ''
  140. }
  141. val = val.toString()
  142. const index = val.indexOf(symbolStr)
  143. if (index != -1) {
  144. val = val.substring(index + 1, val.length)
  145. return val
  146. } else {
  147. return val
  148. }
  149. }
  150. }
  151. }
  152. </script>
  153. <script module="renderJS" lang="renderjs">
  154. export default {
  155. data() {
  156. return {
  157. acceptType: '*'
  158. }
  159. },
  160. mounted() {
  161. // renderjs 的 mounted 不接收参数,acceptType 通过 acceptChanged 方法初始化
  162. console.log('cop-chooseFile renderJS mounted')
  163. },
  164. methods: {
  165. // 监听触发标记的变化
  166. onTriggerChange(newVal, oldVal, ownerVm, ins) {
  167. console.log('cop-chooseFile: 触发标记改变', newVal)
  168. if (newVal && newVal !== oldVal) {
  169. this.createFileInputDom(null, ownerVm)
  170. }
  171. },
  172. // 接收 accept 属性的变化
  173. acceptChanged(newVal) {
  174. this.acceptType = newVal || '*'
  175. },
  176. createFileInputDom(e, ownerVm) {
  177. console.log('cop-chooseFile: 开始选择文件')
  178. let fileInput = document.createElement('input')
  179. fileInput.setAttribute('type', 'file')
  180. fileInput.setAttribute('accept', this.acceptType)
  181. fileInput.style.display = 'none'
  182. document.body.appendChild(fileInput)
  183. fileInput.click()
  184. fileInput.addEventListener('change', e => {
  185. let file = e.target.files[0]
  186. if (file) {
  187. console.log('cop-chooseFile: 选择了文件', file.name, file.size)
  188. // #ifdef APP-PLUS
  189. let reader = new FileReader();
  190. reader.readAsDataURL(file);
  191. reader.onload = function(event) {
  192. const base64Str = event.target.result; // 文件的base64
  193. console.log('cop-chooseFile: 文件读取完成')
  194. // 先调用 receiveRenderFile 传递原始文件信息(用于所有文件类型)
  195. ownerVm.callMethod('receiveRenderFile', {
  196. name: file.name,
  197. filePath: base64Str,
  198. size: file.size,
  199. type: file.type
  200. })
  201. // 只对文本/JSON 文件调用 parseJSONData
  202. const isTextFile = file.type.includes('text') || file.type.includes('json') ||
  203. file.name.endsWith('.txt') || file.name.endsWith('.json')
  204. if (isTextFile) {
  205. try {
  206. ownerVm.callMethod('parseJSONData', base64Str)
  207. } catch(e) {
  208. console.log('parseJSONData failed:', e.message)
  209. }
  210. }
  211. }
  212. // #endif
  213. // #ifdef H5
  214. // 如果需要得到文件的本地路径,可以通过下面方法
  215. const filePath = URL.createObjectURL(file)
  216. ownerVm.callMethod('receiveRenderFile', {
  217. name: file.name,
  218. filePath: filePath,
  219. size: file.size,
  220. type: file.type
  221. })
  222. // #endif
  223. } else {
  224. console.log('cop-chooseFile: 未选择文件')
  225. }
  226. // 清理 input 元素
  227. setTimeout(function() {
  228. if (fileInput.parentNode) {
  229. document.body.removeChild(fileInput)
  230. console.log('cop-chooseFile: input 已清理')
  231. }
  232. }, 100)
  233. })
  234. }
  235. }
  236. }
  237. </script>
  238. <style scoped>
  239. /* 无样式,组件为隐藏触发器 */
  240. </style>