314 lines
9.8 KiB
JavaScript
314 lines
9.8 KiB
JavaScript
(function () {
|
|
var form = document.querySelector("[data-feedback-form]");
|
|
if (!form) return;
|
|
|
|
var types = form.querySelectorAll(".feedback-type");
|
|
var content = form.querySelector("[data-feedback-content]");
|
|
var count = form.querySelector("[data-feedback-count]");
|
|
var uploads = form.querySelector("[data-feedback-uploads]");
|
|
var fileInput = form.querySelector("[data-feedback-file]");
|
|
var submitBtn = form.querySelector(".feedback-form__submit");
|
|
var files = [];
|
|
var maxFiles = 3;
|
|
var submitting = false;
|
|
var ALLOWED_MIME = {
|
|
"image/jpeg": true,
|
|
"image/jpg": true,
|
|
"image/pjpeg": true,
|
|
"image/png": true,
|
|
"image/gif": true,
|
|
};
|
|
var ALLOWED_EXT = {
|
|
jpg: true,
|
|
jpeg: true,
|
|
png: true,
|
|
gif: true,
|
|
};
|
|
var TYPE_MAP = {
|
|
play: 2, // 播放问题
|
|
account: 3, // 账号问题
|
|
report: 4, // 内容举报 → 其他
|
|
suggest: 1, // 功能建议
|
|
other: 4, // 其他
|
|
};
|
|
|
|
function toast(message, type) {
|
|
if (typeof window.showToast === "function") {
|
|
window.showToast(message, type);
|
|
return;
|
|
}
|
|
if (window.AuthAPI && typeof window.AuthAPI.toast === "function") {
|
|
window.AuthAPI.toast(message, type || "info");
|
|
return;
|
|
}
|
|
window.alert(message);
|
|
}
|
|
|
|
Array.prototype.forEach.call(types, function (type) {
|
|
var input = type.querySelector('input[type="radio"]');
|
|
if (!input) return;
|
|
input.addEventListener("change", function () {
|
|
Array.prototype.forEach.call(types, function (item) {
|
|
item.classList.toggle("is-active", item.querySelector("input").checked);
|
|
});
|
|
});
|
|
});
|
|
|
|
if (content && count) {
|
|
content.addEventListener("input", function () {
|
|
count.textContent = String(Array.from(content.value || "").length);
|
|
});
|
|
}
|
|
|
|
function isAllowedImage(file) {
|
|
if (!file) return false;
|
|
var mime = String(file.type || "").toLowerCase();
|
|
if (mime && ALLOWED_MIME[mime]) return true;
|
|
var name = String(file.name || "");
|
|
var dot = name.lastIndexOf(".");
|
|
if (dot < 0) return false;
|
|
var ext = name.slice(dot + 1).toLowerCase();
|
|
return !!ALLOWED_EXT[ext];
|
|
}
|
|
|
|
function renderUploads() {
|
|
if (!uploads || !fileInput) return;
|
|
uploads.innerHTML = "";
|
|
|
|
files.forEach(function (file, index) {
|
|
var item = document.createElement("div");
|
|
item.className = "feedback-upload feedback-upload--preview";
|
|
if (file.uploading) item.classList.add("is-uploading");
|
|
if (file.error) item.classList.add("is-error");
|
|
var img = document.createElement("img");
|
|
img.src = "/static/images/placeholder.png?v=2";
|
|
img.setAttribute("data-src", file.previewUrl);
|
|
img.alt = file.name || "截图";
|
|
img.loading = "lazy";
|
|
img.decoding = "async";
|
|
var remove = document.createElement("button");
|
|
remove.type = "button";
|
|
remove.className = "feedback-upload__remove";
|
|
remove.setAttribute("aria-label", "删除截图");
|
|
remove.innerHTML = '<img class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/close.svg" alt="" loading="lazy" decoding="async">';
|
|
remove.addEventListener("click", function () {
|
|
if (file.previewUrl) URL.revokeObjectURL(file.previewUrl);
|
|
files.splice(index, 1);
|
|
renderUploads();
|
|
});
|
|
item.appendChild(img);
|
|
item.appendChild(remove);
|
|
uploads.appendChild(item);
|
|
});
|
|
|
|
if (files.length < maxFiles) {
|
|
var label = document.createElement("label");
|
|
label.className = "feedback-upload feedback-upload--add";
|
|
label.appendChild(fileInput);
|
|
fileInput.hidden = true;
|
|
var icon = document.createElement("img");
|
|
icon.className = "icon";
|
|
icon.src = "/static/images/placeholder.png?v=2";
|
|
icon.setAttribute("data-src", "/static/images/plus-dim.svg");
|
|
icon.alt = "";
|
|
icon.loading = "lazy";
|
|
icon.decoding = "async";
|
|
label.appendChild(icon);
|
|
var text = document.createElement("span");
|
|
text.textContent = "上传截图";
|
|
label.appendChild(text);
|
|
uploads.appendChild(label);
|
|
} else {
|
|
uploads.appendChild(fileInput);
|
|
fileInput.hidden = true;
|
|
}
|
|
|
|
if (typeof window.initLazyImages === "function") {
|
|
window.initLazyImages(uploads);
|
|
}
|
|
}
|
|
|
|
function extractUploadURL(data) {
|
|
if (!data) return "";
|
|
if (typeof data === "string") return data;
|
|
return String(data.sourceNameFs || data.url || data.path || data.id || "").trim();
|
|
}
|
|
|
|
function uploadOne(fileItem) {
|
|
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
|
|
return Promise.reject(new Error("上传服务未就绪"));
|
|
}
|
|
if (fileItem.remoteURL) return Promise.resolve(fileItem.remoteURL);
|
|
fileItem.uploading = true;
|
|
renderUploads();
|
|
var fd = new FormData();
|
|
fd.append("file", fileItem.file, fileItem.name || "image.jpg");
|
|
return window.HttpRequest.post("/api/upload/image", fd)
|
|
.then(function (res) {
|
|
if (!res || Number(res.code) !== 200 || !res.data) {
|
|
throw new Error((res && (res.tip || res.msg)) || "上传失败");
|
|
}
|
|
var url = extractUploadURL(res.data);
|
|
if (!url) throw new Error("上传成功但未返回图片地址");
|
|
fileItem.remoteURL = url;
|
|
fileItem.uploading = false;
|
|
fileItem.error = false;
|
|
renderUploads();
|
|
return url;
|
|
})
|
|
.catch(function (err) {
|
|
fileItem.uploading = false;
|
|
fileItem.error = true;
|
|
renderUploads();
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
function uploadAll() {
|
|
var chain = Promise.resolve([]);
|
|
files.forEach(function (item) {
|
|
chain = chain.then(function (urls) {
|
|
return uploadOne(item).then(function (url) {
|
|
urls.push(url);
|
|
return urls;
|
|
});
|
|
});
|
|
});
|
|
return chain;
|
|
}
|
|
|
|
function selectedType() {
|
|
var checked = form.querySelector('input[name="type"]:checked');
|
|
var key = checked ? String(checked.value || "") : "play";
|
|
return TYPE_MAP[key] || 2;
|
|
}
|
|
|
|
function apiTip(payload, fallback) {
|
|
if (!payload) return fallback;
|
|
if (payload.tip || payload.msg) return String(payload.tip || payload.msg);
|
|
if (payload.data && (payload.data.tip || payload.data.msg)) {
|
|
return String(payload.data.tip || payload.data.msg);
|
|
}
|
|
if (payload.message) return String(payload.message);
|
|
return fallback;
|
|
}
|
|
|
|
function setBusy(on) {
|
|
submitting = !!on;
|
|
if (submitBtn) {
|
|
submitBtn.disabled = !!on;
|
|
submitBtn.classList.toggle("is-loading", !!on);
|
|
if (on) {
|
|
if (!submitBtn.dataset.labelHtml) {
|
|
submitBtn.dataset.labelHtml = submitBtn.innerHTML;
|
|
}
|
|
submitBtn.textContent = "提交中…";
|
|
} else if (submitBtn.dataset.labelHtml) {
|
|
submitBtn.innerHTML = submitBtn.dataset.labelHtml;
|
|
}
|
|
}
|
|
if (content) content.disabled = !!on;
|
|
if (fileInput) fileInput.disabled = !!on;
|
|
Array.prototype.forEach.call(form.querySelectorAll("input, textarea, button"), function (el) {
|
|
if (el === submitBtn) return;
|
|
if (el === fileInput) return;
|
|
if (el === content) return;
|
|
if (el.type === "file") return;
|
|
el.disabled = !!on;
|
|
});
|
|
}
|
|
|
|
if (fileInput) {
|
|
fileInput.addEventListener("change", function () {
|
|
var selected = Array.prototype.slice.call(fileInput.files || []);
|
|
var rejectedFormat = false;
|
|
var rejectedFull = false;
|
|
selected.forEach(function (file) {
|
|
if (files.length >= maxFiles) {
|
|
rejectedFull = true;
|
|
return;
|
|
}
|
|
if (!isAllowedImage(file)) {
|
|
rejectedFormat = true;
|
|
return;
|
|
}
|
|
files.push({
|
|
name: file.name,
|
|
file: file,
|
|
previewUrl: URL.createObjectURL(file),
|
|
remoteURL: "",
|
|
uploading: false,
|
|
error: false,
|
|
});
|
|
});
|
|
fileInput.value = "";
|
|
renderUploads();
|
|
if (rejectedFormat) toast("仅支持 jpg / png / gif 格式", "error");
|
|
else if (rejectedFull) toast("最多上传 3 张图片", "error");
|
|
});
|
|
}
|
|
|
|
renderUploads();
|
|
|
|
form.addEventListener("submit", function (event) {
|
|
event.preventDefault();
|
|
if (submitting) return;
|
|
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
|
|
toast("反馈服务未就绪", "error");
|
|
return;
|
|
}
|
|
|
|
var text = String((content && content.value) || "").trim();
|
|
if (!text) {
|
|
toast("请填写问题描述", "error");
|
|
return;
|
|
}
|
|
if (Array.from(text).length > 500) {
|
|
toast("问题描述最多 500 字", "error");
|
|
return;
|
|
}
|
|
|
|
var contactInput = form.querySelector('input[name="contact"]');
|
|
var contact = contactInput ? String(contactInput.value || "").trim() : "";
|
|
var type = selectedType();
|
|
|
|
setBusy(true);
|
|
uploadAll()
|
|
.then(function (screenshots) {
|
|
var payload = {
|
|
type: type,
|
|
content: text,
|
|
contact: contact,
|
|
screenshots: screenshots || [],
|
|
};
|
|
return window.HttpRequest.post("/api/feedback/add", payload);
|
|
})
|
|
.then(function (res) {
|
|
if (!res || Number(res.code) !== 200) {
|
|
var failTip = apiTip(res, "提交失败");
|
|
var failErr = new Error(failTip);
|
|
failErr.data = res;
|
|
throw failErr;
|
|
}
|
|
toast("反馈提交成功", "success");
|
|
form.reset();
|
|
files.forEach(function (f) {
|
|
if (f.previewUrl) URL.revokeObjectURL(f.previewUrl);
|
|
});
|
|
files = [];
|
|
Array.prototype.forEach.call(types, function (item) {
|
|
var input = item.querySelector("input");
|
|
item.classList.toggle("is-active", !!(input && input.checked));
|
|
});
|
|
if (count) count.textContent = "0";
|
|
renderUploads();
|
|
})
|
|
.catch(function (err) {
|
|
toast(apiTip(err, "提交失败,请稍后重试"), "error");
|
|
})
|
|
.finally(function () {
|
|
setBusy(false);
|
|
});
|
|
});
|
|
})();
|