/** * 附件上传到 DMS 的文件服务(`POST {DMS}/file/uploadFile`)。 * * **实测契约(2026-09-23,打真实服务)**: * - `multipart/form-data`,**五个字段都必填**: * `file` / `type` / `columnId` / `contentId` / `paraName` —— * 少任何一个都返回 `{"code":-1,"message":"缺少必传字段"}` * - `type` 决定**存储目录**:`0` 图 → `/static/photo/…`;`1` 音 / `2` 视 / `3` 文件 → `/static/file/…` * - 成功返回 `{"code":200,"message":"上传成功!","content":"/static/photo/1889-c_question-.png"}` * —— **`content` 就是那个相对路径** * - ⚠️ 返回的相对路径**当前直接取不到**(10081 / 10011 都 404)—— * 所以聊天里的缩略图**不能拿它当 `` 的 src**,预览要用本地 blob(见上传模块) * - ⚠️ 走 `/dms-api` 代理:页面是 HTTPS、接口是 HTTP,直连会被混合内容拦掉 * (代理已注入 DMS token,实测这个端点不校验 token 也无妨) * * 这个相对路径要放进聊天请求体的 `pictures` / `files` 里(用户 2026-09-23 指定)。 */ import { getDmsApiBaseUrl } from "@/utils/runtime-config"; /** * 上传时随文件一起提交的三个 DMS 定位字段(**会进文件名**)。 * * 取值由用户 2026-09-23 指定:附件按「属于 1889 助手问答记录的问题内容」落库 —— * 会话/问答记录都在 1889,附件跟着问题走。 */ export const DMS_ATTACHMENT_COLUMN_ID = 1889; export const DMS_ATTACHMENT_CONTENT_ID = 0; export const DMS_ATTACHMENT_PARA_NAME = "c_question"; /** `type`:0 图 · 1 音 · 2 视 · 3 文件(实测 0 与 3 的差别就是落在 photo/ 还是 file/ 目录) */ export const DMS_ATTACHMENT_TYPE_IMAGE = "0"; export const DMS_ATTACHMENT_TYPE_FILE = "3"; /** * 把上传返回的**相对路径**变成一个**可显示**的地址(用于聊天里的缩略图预览)。 * * 实测(2026-09-23):相对路径本身取不到(10081 / 10011 的根路径都 404), * 但**在 DMS 主机前加上 `/dms` 就能取到** —— * `http://121.43.55.7:10081/dms/static/photo/….png` → **200**。 * 而 `/dms-api` 代理补的正好就是 `/dms`,所以这里直接拼代理前缀即可。 * * ⚠️ 存进消息里的就是这个地址:它依赖代理前缀保持不变; * 发给**后端**的仍是相对路径(见 `toChatAttachmentUrls` 用的 `fileUrl` 字段),两者不要混。 */ export const buildDmsFileDisplayUrl = (relativePath: string): string => { const path = String(relativePath || "").trim(); if (!path) return ""; if (/^https?:\/\//i.test(path)) return path; // 已经是绝对地址就原样返回 const base = getDmsApiBaseUrl(); return base ? `${base}${path.startsWith("/") ? "" : "/"}${path}` : ""; }; /** * 上传一个附件,返回**相对路径**(如 `/static/photo/1889-c_question-.png`)。 * 任何失败都抛 `Error`(消息可直接给用户看),由调用方更新上传项状态。 */ export const uploadDmsAttachment = async ( file: File, options: { isImage: boolean; signal?: AbortSignal } ): Promise => { const base = getDmsApiBaseUrl(); if (!base) { throw new Error("未配置 DMS 接口地址,无法上传附件"); } const form = new FormData(); form.append("file", file); form.append("type", options.isImage ? DMS_ATTACHMENT_TYPE_IMAGE : DMS_ATTACHMENT_TYPE_FILE); form.append("columnId", String(DMS_ATTACHMENT_COLUMN_ID)); form.append("contentId", String(DMS_ATTACHMENT_CONTENT_ID)); form.append("paraName", DMS_ATTACHMENT_PARA_NAME); const response = await fetch(`${base}/file/uploadFile`, { method: "POST", body: form, signal: options.signal, }); if (!response.ok) { throw new Error(`上传失败(HTTP ${response.status})`); } const payload = await response.json().catch(() => null); const path = String(payload?.content ?? "").trim(); if (payload?.code !== 200 || !path) { throw new Error(String(payload?.message || "上传失败")); } return path; };