import { useAuthStore } from '~/stores/auth' interface ApiResponse { code: number msg: string data: T } async function request( url: string, body: unknown, extraHeaders?: Record ): Promise { const auth = useAuthStore() const router = useRouter() const headers: Record = { 'Content-Type': 'application/json', } if (auth.token) { headers['Authorization'] = auth.token } if (extraHeaders) { Object.assign(headers, extraHeaders) } const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body), }) if (!response.ok) { throw new Error(`HTTP error: ${response.status}`) } const result: ApiResponse = await response.json() if (result.code === 18) { auth.logout() router.push('/') throw new Error(result.msg || 'Unauthorized') } if (result.code !== 0) { throw new Error(result.msg || 'Unknown error') } return result.data } // 读操作前缀:以这些开头的 api 方法视为只读,不触发处理中遮罩;其余(add/update/del/save/create…)视为写操作。 const READONLY_RE = /^(get|list|load|query|fetch|search|count|stat|export|check)/i function isMutating(method: string): boolean { return !READONLY_RE.test(method) } export function useApi() { const auth = useAuthStore() const { begin, end } = useLoading() // setup 期捕获全局遮罩控制 // 写操作(addproduct/updateproduct/delproduct/addproductversion… 及厂家/账号等增删改) // 统一套全屏遮罩:请求期间挡住整页、拦截点击,从根上杜绝重复提交/误点。只读 getXxx 不遮罩。 async function webApi(method: string, data?: unknown): Promise { const extraHeaders: Record = {} if (auth.currentAppId) { extraHeaders['X-App-Id'] = String(auth.currentAppId) } if (!isMutating(method)) { return request(`/web/api/api_${method}`, data ?? {}, extraHeaders) } begin() try { return await request(`/web/api/api_${method}`, data ?? {}, extraHeaders) } finally { end() } } async function consoleApi(path: string, data?: unknown): Promise { return request(`/console/api/${path}`, data ?? {}) } // 文件直传:后端(api_getcostoken)按「第三方服务配置→存储→阿里云 OSS」签发一个预签名 PUT URL, // 浏览器用同样的 Content-Type PUT 直传,最终 URL(去签名 query)作为文件地址返回。 // 注意:OSS 预签名 PUT 把 Content-Type 计入签名,故这里 PUT 的 Content-Type 必须与提交给后端 // 签名的 content_type 完全一致;filename 也要一并提交,后端才能拼出完整 object key。 async function uploadFile(file: File, path: string): Promise { const contentType = file.type || 'application/octet-stream' const token = await webApi<{ upload_url?: string url?: string }>('getcostoken', { path, filename: file.name, content_type: contentType }) const uploadUrl = token.upload_url || token.url || '' if (!uploadUrl) { throw new Error('未获取到上传地址:请确认已在【第三方服务配置→存储】配置并启用「阿里云 OSS」') } const putResponse = await fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': contentType, }, body: file, }) if (!putResponse.ok) { throw new Error(`Upload failed: ${putResponse.status}`) } // 优先用后端给的裸 URL;兜底自己去掉签名 query。 return token.url || uploadUrl.split('?')[0] } return { webApi, consoleApi, uploadFile, } }