/** * 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 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:非图片不再走 破图 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 }