/** * 扫码拍照回传工具 (H5 only) * * 二维码把会议上下文塞进 URL query (ry-h5 hash 路由, base=/camera/): * http://host:8090/camera/#/pages/panorama/index?meetingId=1&subType=L_PANORAMA_FRONT&apiBase=http%3A%2F%2Fhost%3A5173%2Fdev-api * * - getCameraParams(): 从 hash 里解析 meetingId / subType / apiBase * - uploadCameraPhoto(base64): 拍照 Base64 → 直传 OSS → 回传 URL 到后端 /business/meetingMaterial/cameraUpload * * 公开上传, 无需 token (后端 SecurityConfig 已 permitAll + 白名单 subType 兜底). */ export type CameraParams = { apiBase: string meetingId: string subType: string } /** 从 hash 路由的 query 里解析会议上下文 */ export function getCameraParams(): CameraParams { const hash = window.location.hash || '' const qs = hash.includes('?') ? hash.split('?')[1] : '' const search = qs || (window.location.search || '').replace(/^\?/, '') const sp = new URLSearchParams(search) return { apiBase: sp.get('apiBase') || '', meetingId: sp.get('meetingId') || '', subType: sp.get('subType') || '', } } type OssSign = { code: number msg: string host: string dir: string accessKeyId: string policy: string signature: string expire: number } /** 拿 OSS 直传签名 (GET /common/oss/sign?dir=...) */ async function getOssSign(apiBase: string, dir: string): Promise { const url = `${apiBase}/common/oss/sign?dir=${encodeURIComponent(dir)}` const resp = await fetch(url) const data = (await resp.json()) as OssSign if (data.code !== 200) throw new Error(data.msg || 'OSS 签名失败') return data } /** Base64 JPEG → 直传 OSS bucket, 返回完整 URL */ async function uploadBase64ToOss(apiBase: string, base64: string, dir: string): Promise { const sign = await getOssSign(apiBase, dir) const key = sign.dir + Date.now() + '_' + Math.random().toString(36).slice(2, 8) + '.jpg' // data:image/jpeg;base64,... → Blob (fetch data URL 最省事, 免手写 base64 解码) const blobResp = await fetch(base64) const blob = await blobResp.blob() const fd = new FormData() fd.append('key', key) fd.append('policy', sign.policy) fd.append('OSSAccessKeyId', sign.accessKeyId) fd.append('signature', sign.signature) fd.append('success_action_status', '200') fd.append('Content-Type', 'image/jpeg') fd.append('file', blob, 'camera.jpg') const resp = await fetch(sign.host, { method: 'POST', body: fd }) if (!resp.ok) throw new Error('OSS 上传失败: HTTP ' + resp.status) return sign.host + '/' + key } /** 回传 OSS URL 数组到后端 (POST /business/meetingMaterial/cameraUpload) */ async function cameraUpload( apiBase: string, meetingId: string, subType: string, ossUrls: string[], extraOssUrls: string[] ): Promise { const resp = await fetch(`${apiBase}/business/meetingMaterial/cameraUpload`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ meetingId: Number(meetingId), subType, ossUrls, extraOssUrls }), }) const data = (await resp.json()) as { code: number; msg: string } if (data.code !== 200) throw new Error(data.msg || '照片回传失败') } /** 连拍多张: 每张 sharp + 可选高斯模糊版 (签到表). 批量直传 OSS 后一次性回传数组存库. */ export type CameraShot = { sharp: string; blurred: string } /** 拍照后调用 (单张): 直传 OSS + 回传 URL 存库. 签到表额外上传高斯模糊版作为 extraOssUrl. */ export async function uploadCameraPhoto(base64: string, blurredBase64: string): Promise { const p = getCameraParams() if (!p.apiBase) throw new Error('缺少 apiBase 参数') if (!p.meetingId) throw new Error('缺少 meetingId 参数') if (!p.subType) throw new Error('缺少 subType 参数') const dir = `ry8080/meeting/${p.meetingId}/camera/` const ossUrl = await uploadBase64ToOss(p.apiBase, base64, dir) // 签到表: 额外上传脱敏版 (A4 70%~85% 高斯模糊), sponsor 只看这个隐藏手机号/身份证号 let extraOssUrl = '' if (p.subType === 'L_SIGN_IN' && blurredBase64) { extraOssUrl = await uploadBase64ToOss(p.apiBase, blurredBase64, dir + 'masked/') } await cameraUpload(p.apiBase, p.meetingId, p.subType, [ossUrl], extraOssUrl ? [extraOssUrl] : []) } /** 拍照后调用 (连拍多张): 每张直传 OSS + 签到表额外上传高斯模糊版, 一次性回传数组存库. */ export async function uploadCameraPhotos(list: CameraShot[]): Promise { const p = getCameraParams() if (!p.apiBase) throw new Error('缺少 apiBase 参数') if (!p.meetingId) throw new Error('缺少 meetingId 参数') if (!p.subType) throw new Error('缺少 subType 参数') if (!list || !list.length) throw new Error('没有可上传的照片') const dir = `ry8080/meeting/${p.meetingId}/camera/` const ossUrls: string[] = [] const extraOssUrls: string[] = [] for (const s of list) { ossUrls.push(await uploadBase64ToOss(p.apiBase, s.sharp, dir)) if (p.subType === 'L_SIGN_IN' && s.blurred) { extraOssUrls.push(await uploadBase64ToOss(p.apiBase, s.blurred, dir + 'masked/')) } else { extraOssUrls.push('') } } await cameraUpload(p.apiBase, p.meetingId, p.subType, ossUrls, extraOssUrls) }