- meetingId 由 DB 自增改为应用赋值 10 位数字 (yyMMdd + 4 位序列, Redis 按日期计数) - 会议开始时间不能晚于结束时间校验; 参会人表单身份证附件/角色单独一行 - 会议详情左右列比例 7:3 -> 8:2 (材料审核列收窄) - admin/workbench 用户总数/角色类型数/各角色分布统计修复 (改用 listByRole 按 role_type 过滤) - 新增 admin 用户管理接口 + 门户页脚 + H5 签到/上传优化
109 lines
3.9 KiB
TypeScript
109 lines
3.9 KiB
TypeScript
/**
|
|
* 扫码拍照回传工具 (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<OssSign> {
|
|
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<string> {
|
|
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,
|
|
ossUrl: string,
|
|
extraOssUrl: string
|
|
): Promise<void> {
|
|
const resp = await fetch(`${apiBase}/business/meetingMaterial/cameraUpload`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ meetingId: Number(meetingId), subType, ossUrl, extraOssUrl }),
|
|
})
|
|
const data = (await resp.json()) as { code: number; msg: string }
|
|
if (data.code !== 200) throw new Error(data.msg || '照片回传失败')
|
|
}
|
|
|
|
/** 拍照后调用: 直传 OSS + 回传 URL 存库. 签到表额外上传高斯模糊版作为 extraOssUrl. */
|
|
export async function uploadCameraPhoto(base64: string, blurredBase64: string): Promise<void> {
|
|
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)
|
|
}
|