|
- /**
- * 图片上传 composable,完全对齐 ai_uniapp_v2/utils/upload.js
- *
- * - POST multipart/form-data 到 /api/common/uploadpic(走服务端代理)
- * - Header: { token, timestr, nonce }
- * - FormData: { file, token, mac, client, source, version, version_code, upload_type:1 }
- */
- import md5 from '~/utils/md5'
-
- export function useUpload() {
- const macCookie = useCookie('mac', { maxAge: 365 * 24 * 3600, path: '/' })
- const tokenCookie = useCookie('token', { path: '/' })
- const openIdCookie = useCookie('open_id', { path: '/' })
- const sourceCookie = useCookie('source', { path: '/' })
- const { getClientId, getClientIos } = useClientDevice()
-
- function getNonce() {
- const timeStr = Date.now().toString()
- const str = md5(timeStr)
- const nonce = str.substring(4, 14)
- return { timeStr, nonce }
- }
-
- /**
- * 上传图片
- * @param {File} file - 浏览器 File 对象
- * @returns {Promise<{code: number, data: {url: string, width: number, height: number}, msg: string}>}
- */
- async function uploadImage(file) {
- if (!file) {
- throw new Error('请选择文件')
- }
-
- const timestamp = Math.floor(Date.now() / 1000)
- const { timeStr, nonce } = getNonce()
- const formData = new FormData()
-
- // ThinkJS 2 会在控制器 __before() 中先校验公共参数,因此这些字段
- // 必须位于文件字段之前,否则 multipart 解析时会误报“client必填”。
- formData.append('token', tokenCookie.value || '')
- formData.append('uid', openIdCookie.value || '')
- formData.append('mac', macCookie.value || '')
- formData.append('base_timestamp', timestamp)
- formData.append('client', String(getClientId()))
- formData.append('source', sourceCookie.value || '')
- formData.append('client_ios', String(getClientIos()))
- formData.append('version', '1.0.0')
- formData.append('version_code', '1')
- formData.append('upload_type', '1')
- formData.append('file', file)
-
- // 走 /api 代理,对齐 V2
- const url = '/api/common/uploadpic'
-
- try {
- const result = await $fetch(url, {
- method: 'POST',
- headers: {
- token: tokenCookie.value || '',
- timestr: timeStr,
- nonce,
- },
- body: formData,
- // 不设置 Content-Type,让浏览器自动设置 multipart boundary
- })
-
- if (result.code === 0) {
- return result
- }
-
- if (result.code === 1009) {
- const { logout } = useUser()
- logout()
- const showLogin = useState('showLogin', () => false)
- showLogin.value = true
- }
-
- throw new Error(result.msg || '上传失败')
- } catch (e) {
- if (e.data) {
- const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data
- if (data && data.code !== undefined) {
- throw new Error(data.msg || '上传失败')
- }
- }
- throw e
- }
- }
-
- /**
- * 打开文件选择器并上传
- */
- function selectAndUpload(options = {}) {
- const { accept = 'image/*' } = options
- return new Promise((resolve, reject) => {
- const input = document.createElement('input')
- input.type = 'file'
- input.accept = accept
- input.onchange = async () => {
- const file = input.files[0]
- if (!file) {
- reject(new Error('未选择文件'))
- return
- }
- try {
- const result = await uploadImage(file)
- resolve(result)
- } catch (e) {
- reject(e)
- }
- }
- input.click()
- })
- }
-
- return {
- uploadImage,
- selectAndUpload,
- }
- }
|