feat(contract): 接入移动端合同打印与小程序文档交付以支持导出分享。

This commit is contained in:
wuzilong committed 2026-10-31 22:34:11 +08:00
1 parent 0cd120ef5e
commit 13630045df
16 files changed
+3666 -4

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
/** 不依赖小程序中可能不存在的 URL 构造器,不接受任意下载主机。 */
export function validateContractDocument(query, storageBase) {
const url = String(query?.url || '')
const type = String(query?.type || '').toLowerCase()
const base = String(storageBase || '').replace(/\/$/, '')
if (!['pdf', 'docx'].includes(type)) throw new Error('不支持的文档格式')
if (!/^https?:\/\/[^/@?#]+(?:\/[^?#]*)?$/.test(base) || !url.startsWith(base + '/')) {
throw new Error('无效的文档地址')
}
const path = url.split(/[?#]/)[0]
let decoded
try { decoded = decodeURIComponent(path) } catch { throw new Error('无效的文档地址') }
if (/[\\\s\x00-\x1f]/.test(url) || decoded.split('/').some((part) => part === '..' || part === '.')) {
throw new Error('无效的文档地址')
}
return { url, type, name: String(query?.name || `合同.${type}`) }
}
/** 保留微信下载错误用于排查,但不展示含临时签名的文件地址。 */
export function contractDownloadErrorMessage(error, url) {
const raw = String(error?.errMsg || '')
const origin = String(url || '').match(/^https?:\/\/[^/?#]+/)?.[0] || '文件存储域名'
if (/url not in domain list|domain.*not.*allowed|域名.*不.*合法/i.test(raw)) {
return `下载被微信拦截:请管理员将 ${origin} 加入小程序的 downloadFile 合法域名,更新配置后重新进入小程序。`
}
const detail = raw.replace(/https?:\/\/[^\s]+/gi, '[下载地址]')
.replace(/[\r\n\t]/g, ' ').replace(/^downloadFile:fail\s*/i, '').trim().slice(0, 180)
const code = error?.errno ?? error?.errCode
const codeText = Number.isFinite(Number(code)) && code != null ? `(错误码 ${Number(code)})` : ''
return detail ? `下载失败:${detail}${codeText}` : `下载失败${codeText},请检查网络或稍后重试`
}