优化展示
This commit is contained in:
parent
260b843eb8
commit
2cfaabc791
@ -5,7 +5,8 @@
|
|||||||
<title>上传文件到 MinIO/S3</title>
|
<title>上传文件到 MinIO/S3</title>
|
||||||
<style>
|
<style>
|
||||||
body { font-family: system-ui, sans-serif; background: #f7f9fb; margin: 0; }
|
body { font-family: system-ui, sans-serif; background: #f7f9fb; margin: 0; }
|
||||||
.uploader-main { max-width: 480px; margin: 60px auto 0; background: #fff; border-radius: 13px; box-shadow: 0 6px 26px #ccc4; padding: 32px 38px 28px; }
|
.main-flex { display: flex; justify-content: center; align-items: flex-start; gap: 36px; min-height: 90vh;}
|
||||||
|
.uploader-main { flex: none; width: 410px; margin: 60px 0 0; background: #fff; border-radius: 13px; box-shadow: 0 6px 26px #ccc4; padding: 32px 38px 28px; }
|
||||||
.uploader-main h2 { text-align: center; font-size: 1.28rem; color: #1a2557; margin-bottom: 22px; }
|
.uploader-main h2 { text-align: center; font-size: 1.28rem; color: #1a2557; margin-bottom: 22px; }
|
||||||
.input-row { margin-bottom: 14px; }
|
.input-row { margin-bottom: 14px; }
|
||||||
.input-row label { font-size: 15px; color: #444; margin-right: 7px; }
|
.input-row label { font-size: 15px; color: #444; margin-right: 7px; }
|
||||||
@ -25,10 +26,15 @@
|
|||||||
.file-label { display: inline-block; width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
|
.file-label { display: inline-block; width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
|
||||||
.percent-label { font-size: 12px; color: #888; }
|
.percent-label { font-size: 12px; color: #888; }
|
||||||
.upload-msg { margin-left: 8px; }
|
.upload-msg { margin-left: 8px; }
|
||||||
|
/* 右侧json面板 */
|
||||||
|
.result-panel { flex: none; width: 380px; margin: 60px 0 0; background: #fff; border-radius: 13px; box-shadow: 0 6px 26px #ccc4; padding: 28px 25px 22px;}
|
||||||
|
.result-panel h3 { font-size: 1rem; margin-bottom: 9px; color: #1a2557;}
|
||||||
|
#jsonResult { font-family: 'JetBrains Mono', 'Menlo', 'Consolas', monospace; background: #f7fafc; border-radius: 7px; padding: 13px 10px; font-size: 14px; color: #354f7d; white-space: pre; min-height: 62px; border:1px solid #e4e9f0;}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="uploader-main">
|
<div class="main-flex">
|
||||||
|
<div class="uploader-main">
|
||||||
<h2>上传文件到 MinIO/S3</h2>
|
<h2>上传文件到 MinIO/S3</h2>
|
||||||
<div class="desc">可拖拽或点击选择,支持多文件上传</div>
|
<div class="desc">可拖拽或点击选择,支持多文件上传</div>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
@ -38,6 +44,7 @@
|
|||||||
<option value="kbase-priv">kbase-priv(私有)</option>
|
<option value="kbase-priv">kbase-priv(私有)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="bucketTip" class="tip" style="margin-bottom:8px;"></div>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<label for="folderName">目标文件夹:</label>
|
<label for="folderName">目标文件夹:</label>
|
||||||
<input type="text" id="folderName" placeholder="默认 default,可留空" />
|
<input type="text" id="folderName" placeholder="默认 default,可留空" />
|
||||||
@ -45,10 +52,14 @@
|
|||||||
<div class="tip" style="margin-bottom:12px;">* 如 bucket 不存在请先在 MinIO 控制台创建</div>
|
<div class="tip" style="margin-bottom:12px;">* 如 bucket 不存在请先在 MinIO 控制台创建</div>
|
||||||
<div id="drop-area">
|
<div id="drop-area">
|
||||||
<div id="drop-msg">拖拽文件到此上传,或点击选择文件</div>
|
<div id="drop-msg">拖拽文件到此上传,或点击选择文件</div>
|
||||||
<!-- 只支持多文件,不支持文件夹上传 -->
|
|
||||||
<input type="file" id="fileElem" multiple />
|
<input type="file" id="fileElem" multiple />
|
||||||
</div>
|
</div>
|
||||||
<div id="uploadList"></div>
|
<div id="uploadList"></div>
|
||||||
|
</div>
|
||||||
|
<div class="result-panel">
|
||||||
|
<h3>上传结果 JSON</h3>
|
||||||
|
<pre id="jsonResult">{}</pre>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="../scripts/uploader.js"></script>
|
<script src="../scripts/uploader.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const fileElem = document.getElementById("fileElem");
|
|||||||
const uploadList = document.getElementById("uploadList");
|
const uploadList = document.getElementById("uploadList");
|
||||||
const folderNameInput = document.getElementById("folderName");
|
const folderNameInput = document.getElementById("folderName");
|
||||||
const bucketNameInput = document.getElementById("bucketName");
|
const bucketNameInput = document.getElementById("bucketName");
|
||||||
|
const jsonResult = document.getElementById("jsonResult");
|
||||||
|
|
||||||
const API_UPLOAD = "http://app.wisdompulse.cn/api/v1/doc/upload-files";
|
const API_UPLOAD = "http://app.wisdompulse.cn/api/v1/doc/upload-files";
|
||||||
|
|
||||||
@ -45,22 +46,6 @@ function createProgressItem(filename) {
|
|||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFiles(files) {
|
|
||||||
uploadList.innerHTML = "";
|
|
||||||
const bucket = getBucketName();
|
|
||||||
if (!bucket) {
|
|
||||||
showMsg("请输入 Bucket 名称", false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!files || !files.length) return;
|
|
||||||
const folder = getTargetFolder();
|
|
||||||
|
|
||||||
// 多文件逐个上传,每个单独显示进度
|
|
||||||
for (const file of files) {
|
|
||||||
await uploadSingleFile(file, bucket, folder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMsg(msg, ok) {
|
function showMsg(msg, ok) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = ok ? "success" : "error";
|
div.className = ok ? "success" : "error";
|
||||||
@ -68,62 +53,137 @@ function showMsg(msg, ok) {
|
|||||||
uploadList.appendChild(div);
|
uploadList.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单文件上传,带进度条
|
// 批量上传(单次FormData),统一进度条管理,JSON右侧展示
|
||||||
function uploadSingleFile(file, bucket, folder) {
|
function handleFiles(fileList) {
|
||||||
return new Promise((resolve) => {
|
uploadList.innerHTML = "";
|
||||||
|
jsonResult.textContent = "{}";
|
||||||
|
const bucket = getBucketName();
|
||||||
|
if (!bucket) {
|
||||||
|
showMsg("请输入 Bucket 名称", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fileList || !fileList.length) return;
|
||||||
|
const folder = getTargetFolder();
|
||||||
|
|
||||||
|
// 显示所有文件的进度条
|
||||||
|
const fileArr = Array.from(fileList);
|
||||||
|
const progressItems = {};
|
||||||
|
fileArr.forEach(f => {
|
||||||
|
progressItems[f.name] = createProgressItem(f.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 构造FormData,支持批量
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.append("bucket", bucket);
|
form.append("bucket", bucket);
|
||||||
form.append("files", file, file.name);
|
if (folder) form.append("folder", folder);
|
||||||
|
fileArr.forEach(f => form.append("files", f, f.name));
|
||||||
const progressItem = createProgressItem(file.name);
|
|
||||||
const progressBar = progressItem.querySelector("progress");
|
|
||||||
const percentLabel = progressItem.querySelector(".percent-label");
|
|
||||||
const msgSpan = progressItem.querySelector(".upload-msg");
|
|
||||||
|
|
||||||
|
// 单一xhr,汇总进度
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open("POST", API_UPLOAD);
|
xhr.open("POST", API_UPLOAD);
|
||||||
|
|
||||||
xhr.upload.onprogress = function (e) {
|
xhr.upload.onprogress = function (e) {
|
||||||
if (e.lengthComputable) {
|
if (e.lengthComputable) {
|
||||||
const percent = Math.round((e.loaded / e.total) * 100);
|
// 进度只到99%,收到响应才到100%
|
||||||
progressBar.value = percent;
|
let percent = Math.round((e.loaded / e.total) * 98); // 98留点余量
|
||||||
percentLabel.textContent = percent + "%";
|
percent = Math.min(percent, 98);
|
||||||
|
fileArr.forEach(f => {
|
||||||
|
const pi = progressItems[f.name];
|
||||||
|
pi.querySelector("progress").value = percent;
|
||||||
|
pi.querySelector(".percent-label").textContent = percent + "%";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 新增处理中提示
|
||||||
|
xhr.upload.onload = function () {
|
||||||
|
fileArr.forEach(f => {
|
||||||
|
const pi = progressItems[f.name];
|
||||||
|
pi.querySelector("progress").value = 99;
|
||||||
|
pi.querySelector(".percent-label").textContent = "99%";
|
||||||
|
const msgSpan = pi.querySelector(".upload-msg");
|
||||||
|
msgSpan.innerHTML = "<span style='color:#888;'>服务器处理中...</span>";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
progressBar.value = 100;
|
fileArr.forEach(f => {
|
||||||
percentLabel.textContent = "100%";
|
const pi = progressItems[f.name];
|
||||||
|
pi.querySelector("progress").value = 100;
|
||||||
|
pi.querySelector(".percent-label").textContent = "100%";
|
||||||
|
});
|
||||||
|
|
||||||
|
// 成功
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
try {
|
try {
|
||||||
const res = JSON.parse(xhr.responseText);
|
const res = JSON.parse(xhr.responseText);
|
||||||
// 查找本文件上传结果
|
jsonResult.textContent = JSON.stringify(res.files, null, 2);
|
||||||
let link = "";
|
fileArr.forEach(f => {
|
||||||
if (res.files && res.files[file.name]) {
|
const pi = progressItems[f.name];
|
||||||
link = `<a href="${res.files[file.name]}" target="_blank">${file.name}</a> 上传成功`;
|
const msgSpan = pi.querySelector(".upload-msg");
|
||||||
msgSpan.innerHTML = link;
|
if (res.files && res.files[f.name]) {
|
||||||
msgSpan.className = "success";
|
msgSpan.innerHTML = `<a href="${res.files[f.name]}" target="_blank">${f.name}</a> 上传成功`;
|
||||||
|
msgSpan.className = "success upload-msg";
|
||||||
} else {
|
} else {
|
||||||
msgSpan.innerHTML = "上传成功";
|
msgSpan.innerHTML = "上传成功";
|
||||||
msgSpan.className = "success";
|
msgSpan.className = "success upload-msg";
|
||||||
}
|
}
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
msgSpan.innerHTML = "上传成功";
|
jsonResult.textContent = "{}";
|
||||||
msgSpan.className = "success";
|
fileArr.forEach(f => {
|
||||||
|
const pi = progressItems[f.name];
|
||||||
|
pi.querySelector(".upload-msg").innerHTML = "上传成功";
|
||||||
|
pi.querySelector(".upload-msg").className = "success upload-msg";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
// Nginx/gateway 504/502等超时(后端可能还在处理)
|
||||||
|
else if (xhr.status === 504 || xhr.status === 502) {
|
||||||
|
jsonResult.textContent = "{}";
|
||||||
|
fileArr.forEach(f => {
|
||||||
|
const pi = progressItems[f.name];
|
||||||
|
const msgSpan = pi.querySelector(".upload-msg");
|
||||||
|
msgSpan.innerHTML = "服务器处理中,请稍后在目标目录查看上传结果";
|
||||||
|
msgSpan.className = "success upload-msg";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 其它错误
|
||||||
|
else {
|
||||||
|
jsonResult.textContent = "{}";
|
||||||
|
fileArr.forEach(f => {
|
||||||
|
const pi = progressItems[f.name];
|
||||||
|
const msgSpan = pi.querySelector(".upload-msg");
|
||||||
msgSpan.innerHTML = "上传失败 " + xhr.status;
|
msgSpan.innerHTML = "上传失败 " + xhr.status;
|
||||||
msgSpan.className = "error";
|
msgSpan.className = "error upload-msg";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
resolve();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = function () {
|
xhr.onerror = function () {
|
||||||
msgSpan.innerHTML = "上传异常";
|
fileArr.forEach(f => {
|
||||||
msgSpan.className = "error";
|
const pi = progressItems[f.name];
|
||||||
resolve();
|
pi.querySelector(".upload-msg").innerHTML = "上传异常";
|
||||||
|
pi.querySelector(".upload-msg").className = "error upload-msg";
|
||||||
|
});
|
||||||
|
jsonResult.textContent = "{}";
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(form);
|
xhr.send(form);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === 放在文件结尾,而不是 handleFiles 内部 ===
|
||||||
|
const bucketTip = document.getElementById("bucketTip");
|
||||||
|
bucketNameInput.addEventListener("change", function () {
|
||||||
|
if (bucketNameInput.value === "kbase-priv") {
|
||||||
|
bucketTip.innerHTML = "私有 bucket 中,文件的可访问链接有效期为 <b>7天</b>";
|
||||||
|
bucketTip.style.color = "#ec0924";
|
||||||
|
} else {
|
||||||
|
bucketTip.innerHTML = "";
|
||||||
|
bucketTip.style.color = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 页面加载时自动触发一次
|
||||||
|
bucketNameInput.dispatchEvent(new Event("change"));
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user