- meetingId 由 DB 自增改为应用赋值 10 位数字 (yyMMdd + 4 位序列, Redis 按日期计数) - 会议开始时间不能晚于结束时间校验; 参会人表单身份证附件/角色单独一行 - 会议详情左右列比例 7:3 -> 8:2 (材料审核列收窄) - admin/workbench 用户总数/角色类型数/各角色分布统计修复 (改用 listByRole 按 role_type 过滤) - 新增 admin 用户管理接口 + 门户页脚 + H5 签到/上传优化
80 lines
3.0 KiB
JavaScript
80 lines
3.0 KiB
JavaScript
// OSS 直传工具 - 配合后端 /common/oss/sign
|
|
// 流程: 1) GET /common/oss/sign?dir=xxx 拿签名
|
|
// 2) POST 到 OSS bucket, formData 携带 policy/signature/key/accessKeyId
|
|
// 3) 拿回 OSS URL 写回表单
|
|
import request from '@/utils/request'
|
|
|
|
/**
|
|
* 获取 OSS 上传签名
|
|
* @param {string} dir 上传目录 (如 'ry8080/invitation/')
|
|
* @returns {Promise<{host, dir, accessKeyId, policy, signature, expire}>}
|
|
*/
|
|
export async function getOssSign(dir) {
|
|
const res = await request({
|
|
url: '/common/oss/sign',
|
|
method: 'get',
|
|
params: { dir }
|
|
})
|
|
if (res.code !== 200) {
|
|
throw new Error(res.msg || 'OSS 签名失败')
|
|
}
|
|
return res
|
|
}
|
|
|
|
/**
|
|
* 直接上传 File 到 OSS bucket
|
|
* @param {File} file 要上传的文件
|
|
* @param {string} dir 上传目录
|
|
* @returns {Promise<string>} 完整 URL (https://bucket.xxx/key)
|
|
*/
|
|
// 根据文件扩展名返回正确的 MIME,避免 OSS 默认给 application/octet-stream
|
|
function getMimeByExt(name) {
|
|
const ext = (name || '').split('.').pop().toLowerCase()
|
|
const map = {
|
|
pdf: 'application/pdf',
|
|
doc: 'application/msword',
|
|
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
png: 'image/png',
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
gif: 'image/gif',
|
|
webp: 'image/webp'
|
|
}
|
|
return map[ext] || 'application/octet-stream'
|
|
}
|
|
|
|
export async function uploadToOss(file, dir) {
|
|
const sign = await getOssSign(dir)
|
|
// 原文件名进 OSS key: 原始名称_时间戳_随机串.原扩展名 (保留中文, 仅清洗 URL/路径危险字符)
|
|
// 这样上传控件能从 URL 里还原原名显示, 无需额外 name 字段/列
|
|
const rawName = file.name || 'file'
|
|
const dot = rawName.lastIndexOf('.')
|
|
const ext = (dot > 0 ? rawName.slice(dot) : '').replace(/[\\/:*?"<>|%\s]+/g, '')
|
|
const base = (dot > 0 ? rawName.slice(0, dot) : rawName)
|
|
.replace(/[\\/:*?"<>|%\s]+/g, '_') || 'file'
|
|
const key = sign.dir + base + '_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8) + ext
|
|
// 显式传 Content-Type,让 OSS 按此存文件元数据, 浏览器拿到 application/pdf 即可内嵌预览
|
|
const mime = getMimeByExt(file.name)
|
|
|
|
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', mime)
|
|
fd.append('file', file)
|
|
|
|
// ⚠️ 不要手动设置 Content-Type header,会让 multipart/form-data boundary 丢失
|
|
// FormData 会自动生成正确的 multipart 格式
|
|
const resp = await fetch(sign.host, {
|
|
method: 'POST',
|
|
body: fd
|
|
})
|
|
if (!resp.ok) {
|
|
throw new Error('OSS 上传失败: HTTP ' + resp.status)
|
|
}
|
|
// 返回 URL 时对路径逐段编码 (保留 / 分隔), 让含中文/特殊字符的 key 在所有下载场景下都是 ASCII 安全 URL
|
|
// OSS 对象 key 本身仍是原始值 (FormData 的 key 字段未编码), 下载时 OSS 会自动把 %XX 还原
|
|
return sign.host + '/' + key.split('/').map(encodeURIComponent).join('/')
|
|
} |