Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

121 Zeilen
3.5 KiB

  1. /**
  2. * 图片上传 composable,完全对齐 ai_uniapp_v2/utils/upload.js
  3. *
  4. * - POST multipart/form-data 到 /api/common/uploadpic(走服务端代理)
  5. * - Header: { token, timestr, nonce }
  6. * - FormData: { file, token, mac, client, source, version, version_code, upload_type:1 }
  7. */
  8. import md5 from '~/utils/md5'
  9. export function useUpload() {
  10. const macCookie = useCookie('mac', { maxAge: 365 * 24 * 3600, path: '/' })
  11. const tokenCookie = useCookie('token', { path: '/' })
  12. const openIdCookie = useCookie('open_id', { path: '/' })
  13. const sourceCookie = useCookie('source', { path: '/' })
  14. const { getClientId, getClientIos } = useClientDevice()
  15. function getNonce() {
  16. const timeStr = Date.now().toString()
  17. const str = md5(timeStr)
  18. const nonce = str.substring(4, 14)
  19. return { timeStr, nonce }
  20. }
  21. /**
  22. * 上传图片
  23. * @param {File} file - 浏览器 File 对象
  24. * @returns {Promise<{code: number, data: {url: string, width: number, height: number}, msg: string}>}
  25. */
  26. async function uploadImage(file) {
  27. if (!file) {
  28. throw new Error('请选择文件')
  29. }
  30. const timestamp = Math.floor(Date.now() / 1000)
  31. const { timeStr, nonce } = getNonce()
  32. const formData = new FormData()
  33. // ThinkJS 2 会在控制器 __before() 中先校验公共参数,因此这些字段
  34. // 必须位于文件字段之前,否则 multipart 解析时会误报“client必填”。
  35. formData.append('token', tokenCookie.value || '')
  36. formData.append('uid', openIdCookie.value || '')
  37. formData.append('mac', macCookie.value || '')
  38. formData.append('base_timestamp', timestamp)
  39. formData.append('client', String(getClientId()))
  40. formData.append('source', sourceCookie.value || '')
  41. formData.append('client_ios', String(getClientIos()))
  42. formData.append('version', '1.0.0')
  43. formData.append('version_code', '1')
  44. formData.append('upload_type', '1')
  45. formData.append('file', file)
  46. // 走 /api 代理,对齐 V2
  47. const url = '/api/common/uploadpic'
  48. try {
  49. const result = await $fetch(url, {
  50. method: 'POST',
  51. headers: {
  52. token: tokenCookie.value || '',
  53. timestr: timeStr,
  54. nonce,
  55. },
  56. body: formData,
  57. // 不设置 Content-Type,让浏览器自动设置 multipart boundary
  58. })
  59. if (result.code === 0) {
  60. return result
  61. }
  62. if (result.code === 1009) {
  63. const { logout } = useUser()
  64. logout()
  65. const showLogin = useState('showLogin', () => false)
  66. showLogin.value = true
  67. }
  68. throw new Error(result.msg || '上传失败')
  69. } catch (e) {
  70. if (e.data) {
  71. const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data
  72. if (data && data.code !== undefined) {
  73. throw new Error(data.msg || '上传失败')
  74. }
  75. }
  76. throw e
  77. }
  78. }
  79. /**
  80. * 打开文件选择器并上传
  81. */
  82. function selectAndUpload(options = {}) {
  83. const { accept = 'image/*' } = options
  84. return new Promise((resolve, reject) => {
  85. const input = document.createElement('input')
  86. input.type = 'file'
  87. input.accept = accept
  88. input.onchange = async () => {
  89. const file = input.files[0]
  90. if (!file) {
  91. reject(new Error('未选择文件'))
  92. return
  93. }
  94. try {
  95. const result = await uploadImage(file)
  96. resolve(result)
  97. } catch (e) {
  98. reject(e)
  99. }
  100. }
  101. input.click()
  102. })
  103. }
  104. return {
  105. uploadImage,
  106. selectAndUpload,
  107. }
  108. }