// 服务器地址(与 request.js 保持一致) const baseUrl = 'http://192.168.1.222:8080'; /** * 图片上传 Hook * 支持 uniapp 和微信小程序 * 解决编辑回显后重新上传的问题 */ import { reactive } from 'vue' /** * @param {Object} options 配置 * @param {string} options.uploadUrl - 上传接口地址 * @param {string} options.fileKey - 上传文件的字段名,默认 'file' * @returns {Object} */ export function useImageUpload(options = {}) { const { uploadUrl = '/api/resident/file/uploadImage', fileKey = 'file' } = options // 使用 reactive 对象存储所有状态 const state = reactive({ // 当前显示的图片(前端展示用,支持 base64/blob URL/网络URL) previewUrl: '', // 服务器返回的 URL(提交时使用) serverUrl: '', // 临时的文件路径(重新上传时用) tempFilePath: null, // 上传状态 uploading: false }) /** * 设置回显图片(编辑时从后端获取) * @param {string} url - 后端返回的图片URL */ const setPreviewUrl = (url) => { if (url) { // 确保 url 是字符串 const urlStr = typeof url === 'string' ? url : (url?.url || String(url)) state.previewUrl = urlStr state.serverUrl = urlStr state.tempFilePath = null // 重置临时文件,表示没有重新上传 } } /** * 选择图片并预览(不上传) * @returns {Promise} 返回临时文件路径 */ const chooseImage = () => { return new Promise((resolve, reject) => { uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['camera', 'album'], success: (res) => { const path = res.tempFilePaths?.[0] if (!path) { reject(new Error('选择图片失败')) return } // 确保 path 是字符串 const pathStr = typeof path === 'string' ? path : String(path) // 设置预览 state.previewUrl = pathStr // 记录临时文件路径 state.tempFilePath = pathStr // 置空服务器URL,等实际上传后再获取 state.serverUrl = '' resolve(pathStr) }, fail: (err) => { reject(err) } }) }) } /** * 上传图片到服务器 * @returns {Promise} 返回 { url: string } */ const uploadImage = () => { // 如果没有新选择临时文件,直接返回已有的服务器URL if (!state.tempFilePath) { return Promise.resolve({ url: state.serverUrl }) } // 如果已经有服务器URL且没有重新选择,也直接返回 if (state.serverUrl && !state.tempFilePath) { return Promise.resolve({ url: state.serverUrl }) } state.uploading = true return new Promise((resolve, reject) => { // 确保 filePath 是字符串 const filePath = state.tempFilePath if (!filePath || typeof filePath !== 'string') { state.uploading = false reject(new Error('文件路径无效')) return } uni.uploadFile({ url: baseUrl + uploadUrl, filePath: filePath, name: fileKey, header: { 'Authorization': 'Bearer ' + (uni.getStorageSync('token') || ''), 'clientid': 'resident' }, success: (res) => { try { const data = JSON.parse(res.data) if (data.code === 200 && data.data) { // 更新服务器URL(确保存的是字符串) const url = typeof data.data === 'string' ? data.data : (data.data?.url || '') state.serverUrl = url // 清空临时文件 state.tempFilePath = null resolve({ url }) } else { uni.showToast({ title: data.msg || '上传失败', icon: 'none' }) reject(new Error(data.msg || '上传失败')) } } catch (e) { uni.showToast({ title: '上传失败', icon: 'none' }) reject(e) } }, fail: (err) => { uni.showToast({ title: err.message || '上传失败', icon: 'none' }) reject(err) }, complete: () => { state.uploading = false } }) }) } /** * 获取最终可提交的URL * 如果有新上传的图片,先上传再返回URL * 如果没有新上传,直接返回已有的服务器URL * @returns {Promise} */ const getSubmitUrl = async () => { // 如果重新选择了新图片,先上传 if (state.tempFilePath) { return await uploadImage() } // 否则返回已有的服务器URL return { url: state.serverUrl } } /** * 重置状态 */ const reset = () => { state.previewUrl = '' state.serverUrl = '' state.tempFilePath = null state.uploading = false } // 返回 reactive state 和方法 // state 作为 reactive 对象,在模板中通过 idCardFrontUpload.state.previewUrl 访问 return { state, setPreviewUrl, chooseImage, uploadImage, getSubmitUrl, reset } }