ui: 复盘表单紧凑布局、即时上传截图并移除占用时新开仓

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-05 22:46:05 +08:00
parent 7bb6ee1154
commit f688a504e1
17 changed files with 544 additions and 263 deletions
+112 -36
View File
@@ -1,63 +1,139 @@
/**
* 复盘表单:四周期截图槽位本地预览
* 复盘表单:四周期截图即时上传与状态展示
*/
(function (global) {
"use strict";
const previewUrls = new WeakMap();
function clearPreview(cell) {
if (!cell) return;
const prev = previewUrls.get(cell);
if (prev) {
URL.revokeObjectURL(prev);
previewUrls.delete(cell);
function newDraftId() {
if (global.crypto && typeof global.crypto.randomUUID === "function") {
return global.crypto.randomUUID().replace(/-/g, "");
}
cell.innerHTML = "";
cell.classList.remove("journal-upload-slot-preview--filled");
var s = "";
for (var i = 0; i < 32; i++) {
s += Math.floor(Math.random() * 16).toString(16);
}
return s;
}
function renderPreview(cell, file, tf) {
clearPreview(cell);
if (!file || !cell) return;
const url = URL.createObjectURL(file);
previewUrls.set(cell, url);
const img = document.createElement("img");
img.src = url;
img.alt = tf + " 预览";
img.className = "journal-upload-slot-thumb";
img.addEventListener("click", function (e) {
e.preventDefault();
if (typeof global.showImage === "function") {
global.showImage(url);
}
});
cell.appendChild(img);
cell.classList.add("journal-upload-slot-preview--filled");
function ensureDraftId(root) {
var scope = root || document;
var el = scope.querySelector("#journal-draft-id");
if (!el) return "";
if (!el.value) {
el.value = newDraftId();
}
return el.value;
}
function rowParts(input) {
var row = input.closest(".journal-upload-row");
if (!row) return {};
return {
row: row,
status: row.querySelector(".journal-upload-status"),
hidden: row.querySelector(".journal-upload-hidden-file"),
};
}
function setStatus(statusEl, text, kind) {
if (!statusEl) return;
statusEl.textContent = text || "";
statusEl.classList.remove(
"journal-upload-status--pending",
"journal-upload-status--ok",
"journal-upload-status--err"
);
if (kind) {
statusEl.classList.add("journal-upload-status--" + kind);
}
}
function uploadSlotFile(input, file) {
var parts = rowParts(input);
var draftId = ensureDraftId(input.form || document);
if (!draftId || !file) {
setStatus(parts.status, "上传失败", "err");
return;
}
setStatus(parts.status, "上传中…", "pending");
if (parts.hidden) {
parts.hidden.value = "";
}
var fd = new FormData();
fd.append("journal_draft_id", draftId);
fd.append("tf", input.getAttribute("data-tf") || "");
fd.append("file", file);
fetch("/api/journal_upload_slot", { method: "POST", body: fd, credentials: "same-origin" })
.then(function (res) {
return res.json().then(function (data) {
return { ok: res.ok, data: data };
});
})
.then(function (result) {
if (!result.ok || !result.data || !result.data.ok) {
throw new Error(
(result.data && result.data.error) || "upload failed"
);
}
var fname = String(result.data.file || "").trim();
if (parts.hidden) {
parts.hidden.value = fname;
}
input.value = "";
setStatus(parts.status, "上传成功 " + fname, "ok");
})
.catch(function () {
if (parts.hidden) {
parts.hidden.value = "";
}
setStatus(parts.status, "上传失败", "err");
});
}
function bindInput(input) {
if (!input || input.dataset.journalSlotBound === "1") return;
input.dataset.journalSlotBound = "1";
const tf = input.getAttribute("data-tf") || "";
const cell = input.closest(".journal-upload-slot");
const preview = cell ? cell.querySelector(".journal-upload-slot-preview") : null;
input.addEventListener("change", function () {
const file = input.files && input.files[0];
var file = input.files && input.files[0];
if (!file) {
clearPreview(preview);
var parts = rowParts(input);
if (parts.hidden) {
parts.hidden.value = "";
}
setStatus(parts.status, "", "");
return;
}
renderPreview(preview, file, tf);
uploadSlotFile(input, file);
});
}
function resetSlots(root) {
var scope = root || document;
var draftEl = scope.querySelector("#journal-draft-id");
if (draftEl) {
draftEl.value = newDraftId();
}
scope.querySelectorAll(".journal-upload-hidden-file").forEach(function (el) {
el.value = "";
});
scope.querySelectorAll(".journal-upload-slot-input").forEach(function (el) {
el.value = "";
});
scope.querySelectorAll(".journal-upload-status").forEach(function (el) {
setStatus(el, "", "");
});
}
function init(root) {
const scope = root || document;
var scope = root || document;
ensureDraftId(scope);
scope.querySelectorAll(".journal-upload-slot-input").forEach(bindInput);
}
global.JournalUploadSlots = { init: init };
global.JournalUploadSlots = { init: init, reset: resetSlots };
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {