file-service.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. const {
  2. getWxApi,
  3. isCancelError
  4. } = require('./platform-utils')
  5. function formatBytes(byteLength) {
  6. const length = Number(byteLength) || 0
  7. if (length >= 1024 && length % 1024 === 0) return `${length / 1024} KB`
  8. if (length >= 1024) return `${(length / 1024).toFixed(2)} KB`
  9. return `${length} bytes`
  10. }
  11. function formatExportStamp(date = new Date()) {
  12. const pad = (value, length = 2) => String(value).padStart(length, '0')
  13. return [
  14. date.getFullYear(),
  15. pad(date.getMonth() + 1),
  16. pad(date.getDate()),
  17. '-',
  18. pad(date.getHours()),
  19. pad(date.getMinutes()),
  20. pad(date.getSeconds())
  21. ].join('')
  22. }
  23. function normalizeExtensions(extensions = []) {
  24. return extensions
  25. .map((extension) => String(extension || '').trim().replace(/^\./, '').toLowerCase())
  26. .filter(Boolean)
  27. }
  28. function getFileName(file, fallback = '未命名文件') {
  29. return String(file && file.name ? file.name : fallback)
  30. }
  31. function getFilePath(file) {
  32. return file && (file.path || file.tempFilePath) ? (file.path || file.tempFilePath) : ''
  33. }
  34. function getFirstSelectedFile(result, fallbackName = '未命名文件') {
  35. const file = result && Array.isArray(result.tempFiles) ? result.tempFiles[0] : null
  36. if (!file) throw new Error('没有选择文件')
  37. const filePath = getFilePath(file)
  38. if (!filePath) throw new Error('无法读取所选文件路径')
  39. return {
  40. file,
  41. name: getFileName(file, fallbackName),
  42. path: filePath
  43. }
  44. }
  45. function assertFileExtension(fileInfo, extensions = [], message = '文件格式不符') {
  46. const normalizedExtensions = normalizeExtensions(extensions)
  47. if (!normalizedExtensions.length) return
  48. const nameText = `${fileInfo && fileInfo.name ? fileInfo.name : ''} ${fileInfo && fileInfo.path ? fileInfo.path : ''}`
  49. const matched = normalizedExtensions.some((extension) => (
  50. new RegExp(`\\.${extension}$`, 'i').test(nameText)
  51. ))
  52. if (!matched) throw new Error(message)
  53. }
  54. function chooseMessageFile(options = {}) {
  55. const wxApi = getWxApi()
  56. const extensions = normalizeExtensions(options.extensions || options.extension || [])
  57. return new Promise((resolve, reject) => {
  58. if (typeof wxApi.chooseMessageFile !== 'function') {
  59. reject(new Error('当前微信版本不支持从聊天记录选择文件'))
  60. return
  61. }
  62. const chooseOptions = {
  63. count: options.count || 1,
  64. type: options.type || 'file',
  65. success: resolve,
  66. fail: reject
  67. }
  68. if (extensions.length) chooseOptions.extension = extensions
  69. wxApi.chooseMessageFile(chooseOptions)
  70. })
  71. }
  72. function chooseLocalFile(options = {}) {
  73. const wxApi = getWxApi()
  74. const extensions = normalizeExtensions(options.extensions || options.extension || [])
  75. return new Promise((resolve, reject) => {
  76. if (typeof wxApi.chooseFile !== 'function') {
  77. reject(new Error(options.unsupportedMessage || '当前微信版本不支持打开本地文件,请从聊天记录选择'))
  78. return
  79. }
  80. const chooseOptions = {
  81. count: options.count || 1,
  82. type: options.type || 'file',
  83. success: resolve,
  84. fail: reject
  85. }
  86. if (extensions.length) chooseOptions.extension = extensions
  87. wxApi.chooseFile(chooseOptions)
  88. })
  89. }
  90. function chooseFile(source = 'message', options = {}) {
  91. return source === 'local'
  92. ? chooseLocalFile(options)
  93. : chooseMessageFile(options)
  94. }
  95. function readFile(filePath, options = {}) {
  96. const wxApi = getWxApi()
  97. return new Promise((resolve, reject) => {
  98. if (typeof wxApi.getFileSystemManager !== 'function') {
  99. reject(new Error('当前微信版本不支持读取文件'))
  100. return
  101. }
  102. const fs = wxApi.getFileSystemManager()
  103. const readOptions = {
  104. filePath,
  105. success: (res) => resolve(res.data),
  106. fail: reject
  107. }
  108. if (options.encoding) readOptions.encoding = options.encoding
  109. fs.readFile(readOptions)
  110. })
  111. }
  112. function toUint8Array(data) {
  113. if (data instanceof Uint8Array) return data
  114. if (data instanceof ArrayBuffer) return new Uint8Array(data)
  115. if (ArrayBuffer.isView(data)) {
  116. return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
  117. }
  118. return new Uint8Array(data || new ArrayBuffer(0))
  119. }
  120. async function loadSelectedFile(source = 'message', options = {}) {
  121. const result = await chooseFile(source, options)
  122. const fileInfo = getFirstSelectedFile(result, options.fallbackName || '未命名文件')
  123. assertFileExtension(fileInfo, options.extensions || options.extension || [], options.extensionMessage || '文件格式不符')
  124. const data = await readFile(fileInfo.path, {
  125. encoding: options.encoding
  126. })
  127. const bytes = options.encoding ? null : toUint8Array(data)
  128. return {
  129. ...fileInfo,
  130. bytes,
  131. data,
  132. size: bytes ? bytes.length : String(data || '').length,
  133. sizeText: formatBytes(bytes ? bytes.length : String(data || '').length),
  134. text: options.encoding ? String(data || '') : ''
  135. }
  136. }
  137. function getUserDataFilePath(fileName) {
  138. const wxApi = getWxApi()
  139. const userDataPath = wxApi.env && wxApi.env.USER_DATA_PATH
  140. if (!userDataPath) throw new Error('当前微信版本不支持生成文件')
  141. return `${userDataPath}/${fileName}`
  142. }
  143. function writeTextFile(filePath, data, encoding = 'utf8') {
  144. const wxApi = getWxApi()
  145. if (typeof wxApi.getFileSystemManager !== 'function') {
  146. throw new Error('当前微信版本不支持生成文件')
  147. }
  148. const fs = wxApi.getFileSystemManager()
  149. if (typeof fs.writeFileSync !== 'function') {
  150. throw new Error('当前微信版本不支持同步生成文件')
  151. }
  152. fs.writeFileSync(filePath, data, encoding)
  153. }
  154. function shareFileToChat(filePath, fileName) {
  155. const wxApi = getWxApi()
  156. return new Promise((resolve, reject) => {
  157. if (typeof wxApi.shareFileMessage !== 'function') {
  158. reject(new Error('当前微信版本不支持发送文件到聊天'))
  159. return
  160. }
  161. wxApi.shareFileMessage({
  162. fileName,
  163. filePath,
  164. success: resolve,
  165. fail: reject
  166. })
  167. })
  168. }
  169. async function saveTextFileToChat(fileName, data) {
  170. const filePath = getUserDataFilePath(fileName)
  171. writeTextFile(filePath, data, 'utf8')
  172. await shareFileToChat(filePath, fileName)
  173. return filePath
  174. }
  175. module.exports = {
  176. assertFileExtension,
  177. chooseFile,
  178. chooseLocalFile,
  179. chooseMessageFile,
  180. formatBytes,
  181. formatExportStamp,
  182. getFirstSelectedFile,
  183. getUserDataFilePath,
  184. isCancelError,
  185. loadSelectedFile,
  186. readFile,
  187. saveTextFileToChat,
  188. shareFileToChat,
  189. toUint8Array,
  190. writeTextFile
  191. }