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
+91 -45
View File
@@ -1660,63 +1660,118 @@ html[data-theme="light"] .symbol-live-price--ok {
}
/* ── 复盘:四周期截图上传 / 详情四宫格 ── */
.journal-upload-slots {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 10px;
.journal-card .journal-form-row1 {
grid-template-columns: repeat(7, minmax(0, 1fr));
margin-bottom: 6px;
}
.journal-upload-slot {
.journal-card .journal-form-row2 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.journal-card .journal-form-row2 select[name="entry_reason"] {
font-size: 0.8rem;
line-height: 1.35;
}
.journal-upload-slots {
display: flex;
flex-wrap: nowrap;
gap: 10px;
align-items: flex-start;
margin-top: 8px;
}
.journal-upload-row {
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px 10px;
border: 1px dashed rgba(130, 145, 190, 0.45);
border-radius: 8px;
background: rgba(12, 16, 32, 0.35);
font-size: 0.82rem;
align-items: stretch;
gap: 4px;
flex: 1 1 0;
min-width: 0;
}
.journal-upload-slot-label {
color: #9aa3c7;
font-weight: 600;
font-size: 0.78rem;
letter-spacing: 0.02em;
}
.journal-upload-slot-input {
font-size: 0.78rem;
max-width: 100%;
}
.journal-upload-slot-preview {
min-height: 72px;
border-radius: 6px;
background: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.journal-upload-slot-preview--filled {
min-height: 96px;
}
.journal-upload-slot-thumb {
width: 100%;
max-height: 140px;
object-fit: contain;
cursor: zoom-in;
border-radius: 4px;
min-width: 0;
font-size: 0.72rem;
padding: 3px 4px;
line-height: 1.2;
}
.journal-upload-status {
font-size: 0.68rem;
color: #8892b0;
min-height: 1.1em;
line-height: 1.25;
word-break: break-all;
}
.journal-upload-status--pending {
color: #c9b458;
}
.journal-upload-status--ok {
color: #6bc98a;
}
.journal-upload-status--err {
color: #ff7b7b;
}
.journal-upload-hint {
margin-top: 6px;
margin-top: 4px;
margin-bottom: 0;
font-size: 0.72rem;
color: #8892b0;
}
.journal-card .journal-upload-slots {
margin-bottom: 2px;
}
.journal-card .form-row.journal-chart-options {
margin-top: 6px;
margin-bottom: 6px;
gap: 6px;
}
.journal-card .mood-grid {
margin-top: 6px;
gap: 8px;
}
@media (max-width: 960px) {
.journal-card .journal-form-row1 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.journal-card .journal-form-row2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.journal-upload-slots {
flex-wrap: wrap;
}
.journal-upload-row {
flex: 1 1 calc(50% - 8px);
}
}
@media (max-width: 560px) {
.journal-upload-row {
flex: 1 1 100%;
}
}
.journal-detail-images {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1747,15 +1802,6 @@ html[data-theme="light"] .symbol-live-price--ok {
cursor: zoom-in;
}
html[data-theme="light"] .journal-upload-slot {
background: #f6f8fb;
border-color: #c5d0de;
}
html[data-theme="light"] .journal-upload-slot-preview {
background: #eef2f7;
}
html[data-theme="light"] .journal-detail-images {
border-top-color: #d0dae4;
}
-1
View File
@@ -43,7 +43,6 @@
`预期RR${escapeHtml(o.expect_rr || "-")}`,
`实际RR${escapeHtml(o.real_rr || "-")}`,
`保本后盯盘:${escapeHtml(o.post_breakeven_stare || "-")}`,
`占用时新开仓:${escapeHtml(o.new_trade_while_occupied || "-")}`,
`心态标签:${escapeHtml(moodTags)}`,
`备注:${escapeHtml(o.note || "无")}`,
];
+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 () {