Files
zkxw/web-admin/src/utils/oa-form-create-upload.ts
T
2026-07-23 18:01:11 +08:00

170 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* FormCreate 运行时补丁:
* - 内置 upload 默认 action 为「/」,会 POST 到前端原点导致 404;接管为附件上传 API
* - 运行时注册 FcRow(flex ._fd-row),间距与铺满由 oa-fc-runtime.scss 处理
* - 覆盖内置 upload:picture-card 下非图片用文档/视频图标展示,避免破图
*/
import { ElMessage } from 'element-plus'
import { fetchUploadAttachment } from '@/api/business'
import { attachmentFullUrl } from '@/composables/useAttachmentActions'
import OaFcRow from '@/components/oa-approval/OaFcRow.vue'
import OaFcUpload from '@/components/oa-approval/OaFcUpload.vue'
type FcFetchOptions = {
source?: string
file?: File
filename?: string
data?: Record<string, unknown>
action?: string
onSuccess?: (res: unknown) => void
onError?: (err: unknown) => void
[key: string]: unknown
}
const FLAG = '__oaUploadConfigured'
const UPLOAD_TYPES = new Set(['upload', 'fcUpload'])
/** 与业务表单 `.form-grid-2` 横向间距对齐(顶层 option.row.gutter / CSS --oa-fc-gutter) */
export const OA_FC_DEFAULT_GUTTER = 20
/** 从上传接口响应中取出可回显的 url(兼容 FormCreate 默认 onSuccess) */
export function resolveUploadResponseUrl(res: any): string {
const raw = String(
res?.data?.url || res?.data?.fileUrl || res?.fileUrl || res?.url || ''
).trim()
if (!raw) return ''
return attachmentFullUrl(raw) || raw
}
function ensureUploadRule(rule: any) {
if (!UPLOAD_TYPES.has(String(rule.type || ''))) return
const props = { ...(rule.props || {}) }
if (!props.action || props.action === '/') {
props.action = '#'
}
const prevSuccess = props.onSuccess
// `$FNX:...` 须留给 FormCreate 编译;替换成函数会丢掉官方 file.url = res.data.url
if (typeof prevSuccess === 'string' && prevSuccess.includes('$FNX')) {
// keep
} else if (typeof prevSuccess === 'function') {
props.onSuccess = (res: any, file: any) => {
try {
prevSuccess(res, file)
} catch {
/* ignore */
}
const url = resolveUploadResponseUrl(res)
// Element Plus 选文件时已写入 blob: URL,必须强制覆盖
if (url && file) {
file.url = url
}
}
} else if (!prevSuccess) {
props.onSuccess = (res: any, file: any) => {
const url = resolveUploadResponseUrl(res)
if (url && file) {
file.url = url
}
}
}
rule.props = props
}
/**
* 运行时补齐 FormCreate rule(upload action / onSuccess)
*/
export function ensureOaUploadRuleProps(rules: unknown): any[] {
if (!Array.isArray(rules)) return []
return rules.map((raw) => {
if (!raw || typeof raw !== 'object') return raw
const rule: any = { ...(raw as object) }
ensureUploadRule(rule)
if (Array.isArray(rule.children)) {
rule.children = ensureOaUploadRuleProps(rule.children)
}
if (Array.isArray(rule.props?.rule)) {
rule.props = {
...rule.props,
rule: ensureOaUploadRuleProps(rule.props.rule)
}
}
if (Array.isArray(rule.control)) {
rule.control = rule.control.map((c: any) => {
if (!c || typeof c !== 'object') return c
if (Array.isArray(c.rule)) {
return { ...c, rule: ensureOaUploadRuleProps(c.rule) }
}
return c
})
}
return rule
})
}
export function setupFormCreateUpload(formCreate: {
fetch?: (opts: FcFetchOptions) => void
component?: (name: string, component: unknown) => void
}) {
if (!formCreate || (formCreate as any)[FLAG]) return
// 主应用 formCreate 默认未挂设计器 FcRow;用 flex 行保证栅格铺满,间距走 CSS
if (typeof formCreate.component === 'function') {
formCreate.component('fcRow', OaFcRow)
formCreate.component('FcRow', OaFcRow)
// 覆盖默认 fcUpload:非图片不再走 <img> 破图
formCreate.component('upload', OaFcUpload)
formCreate.component('fcUpload', OaFcUpload)
}
const prevFetch = formCreate.fetch
formCreate.fetch = (options: FcFetchOptions) => {
const isUpload = options?.source === 'upload' || !!options?.file
if (!isUpload) {
if (typeof prevFetch === 'function') {
return prevFetch(options)
}
return
}
void (async () => {
try {
const file = options.file
if (!file) {
throw new Error('未选择文件')
}
const fd = new FormData()
fd.append('file', file)
fd.append('folder', 'oa-approval')
const extra = options.data
if (extra && typeof extra === 'object') {
Object.entries(extra).forEach(([k, v]) => {
if (v != null && k !== 'file' && k !== 'folder') {
fd.append(k, String(v))
}
})
}
const res: any = await fetchUploadAttachment(fd)
const rawUrl = String(res?.fileUrl || res?.data?.fileUrl || '').trim()
if (!rawUrl) {
throw new Error('上传失败:未返回文件地址')
}
// 预览用完整 URL;设计器默认 onSuccess 为 file.url = res.data.url
const url = attachmentFullUrl(rawUrl) || rawUrl
options.onSuccess?.({
data: { url, fileUrl: rawUrl },
fileUrl: rawUrl,
url
})
} catch (e) {
const msg = e instanceof Error ? e.message : '上传失败'
ElMessage.error(msg)
options.onError?.(e)
}
})()
}
;(formCreate as any)[FLAG] = true
}