import { File, isAndroid, isEngine, NodeInterface, Plugin, READY_CARD_KEY, getExtensionName, PluginOptions, CARD_VALUE_KEY, decodeCardValue, encodeCardValue, RequestData, RequestHeaders } from "@aomao/engine"; import AudioComponent, { AudioValue } from "./component"; export interface Options extends PluginOptions { /** * 音频上传地址 */ action: string; /** * 数据返回类型,默认 json */ type?: "*" | "json" | "xml" | "html" | "text" | "js"; /** * 音频文件上传时 FormData 的名称,默认 file */ name?: string; /** * 额外携带数据上传 */ data?: RequestData /** * 请求类型,默认 multipart/form-data; */ contentType?: string; /** * 是否跨域 */ crossOrigin?: boolean; /** * 请求头 */ headers?: RequestHeaders; /** * 文件接收的格式,默认 "*" */ accept?: string | Array; /** * 文件选择限制数量 */ multiple?: boolean | number; /** * 上传大小限制,默认 1024 * 1024 * 5 就是5M */ limitSize?: number; /** * 解析上传后的Respone,返回 result:是否成功,data:成功:{id:音频唯一标识,url:音频地址},失败:错误信息 */ parse?: (response: any) => { result: boolean; data: | { url: string; id?: string; status?: "uploading" | "transcoding" | "done" | "error"; } | string; }; /** * 查询地址 */ query?: { /** * 查询地址 */ action: string; /** * 数据返回类型,默认 json */ type?: "*" | "json" | "xml" | "html" | "text" | "js"; /** * 额外携带数据上传 */ data?: RequestData; /** * 请求类型,默认 multipart/form-data; */ contentType?: string; }; } export default class extends Plugin { private cardComponents: { [key: string]: AudioComponent } = {}; static get pluginName() { return "audio-uploader"; } extensionNames = ["mp3"]; init() { if (isEngine(this.editor)) { this.editor.on("drop:files", (files) => this.dropFiles(files)); this.editor.on("paste:event", ({ files }) => this.pasteFiles(files)); this.editor.on("paste:each", (node) => this.pasteEach(node)); } let { accept } = this.options; const names: Array = []; if (typeof accept === "string") accept = accept.split(","); (accept || []).forEach((name) => { name = name.trim(); const newName = name.split(".").pop(); if (newName) names.push(newName); }); if (names.length > 0) this.extensionNames = names; } isAudio(file: File) { const name = getExtensionName(file); return this.extensionNames.indexOf(name) >= 0; } async execute(files?: Array | MouseEvent | string, ...args: any) { if (typeof files === "string") { switch (files) { case "query": return this.query(args[0], args[1], args[2]); } return; } const { request, card, language } = this.editor; const { action, data, type, contentType, multiple, crossOrigin, headers, name, } = this.options; const { parse } = this.options; const limitSize = this.options.limitSize || 5 * 1024 * 1024; if (!Array.isArray(files)) { files = await request.getFiles({ event: files, accept: isAndroid ? "audio/*" : this.extensionNames.length > 0 ? "." + this.extensionNames.join(",.") : "", multiple, }); } if (files.length === 0) return; request.upload( { url: action, data, type, contentType, crossOrigin, headers, onBefore: (file) => { if (file.size > limitSize) { this.editor.messageError('upload-limit', language .get("audio", "uploadLimitError") .toString() .replace("$size", (limitSize / 1024 / 1024).toFixed(0) + "M") ); return false; } return true; }, onReady: (fileInfo) => { if (!isEngine(this.editor) || this.cardComponents[fileInfo.uid]) return; const component = card.insert("audio", { status: "uploading", name: fileInfo.name, size: fileInfo.size, }) as AudioComponent; this.cardComponents[fileInfo.uid] = component; }, onUploading: (file, { percent }) => { const component = this.cardComponents[file.uid || ""]; if (!component) return; component.setProgressPercent(percent); }, onSuccess: (response, file) => { const component = this.cardComponents[file.uid || ""]; if (!component) return; const id: string = response.id || (response.data && response.data.id); const url: string = response.url || (response.data && response.data.url); const cover: string = response.cover || (response.data && response.data.cover); const download: string = response.download || (response.data && response.data.download); let status: "uploading" | "transcoding" | "done" | "error" = response.status || (response.data && response.data.status); status = status === "transcoding" ? "transcoding" : "done"; let result: { result: boolean; data: | { url: string; audio_id?: string; cover?: string; download?: string; status?: "uploading" | "transcoding" | "done" | "error"; } | string; } = { result: true, data: { audio_id: id, url, cover, download, status, }, }; if (parse) { const customizeResult = parse(response); if (customizeResult.result) { let data = result.data as { url: string; audio_id?: string; cover?: string; download?: string; status?: "uploading" | "transcoding" | "done" | "error"; }; if (typeof customizeResult.data === "string") result.data = { ...data, url: customizeResult.data, }; else { data.url = customizeResult.data.url; if (customizeResult.data.status !== undefined) data = { ...data, status: customizeResult.data.status, }; if (customizeResult.data.id !== undefined) data = { ...data, audio_id: customizeResult.data.id, }; result.data = { ...data }; } } else { result = { result: false, data: customizeResult.data.toString(), }; } } else if (!url) { result = { result: false, data: response.data }; } //失败 if (!result.result) { card.update(component.id, { status: "error", message: (result.data as string) || this.editor.language.get("audio", "uploadError"), }); } //成功 else { this.editor.card.update( component.id, typeof result.data === "string" ? { url: result.data } : { ...result.data, } ); } delete this.cardComponents[file.uid || ""]; }, onError: (error, file) => { const component = this.cardComponents[file.uid || ""]; if (!component) return; card.update(component.id, { status: "error", message: error.message || this.editor.language.get("audio", "uploadError"), }); delete this.cardComponents[file.uid || ""]; }, }, files, name ); return; } query( audio_id: string, success: (data?: { url: string; name?: string; cover?: string; download?: string; status?: string; }) => void, failed: (message: string) => void = () => { return; } ) { const { request } = this.editor; const { query, parse } = this.options; if (!query || !audio_id) return success(); const { action, type, contentType, data } = query; request.ajax({ url: action, contentType: contentType || "", type: type === undefined ? "json" : type, data: typeof data === 'function' ? async () => { const newData = data() return { ...newData, id: audio_id, } }: { ...data, id: audio_id, }, success: (response: any) => { const { result, data } = response; if (!result) { failed(data); } else { const result = parse ? parse(response) : response; if (result.result === false) { failed( result.data || this.editor.language.get("audio", "loadError") ); } else success({ ...result.data, status: result.data.status !== "transcoding" ? "done" : "transcoding", }); } }, error: (error) => { failed(error.message || this.editor.language.get("audio", "loadError")); }, method: "GET", }); } dropFiles(files: Array) { if (!isEngine(this.editor)) return; files = files.filter((file) => this.isAudio(file)); if (files.length === 0) return; this.editor.command.execute("audio-uploader", files); return false; } pasteFiles(files: Array) { if (!isEngine(this.editor)) return; files = files.filter((file) => this.isAudio(file)); if (files.length === 0) return; this.editor.command.execute( "audio-uploader", files.filter((file) => this.isAudio(file)), files ); return false; } pasteEach(node: NodeInterface) { //是卡片,并且还没渲染 if (node.isCard() && node.attributes(READY_CARD_KEY)) { if (node.attributes(READY_CARD_KEY) !== "audio") return; const value = decodeCardValue(node.attributes(CARD_VALUE_KEY)); if (!value || !value.url) { node.remove(); return; } if (value.status === "uploading") { //如果是上传状态,设置为正常状态 value.percent = 0; node.attributes( CARD_VALUE_KEY, encodeCardValue({ ...value, status: "done" }) ); } return; } } }