你现在正在访问的是萌娘百科用于保存和管理图片、音频等媒体文件的子项目萌娘共享,想要回到萌娘百科,请使用浏览器上的返回按钮、或点击这里
Widget:Uploader
AnnAngela(讨论 | 贡献)2022年2月20日 (日) 11:20的版本 (创建页面,内容为“<script> // <pre> // 引自User:東東君/js/uploader.js "use strict"; $(() => (async () => { if (mw.config.get("wgPageName") !== "MediaWiki:Uploader") {…”)
<script>
//
// 引自[[User:東東君/js/uploader.js]] "use strict"; $(() => (async () => { if (mw.config.get("wgPageName") !== "MediaWiki:Uploader") { return; } await mw.loader.using(["mediawiki.notify", "mediawiki.api"]); const commonsApi = new mw.Api({ timeout: 7000, }); const cdnUrl = { requireJs: "https://cdn.jsdelivr.net/npm/requirejs@2.3.6/require.min.js", jss: "https://cdn.jsdelivr.net/npm/jss@10.4.0/dist/jss.min.js", jssPreset: "https://cdn.jsdelivr.net/npm/jss-preset-default@10.4.0/dist/jss-preset-default.min.js", vue: "https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js", }; /** * @param {string} sourceUrl * @return {Promise<never>} */ const loadScript = (sourceUrl) => new Promise((resolve, reject) => { const scriptTag = document.createElement("script"); scriptTag.src = sourceUrl; document.body.appendChild(scriptTag); scriptTag.addEventListener("load", resolve); scriptTag.addEventListener("error", reject); }); /** * @param {{ [moduleName: string]: string }[]} modulePaths * @return {Promise<any>} */ const loadModules = (modulePaths) => new Promise((resolve, reject) => { const paths = Object.fromEntries(modulePaths.map((m) => Object.entries(m).map(([k, v]) => [k, v.replace(/\.js$/, "")])[0])); const loadModuleNames = modulePaths.map(item => Object.keys(item)[0]); window.require.config({ paths }); window.require(loadModuleNames, (...modules) => resolve(modules), reject); }); /** * @typedef {object} MwApiError * @property {string} code * @property {string} info */ /** * @typedef {object} JQueryXHRError * @property {number} status * @property {string} statusText */ /** * @param {string} word * @return {Promise<object, Error>} */ const getHints = (word) => commonsApi.post({ action: "query", format: "json", list: "search", srsearch: word, srnamespace: "14", srlimit: "20", }); /** * @param {object} options * @param {File} options.body * @param {string} options.fileName * @param {string} options.comment * @param {string} options.pageContent * @return {Promise<object, (Error | MwApiError | JQueryXHRError)>} */ async function upload({ body, fileName, comment, pageContent }) { const data = { filename: fileName, comment: comment, text: pageContent, action: "upload", format: "json", ignorewarnings: true, token: await commonsApi.getToken("csrf"), }; if (typeof body === "string") { data.url = body; } else { data.file = body; } const formData = new FormData(); Object.entries(data).forEach(([key, value]) => { formData.append(key, value); }); // 点名批评 mediawiki.api 没考虑过跨域传文件! return $.ajax({ url: `${mw.config.get("wgServer") + mw.config.get("wgScriptPath")}/api.php`, type: "post", timeout: 30000, xhrFields: { withCredentials: true }, contentType: false, processData: false, data: formData, }).promise().then((data) => { if ("error" in data) { throw data.error; } return data; }); } /** * @param {Error | MwApiError | JQueryXHRError} errorObject * @return {string} */ function errorInfo(errorObject) { if (errorObject instanceof Error) { return `${errorObject} ${errorObject.stack.split("\n")[1].trim()}`; } else if (typeof errorObject.status === "number") { return `[${errorObject.status}] ${errorObject.statusText}`; } return `${errorObject.code} - ${errorObject.info}`; } /** * @param {string[]} fileNames * @return {Promise<object, Error>} */ async function checkFileNames(fileNames) { const data = await commonsApi.post({ action: "query", format: "json", titles: fileNames.map(item => `File:${item}`).join("|"), prop: "", }); return Object.fromEntries(Object.values(data.query.pages).map((item) => [item.title.replace("File:", ""), !("missing" in item)])); } await loadScript(cdnUrl.requireJs); const [ { "default": jss }, { "default": jssPreset }, Vue, ] = await loadModules([ { jss: cdnUrl.jss }, { jssPreset: cdnUrl.jssPreset }, { Vue: cdnUrl.vue }, ]); jss.setup(jssPreset()); $(document.body).append('<div id="widget-fileUploader"></div>'); const template = ` <div id="widget-fileUploader" :class="s.container"> <input ref="fileInput" style="display:none" type="file" multiple="multiple" :accept="allowedFileTypes.map(item => '.' + item).join(',')" @change="addFileByFileSelector" @click="clearHint" /> <div :class="s.closeBtn" @click="hideWidget">×</div> <div :class="s.body"> <div :class="s.fileList" @dragenter.prevent="() => {}" @dragover.prevent="() => {}" @drop.prevent="addFileByDropping" > <div v-if="files.length === 0" key="hintMask" class="hintMask" @click="$refs.fileInput.click()" > <div class="hintText">点此添加文件,或将文件拖放至此</div> </div> <div v-for="(item, index) in files" :key="item.body.lastModified" class="item" :data-name="item.fileName" :data-selected="index === focusedFileIndex" title="单击选中文件,双击复制文件名" @click="focusFile(index)" > <img v-if="isImageFile(item.body)" :src="item.objectUrl" /> <div v-else class="unablePreviewHint"> <div>不支持预览的文件类型</div> <div v-if="typeof item.body !== 'string'" class="type" >Mimetype: {{ item.body.type }}</div> </div> <div class="removeBtn" @click.stop="files.splice(index, 1)">×</div> </div> <div v-if="files.length !== 0" class="item addFileBox" @click="$refs.fileInput.click()" /> </div> <div :class="s.panel"> <div class="block"> <div class="input-container" title="上传后使用文件时的名字,要求不能和现有文件重复"> <span>文件名:</span> <input v-model.trim="form.fileName" @click="clearHint" /> </div> <div class="input-container categoryInput" title="所有文件共享分类"> <span>分<span style="visibility: hidden;">一</span>类:</span> <input ref="categoryInput" v-model.trim="form.categoryInput" @input="loadCategoryHint" @keydown.enter="addCategory(form.categoryInput)" @keydown.up.prevent="handlerFor_categoryInput_wasKeyDowned" /> <div class="inputHint">按下回车添加分类</div> <div ref="categoryHints" v-if="categoryHints.length !== 0" class="categoryHints" tabindex="0" @keydown.enter="addCategory(categoryHints[categoryHintFocusedIndex])" @keydown.prevent="handlerFor_categoryHints_wasKeyDowned" > <div v-for="(item, index) in categoryHints" class="item" :data-selected="index === categoryHintFocusedIndex" @click="addCategory(item)" >{{ item }}</div> </div> </div> <div class="categories"> <div v-for="(item, index) in form.categories" class="item" title="点击删除分类" @click="form.categories.splice(index, 1)" >{{ item }}</div> </div> </div> <div class="block"> <div class="input-container"> <span>角色名:</span> <input v-model.trim="form.charaName" @click="clearHint" /> </div> <div class="input-container"> <span>作<span style="visibility: hidden;">一</span>者:</span> <input v-model.trim="form.author" @click="clearHint" /> </div> <div class="input-container"> <span>源地址:</span> <input v-model.trim="form.source" @click="clearHint" /> </div> </div> <div class="block" style="flex-direction:column; justify-content:space-around; align-items:flex-start;" > <div class="input-container" title="所有文件共享前缀"> <span>添加前缀:</span> <input v-model.trim="form.prefix" style="width:calc(100% - 6em)" @click="clearHint" /> </div> <div class="input-container" style="justify-content:flex-start;" > <select v-model.trim="form.license" @click="clearHint"> <option disabled="disabled" value="">选择授权协议(将鼠标放在选项上显示详情)</option> <optgroup label="CC协议"> <option value="CC Zero" title="作者授权以无著作权方式使用">CC-0</option> <option value="CC BY" title="作者授权以署名方式使用,该授权需兼容3.0协议">CC BY 3.0</option> <option value="CC BY-SA" title="作者授权以署名-相同方式方式使用,该授权需兼容3.0协议">CC BY-SA 3.0</option> <option value="CC BY-NC-SA" title="作者授权以署名-非商业使用-相同协议方式使用,该授权需兼容3.0协议">CC BY-NC-SA 3.0</option> </optgroup> <optgroup label="公有领域"> <option value="PD-Old">作者离世一定年限后流入公有领域</option> <option value="PD-Other">其他原因流入公有领域</option> </optgroup> <optgroup label="其他"> <option value="Copyright" title="原作者没有明确的授权声明">原作者保留权利</option> <option value="none:gotoCommons">原作者授权萌百使用</option> <option value="可自由使用" title="作者放弃版权或声明可自由使用">可自由使用</option> <option value="萌娘百科版权所有">萌娘百科版权所有</option> </optgroup> </select> </div> <div class="buttons" @click="clearHint"> <button @click="addSourceUrlFile">添加源地址文件</button> <button :disabled="status === 2" title="执行上传文件" @click="submit(false)">上传</button> <button :disabled="status === 2" title="在发生文件名已存在的情况时,自动滤掉已存在的文件。通常用于在上一次批量上传中一部分失败后,再次尝试将之前没传上去的文件重新上传" @click="submit(true)" >差分上传</button> <button title="将当前文件除文件名的信息同步到全部文件" @click="asyncCurrentFileInfo">同步文件信息</button> <button @click="showManual">使用说明</button> </div> </div> </div> </div> </div> `; const createStyles = () => jss.createStyleSheet({ container: { width: "100%", height: "100%", position: "fixed", top: 0, left: 0, backgroundColor: "rgba(0, 0, 0, 0.3)", zIndex: 100, }, closeBtn: { fontSize: 30, fontWeight: "bold", color: "white", fontFamily: "Simsun", position: "fixed", top: 10, right: 20, transition: "transform 0.3s", zIndex: 10001, cursor: "pointer", "&:hover": { transform: "rotate(90deg)", }, }, body: { minWidth: 800, maxWidth: 930, height: 500, backgroundColor: "white", borderRadius: 10, border: "5px #eee solid", position: "absolute", top: 0, left: 0, right: 0, bottom: 0, margin: "auto", }, fileList: { height: "70%", backgroundColor: "white", borderRadius: "10px 10px 0 0", position: "relative", borderBottom: "3px #ccc solid", boxSizing: "border-box", overflow: "auto", cursor: "pointer", paddingBottom: 10, "& .hintMask": { position: "absolute", width: "100%", height: "100%", top: 0, left: 0, "&::before, &::after": { content: '""', width: 40, height: 150, backgroundColor: "#ddd", position: "absolute", top: 0, left: 0, right: 0, bottom: 0, margin: "auto", }, "&::after": { width: 150, height: 40, }, "& > .hintText": { fontSize: 22, color: "#ddd", position: "absolute", left: "50%", transform: "translateX(-50%)", bottom: 30, whiteSpace: "nowrap", }, }, "& > .item": { width: 200, height: 150, boxSizing: "border-box", backgroundColor: "white", marginLeft: 10, marginTop: 10, border: "1px #ccc solid", display: "inline-block", position: "relative", cursor: "pointer", verticalAlign: "middle", "&.addFileBox": { "&::before, &::after": { content: '""', width: 15, height: 60, backgroundColor: "#ddd", position: "absolute", top: 0, left: 0, bottom: 0, right: 0, margin: "auto", }, "&::after": { width: 60, height: 15, }, }, '&[data-selected="true"]': { borderColor: "#4EBE8C", "&::after": { content: '""', display: "block", position: "absolute", width: "100%", height: "100%", top: 0, left: 0, boxSizing: "border-box", border: "3px #4EBE8C solid", pointerEvents: "none", }, }, "&::before": { content: "attr(data-name)", display: "block", width: "100%", position: "absolute", bottom: 0, left: 0, backgroundColor: "rgba(0, 0, 0, 0.5)", color: "white", fontSize: 13, textAlign: "center", lineHeight: "25px", overflow: "hidden", height: 25, textOverflow: "ellipsis", whiteSpace: "nowrap", boxSizing: "border-box", padding: "0 10px", }, "& > img": { width: "100%", height: "100%", padding: 5, boxSizing: "border-box", objectFit: "scale-down", }, "& > .unablePreviewHint": { width: "100%", height: "100%", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", color: "#666", fontSize: 13, "& > .type": { width: "80%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", }, }, "& > .removeBtn": { width: 20, height: 20, borderRadius: "50%", textAlign: "center", lineHeight: "20px", fontWeight: "bold", fontFamily: "黑体", position: "absolute", top: 5, right: 5, "&:hover": { backgroundColor: "#666", color: "white", }, }, }, }, panel: { height: "30%", padding: 10, boxSizing: "border-box", display: "flex", "& > .block": { display: "flex", flex: 1, flexWrap: "wrap", alignItems: "center", height: "100%", padding: "0 10px", "& .input-container": { minWidth: 240, position: "relative", "& > *": { verticalAlign: "middle", fontSize: 14, }, "& > input": { boxSizing: "border-box", width: "calc(100% - 5em)", minWidth: 150, }, }, }, "& .categoryInput": { position: "relative", "& .inputHint": { opacity: 0, transition: "opacity 0.2s", backgroundColor: "#fffeee", border: "1px #ccc solid", padding: "2px 10px", position: "absolute", bottom: "calc(100% - 7px)", left: "calc(100% - 7px)", zIndex: 1, borderRadius: 5, whiteSpace: "nowrap", }, "& > input:focus + .inputHint": { opacity: 1, }, }, "& .categoryHints": { minWidth: 170, maxHeight: 140, backgroundColor: "white", whiteSpace: "nowrap", overflow: "auto", position: "absolute", right: 9, bottom: "100%", border: "1px #666 solid", boxSizing: "border-box", borderBottom: "none", display: "flex", flexDirection: "column-reverse", "& > .item": { minHeight: 20, lineHeight: "20px", boxSizing: "border-box", padding: "0 5px", width: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", cursor: "pointer", '&[data-selected="true"]': { backgroundColor: "#ccc", }, }, }, "& .categories": { width: "100%", height: 23, border: "1px #ccc solid", borderRadius: 5, overflow: "auto", marginRight: 5, boxSizing: "border-box", "& > .item": { display: "inline-block", lineHeight: "15px", textAlign: "center", border: "1px #666 solid", backgroundColor: "#eee", margin: "2px 3px", padding: "0 5px", fontSize: 14, cursor: "pointer", }, }, "& .buttons": { width: "100%", "& > button": { marginTop: 5, }, }, }, }).attach().classes; new Vue({ el: "#widget-fileUploader", template, data() { return { s: createStyles(), // 样式 allowedFileTypes: ["ogg", "ogv", "oga", "flac", "opus", "wav", "webm", "mp3", "png", "gif", "jpg", "jpeg", "webp", "svg", "pdf", "ppt", "jp2", "doc", "docx", "xls", "xlsx", "psd", "sai", "swf", "mp4"], files: [], // 待上传的文件 focusedFileIndex: 0, categoryHints: [], categoryInputDebounceTimeoutKey: 0, categoryHintFocusedIndex: -1, categoryFocused: false, status: 1, // 0:失败,1:初始化,2:提交中,3:成功 form: { fileName: "", categoryInput: "", // 分类输入栏 categories: [], // 实际要提交的分类 charaName: "", author: "", source: "", prefix: "", license: "", }, doubleClickTimeoutKey: 0, // 用于双击复制文件名 }; }, mounted() { $("#loading").html('<a href="javascript:void(0);" id="show">点此重新调出上传界面</a>'); $("#show").on("click", () => { $("#widget-fileUploader").fadeIn(200); return false; }); }, watch: { files() { this.focusedFileIndex === 0 && this.focusFile(0); }, form: { deep: true, handler() { if (!this.files[this.focusedFileIndex]) { return; } this.files[this.focusedFileIndex] = { ...this.files[this.focusedFileIndex], fileName: this.form.fileName, author: this.form.author, charaName: this.form.charaName, source: this.form.source, license: this.form.license, }; }, }, license(val) { if (val === "none:gotoCommons") { alert("该协议需要手动填写授权证明,请到共享站进行上传"); window.open(`${mw.config.get("wgServer").replace("zh.moegirl", "commons.moegirl") + mw.config.get("wgScriptPath")}/Special:上传文件`, "_blank"); } }, }, computed: { license() { return this.form.license; }, }, methods: { createFileItem(fileBody) { return { body: fileBody, objectUrl: typeof fileBody === "string" ? fileBody : URL.createObjectURL(fileBody), fileName: typeof fileBody === "string" ? fileBody.replace(/.+\/(.+?)$/, "$1") : fileBody.name, author: "", charaName: "", source: "", license: "Copyright", }; }, isImageFile(fileBody) { const imageType = ["jpg", "png", "jpeg", "gif", "webp"]; return imageType.includes((typeof fileBody === "string" ? fileBody : fileBody.name).replace(/.+\.(.+?)$/, "$1")); }, hideWidget() { $("#widget-fileUploader").fadeOut(200); $("#content").css("position", "relative"); }, loadCategoryHint() { clearTimeout(this.categoryInputDebounceTimeoutKey); this.categoryInputDebounceTimeoutKey = setTimeout(() => { if (this.form.categoryInput === "") { return; } getHints(this.form.categoryInput) .then(data => { const hints = data.query.search.map(item => item.title.split("Category:")[1]); this.categoryHints = hints; }); }, 500); }, clearHint() { this.categoryHints = []; }, resetCategory() { this.form.categoryInput = ""; this.categoryHints = []; this.categoryHintFocusedIndex = -1; }, addCategory(categoryName) { if (!this.form.categories.includes(categoryName)) { this.form.categories.push(categoryName); } this.resetCategory(); }, // 实现上下键切换分类提示 handlerFor_categoryHints_wasKeyDowned(e) { if (e.code === "ArrowUp") { this.categoryHintFocusedIndex++; if (this.categoryHintFocusedIndex > this.categoryHints.length - 1) { this.categoryHintFocusedIndex = 0; } } if (e.code === "ArrowDown") { this.categoryHintFocusedIndex--; if (this.categoryHintFocusedIndex < 0) { this.$refs.categoryInput.focus(); } } this.categoryHintFocusedIndex >= 0 && this.$refs.categoryHints.querySelectorAll("div")[this.categoryHintFocusedIndex].scrollIntoView(); }, handlerFor_categoryInput_wasKeyDowned() { if (this.categoryHints.length === 0 || !this.$refs.categoryHints) { return; } this.$refs.categoryHints.focus(); this.categoryHintFocusedIndex = 0; }, addFileByFileSelector(e) { Array.from(e.target.files).forEach(file => { if (this.files.length === 50) { return; } if (file.size / 1024 / 1024 > 20) { return alert(`文件【${file.name}】大小超过20m,无法上传!`); } this.files.push(this.createFileItem(file)); }); e.target.value = ""; if (this.files.length === 50) { mw.notify("一次最多上传50个文件", { type: "wran" }); } }, addFileByDropping(e) { Array.from(e.dataTransfer.files).forEach(file => { if (this.files.length === 50) { return; } if (!this.allowedFileTypes.includes(file.name.replace(/.+\.(.+?)$/, "$1"))) { return alert(`【${file.name}】不支持上传这种格式的文件!`); } if (file.size / 1024 / 1024 > 20) { return alert(`【${file.name}】的大小超过20m,无法上传!`); } this.files.push(this.createFileItem(file)); }); if (this.files.length === 50) { mw.notify("一次最多上传50个文件", { type: "wran" }); } }, focusFile(index) { this.focusedFileIndex = index; const file = this.files[index]; this.form = { ...this.form, fileName: file.fileName, author: file.author, charaName: file.charaName, source: file.source, license: file.license, }; // 实现双击复制文件名 if (this.doubleClickTimeoutKey === 0) { this.doubleClickTimeoutKey = setTimeout(() => { this.doubleClickTimeoutKey = 0; }, 300); } else { mw.notify("已复制文件名"); this.copyFileName(this.form.prefix + file.fileName); clearTimeout(this.doubleClickTimeoutKey); this.doubleClickTimeoutKey = 0; } }, addSourceUrlFile() { const url = (prompt("请输入文件地址:") || "").trim(); if (!url) { return; } this.files.push(this.createFileItem(url)); }, copyFileName(fileName) { const inputTag = document.createElement("input"); inputTag.value = fileName; inputTag.style.cssText = "position: fixed; left: -9999px;"; document.body.appendChild(inputTag); inputTag.focus(); document.execCommand("selectAll"); document.execCommand("copy"); setTimeout(() => document.body.removeChild(inputTag), 1000); }, asyncCurrentFileInfo() { if (!confirm("确定要将当前选中的文件信息(不含文件名)同步到所有文件中?")) { return; } const currentFile = this.files[this.focusedFileIndex]; if (!currentFile) { return mw.notify("当前未选中文件"); } this.files.forEach(item => { item.author = currentFile.author; item.charaName = currentFile.charaName; item.source = currentFile.source; item.license = currentFile.license; }); mw.notify("已同步"); }, showManual() { alert([ "使用说明", "1. 该插件是一个文件上传工具,支持拖拽上传、批量上传。", "2. 若文件上传时发生异常,请以萌娘共享的监视列表为准。", "3. 每个文件拥有独立的信息,但“分类”和“添加前缀”是共享的。在需要同步每个文件的角色名、作者等信息时可以使用“同步文件信息”的功能。", "4. 什么是“差分上传”:在发生文件名已存在的情况时,自动滤掉已存在的文件。通常用于在上一次批量上传中一部分失败后,再次尝试将之前没传上去的文件重新上传。", "5. 双击文件可以自动复制“前缀 + 文件名”。", ].join("\n")); }, async submit(diffMode) { if (this.files.length === 0) { return mw.notify("您还没有上传任何文件", { type: "warn" }); } if (this.files.some(item => item.fileName === "")) { return mw.notify("存在文件名为空的文件", { type: "warn" }); } const duplicateFilesName = new Set(); const filesName = this.files.map(({ fileName }) => fileName); for (const n of filesName) { if (filesName.indexOf(n) !== filesName.lastIndexOf(n)) { duplicateFilesName.add(n); } } if (duplicateFilesName.size > 0) { return alert([ "这些文件名发生了重复,请不要给要上传的文件设置相同的名称:", ...Array.from(duplicateFilesName.values()), ].join("\n")); } const authorizedForMoegirlFiles = this.files.filter(item => item.license === "none:gotoCommons"); if (authorizedForMoegirlFiles.length > 0) { return alert([ "这些文件的授权协议不允许使用上传工具,请在本次上传中删除,并前往共享站填写授权信息后上传:", ...authorizedForMoegirlFiles.map(item => item.fileName), ].join("\n")); } if (!confirm("确定要开始上传吗?")) { return; } let postData = this.files.map(item => { const metaCategories = `${item.charaName ? `[[分类:${item.charaName}]]` : ""}${item.author ? `[[分类:作者:${item.author}]]` : ""}`; const source = item.source ? `源地址:${item.source}` : ""; const comment = []; if (item.license) { comment.push(`协议:${item.license}`); } if (item.charaName) { comment.push(`人物:[[分类:${item.charaName}|${item.charaName}]]`); } if (item.author) { comment.push(`作者:[[分类:作者:${item.author}|${item.author}]]`); } if (item.source) { comment.push(`源地址:${item.source}`); } if (this.form.categories.length > 0) { comment.push(`其他分类:[[分类:${this.form.categories.join("]]、[[分类:")}]]`); } const pageContent = [ "== 文件说明 ==", metaCategories + this.form.categories.map(item => `[[分类:${item}]]`).join(""), source, "== 授权协议 ==", `{{${item.license}}}`, ].join("\n"); return { body: item.body, fileName: this.form.prefix + item.fileName, comment: comment.join(","), pageContent, }; }); mw.notify(`开始${diffMode ? "差分" : ""}上传,共${postData.length}个文件...`); console.log(`---- Moegirl:fileUploader 开始${diffMode ? "差分" : ""}上传,共${postData.length}个文件 ----`); this.status = 2; const printLogFn = (type = "info") => msg => { mw.notify(msg, { type }); console.log(msg); }; const printLog = printLogFn(); printLog.warn = printLogFn("warn"); printLog.error = printLogFn("error"); try { const checkedResult = await checkFileNames(postData.map(item => item.fileName)); const existedFiles = postData.filter(item => checkedResult[item.fileName.replace(/^./, s => s.toUpperCase())]); // 首字母转大写,因为checkedResult返回的文件名首字母是大写 if (existedFiles.length > 0 && !diffMode) { alert([ "这些文件名已被使用,请为对应的文件更换其他名称:", ...existedFiles.map(item => item.fileName), ].join("\n")); this.status = 1; return; } if (diffMode) { postData = postData.filter(item => !checkedResult[item.fileName.replace(/^./, s => s.toUpperCase())]); } if (diffMode && postData.length === 0) { alert("差分模式下没有可以上传的文件"); this.status = 1; return; } printLog.warn(`差分上传共需要上传${postData.length}个文件`); let uploadResults = []; if (postData.length <= 3) { uploadResults = await Promise.all( postData.map(item => new Promise(resolve => { upload(item) .then(() => { printLog(`【${item.fileName}】上传成功`); resolve({ fileName: item.fileName, result: true }); }) .catch((e) => { printLog.error(`【${item.fileName}】上传失败:${errorInfo(e)}`); console.error(e); resolve({ fileName: item.fileName, result: false }); }); })), ); } else { alert("上传的文件超过个三个,执行分段上传,请耐心等待。进入控制台可查看全部日志(按F12后选择Console)。"); printLog.warn("上传文件超过3个,执行分段上传"); // 分段上传 const segmentedPostData = [[]]; for (const item of postData) { if (segmentedPostData[segmentedPostData.length - 1].length >= 3) { segmentedPostData.push([]); } segmentedPostData[segmentedPostData.length - 1].push(item); } console.log(segmentedPostData); for (let i = 0, len = segmentedPostData.length; i < len; i++) { printLog(`共${len}个分段,现在开始第${i + 1}个`); const segment = segmentedPostData[i]; const segmentedUploadResult = await Promise.all( segment.map(item => new Promise(resolve => { upload(item) .then(() => { printLog(`【${item.fileName}】上传成功`); resolve({ fileName: item.fileName, result: true }); }) .catch((e) => { printLog.error(`【${item.fileName}】上传失败:${errorInfo(e)}`); resolve({ fileName: item.fileName, result: false }); }); })), ); uploadResults.push(...segmentedUploadResult); printLog(`第${i + 1}个分段完成,其中${segmentedUploadResult.filter(item => item.result).length}个成功,${segmentedUploadResult.filter(item => !item.result).length}个失败`); } } const report = [ `全部上传结果:共计${uploadResults.length}个文件,其中${uploadResults.filter(item => item.result).length}个成功,${uploadResults.filter(item => !item.result).length}个失败`, ...uploadResults.map((item, index) => `${index + 1}. 【${item.fileName}】${item.result ? "成功" : "失败"}`), ].join("\n"); console.log(report); alert(report); this.status = 3; } catch (e) { console.log("上传流程出现错误", e); mw.notify("网络错误,请重试", { type: "error" }); this.status = 0; } }, }, }); })()); //
</script>