feat: move trade journal editor into dialog
This commit is contained in:
+21
-5
@@ -95,6 +95,7 @@ const elements = {
|
|||||||
loading: document.querySelector("#loadingOverlay"),
|
loading: document.querySelector("#loadingOverlay"),
|
||||||
toast: document.querySelector("#toast"),
|
toast: document.querySelector("#toast"),
|
||||||
stockDialog: document.querySelector("#stockDialog"),
|
stockDialog: document.querySelector("#stockDialog"),
|
||||||
|
tradeLogDialog: document.querySelector("#tradeLogDialog"),
|
||||||
alertsDialog: document.querySelector("#alertsDialog"),
|
alertsDialog: document.querySelector("#alertsDialog"),
|
||||||
assistantDialog: document.querySelector("#assistantDialog"),
|
assistantDialog: document.querySelector("#assistantDialog"),
|
||||||
globalSearchDialog: document.querySelector("#globalSearchDialog"),
|
globalSearchDialog: document.querySelector("#globalSearchDialog"),
|
||||||
@@ -511,8 +512,11 @@ function bindEvents() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
document.querySelector("#journalForm").addEventListener("submit", saveJournal);
|
document.querySelector("#journalForm").addEventListener("submit", saveJournal);
|
||||||
|
document.querySelector("#openTradeLogDialog").addEventListener("click", openTradeLogDialog);
|
||||||
|
document.querySelector("#closeTradeLogDialog").addEventListener("click", closeTradeLogDialog);
|
||||||
document.querySelector("#tradeLogForm").addEventListener("submit", saveTradeLog);
|
document.querySelector("#tradeLogForm").addEventListener("submit", saveTradeLog);
|
||||||
document.querySelector("#cancelTradeEdit").addEventListener("click", resetTradeLogForm);
|
document.querySelector("#cancelTradeEdit").addEventListener("click", closeTradeLogDialog);
|
||||||
|
elements.tradeLogDialog.addEventListener("close", resetTradeLogForm);
|
||||||
document.querySelector("#tradeLogTableBody").addEventListener("click", handleTradeLogAction);
|
document.querySelector("#tradeLogTableBody").addEventListener("click", handleTradeLogAction);
|
||||||
document.querySelector("#stockNoteForm").addEventListener("submit", saveStockNote);
|
document.querySelector("#stockNoteForm").addEventListener("submit", saveStockNote);
|
||||||
document.querySelector("#watchStockButton").addEventListener("click", toggleActiveWatchlist);
|
document.querySelector("#watchStockButton").addEventListener("click", toggleActiveWatchlist);
|
||||||
@@ -1513,6 +1517,17 @@ async function saveJournal(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openTradeLogDialog() {
|
||||||
|
resetTradeLogForm();
|
||||||
|
if (!elements.tradeLogDialog.open) elements.tradeLogDialog.showModal();
|
||||||
|
requestAnimationFrame(() => document.querySelector("#tradeLogCode").focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeTradeLogDialog() {
|
||||||
|
if (elements.tradeLogDialog.open) elements.tradeLogDialog.close();
|
||||||
|
else resetTradeLogForm();
|
||||||
|
}
|
||||||
|
|
||||||
async function saveTradeLog(event) {
|
async function saveTradeLog(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const button = document.querySelector("#saveTradeLog");
|
const button = document.querySelector("#saveTradeLog");
|
||||||
@@ -1536,8 +1551,8 @@ async function saveTradeLog(event) {
|
|||||||
});
|
});
|
||||||
state.tradeEntries = payload.items || [];
|
state.tradeEntries = payload.items || [];
|
||||||
state.tradeSummary = payload.summary || {};
|
state.tradeSummary = payload.summary || {};
|
||||||
resetTradeLogForm();
|
|
||||||
renderTradeLog();
|
renderTradeLog();
|
||||||
|
closeTradeLogDialog();
|
||||||
showToast("交易记录已保存");
|
showToast("交易记录已保存");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(error.message || "交易记录保存失败");
|
showToast(error.message || "交易记录保存失败");
|
||||||
@@ -1552,7 +1567,7 @@ function resetTradeLogForm() {
|
|||||||
document.querySelector("#tradeLogDate").value = elements.tradeDate.value || todayString();
|
document.querySelector("#tradeLogDate").value = elements.tradeDate.value || todayString();
|
||||||
document.querySelector("#tradeLogQuantity").value = "0";
|
document.querySelector("#tradeLogQuantity").value = "0";
|
||||||
document.querySelector("#tradeLogPosition").value = "0";
|
document.querySelector("#tradeLogPosition").value = "0";
|
||||||
document.querySelector("#cancelTradeEdit").hidden = true;
|
setText("tradeLogDialogTitle", "交易日志");
|
||||||
setText("saveTradeLog", "保存交易");
|
setText("saveTradeLog", "保存交易");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1573,9 +1588,10 @@ function editTradeLog(id) {
|
|||||||
document.querySelector("#tradeLogTags").value = (item.tags || []).join(", ");
|
document.querySelector("#tradeLogTags").value = (item.tags || []).join(", ");
|
||||||
document.querySelector("#tradeLogThesis").value = item.thesis || "";
|
document.querySelector("#tradeLogThesis").value = item.thesis || "";
|
||||||
document.querySelector("#tradeLogExecution").value = item.execution || "";
|
document.querySelector("#tradeLogExecution").value = item.execution || "";
|
||||||
document.querySelector("#cancelTradeEdit").hidden = false;
|
setText("tradeLogDialogTitle", "编辑交易日志");
|
||||||
setText("saveTradeLog", "保存修改");
|
setText("saveTradeLog", "保存修改");
|
||||||
document.querySelector("#tradeLogForm").scrollIntoView({ behavior: motionEnabled() ? "smooth" : "auto", block: "start" });
|
if (!elements.tradeLogDialog.open) elements.tradeLogDialog.showModal();
|
||||||
|
requestAnimationFrame(() => document.querySelector("#tradeLogCode").focus());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTradeLogAction(event) {
|
async function handleTradeLogAction(event) {
|
||||||
|
|||||||
+31
-21
@@ -928,28 +928,11 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section class="workspace-section trade-journal-section">
|
<section class="workspace-section trade-journal-section">
|
||||||
<div class="workspace-heading"><h3>交易日志</h3><span id="tradeLogCount">0 条</span></div>
|
<div class="workspace-heading trade-log-heading">
|
||||||
|
<div><h3>交易日志记录</h3><span id="tradeLogCount">0 条</span></div>
|
||||||
|
<button id="openTradeLogDialog" class="button primary" type="button"><i data-lucide="notebook-pen"></i><span>交易日志</span></button>
|
||||||
|
</div>
|
||||||
<div id="tradeLogSummary" class="trade-log-summary"></div>
|
<div id="tradeLogSummary" class="trade-log-summary"></div>
|
||||||
<form id="tradeLogForm" class="trade-log-form">
|
|
||||||
<div class="trade-log-form-grid">
|
|
||||||
<label class="form-field"><span>交易日</span><input id="tradeLogDate" type="date" required></label>
|
|
||||||
<label class="form-field"><span>股票代码</span><input id="tradeLogCode" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required placeholder="002141"></label>
|
|
||||||
<label class="form-field"><span>股票名称</span><input id="tradeLogName" maxlength="40" required placeholder="贤丰控股"></label>
|
|
||||||
<label class="form-field"><span>动作</span><select id="tradeLogAction"><option value="buy">买入</option><option value="sell">卖出</option><option value="add">加仓</option><option value="trim">减仓</option><option value="watch">观察</option></select></label>
|
|
||||||
<label class="form-field"><span>成交价格</span><input id="tradeLogPrice" type="number" min="0.001" step="0.001" required></label>
|
|
||||||
<label class="form-field"><span>数量</span><input id="tradeLogQuantity" type="number" min="0" step="100" value="0"></label>
|
|
||||||
<label class="form-field"><span>仓位 %</span><input id="tradeLogPosition" type="number" min="0" max="100" step="0.1" value="0"></label>
|
|
||||||
<label class="form-field"><span>盈亏金额(可选)</span><input id="tradeLogPnlAmount" type="number" step="0.01"></label>
|
|
||||||
<label class="form-field"><span>盈亏 %(可选)</span><input id="tradeLogPnlPct" type="number" step="0.01"></label>
|
|
||||||
<label class="form-field"><span>当时情绪</span><select id="tradeLogEmotion"><option value="calm">平静</option><option value="confident">笃定</option><option value="hesitant">犹豫</option><option value="anxious">焦虑</option><option value="impulsive">冲动</option></select></label>
|
|
||||||
<label class="form-field trade-tags-field"><span>标签</span><input id="tradeLogTags" maxlength="160" placeholder="计划内, 追高, 止损"></label>
|
|
||||||
</div>
|
|
||||||
<div class="trade-log-text-grid">
|
|
||||||
<label class="form-field"><span>交易逻辑</span><textarea id="tradeLogThesis" maxlength="2000" placeholder="为什么在这里采取动作"></textarea></label>
|
|
||||||
<label class="form-field"><span>执行复核</span><textarea id="tradeLogExecution" maxlength="2000" placeholder="是否按计划执行,偏差在哪里"></textarea></label>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-actions"><button id="cancelTradeEdit" class="button" type="button" hidden>取消编辑</button><button id="saveTradeLog" class="button primary" type="submit">保存交易</button></div>
|
|
||||||
</form>
|
|
||||||
<div class="table-frame trade-log-table-frame">
|
<div class="table-frame trade-log-table-frame">
|
||||||
<table class="data-table trade-log-table">
|
<table class="data-table trade-log-table">
|
||||||
<thead><tr><th>日期</th><th>股票</th><th>动作</th><th class="number">价格</th><th class="number">数量</th><th class="number">仓位</th><th class="number">盈亏</th><th>情绪 / 标签</th><th>交易逻辑</th><th>执行复核</th><th>操作</th></tr></thead>
|
<thead><tr><th>日期</th><th>股票</th><th>动作</th><th class="number">价格</th><th class="number">数量</th><th class="number">仓位</th><th class="number">盈亏</th><th>情绪 / 标签</th><th>交易逻辑</th><th>执行复核</th><th>操作</th></tr></thead>
|
||||||
@@ -972,6 +955,33 @@
|
|||||||
<span id="updatedAt">--</span>
|
<span id="updatedAt">--</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<dialog id="tradeLogDialog" class="settings-dialog trade-log-dialog" aria-labelledby="tradeLogDialogTitle">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<div><span class="dialog-eyebrow">我的复盘</span><h2 id="tradeLogDialogTitle">交易日志</h2></div>
|
||||||
|
<button id="closeTradeLogDialog" class="icon-button" type="button" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button>
|
||||||
|
</div>
|
||||||
|
<form id="tradeLogForm" class="trade-log-form">
|
||||||
|
<div class="trade-log-form-grid">
|
||||||
|
<label class="form-field"><span>交易日</span><input id="tradeLogDate" type="date" required></label>
|
||||||
|
<label class="form-field"><span>股票代码</span><input id="tradeLogCode" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required placeholder="002141"></label>
|
||||||
|
<label class="form-field"><span>股票名称</span><input id="tradeLogName" maxlength="40" required placeholder="贤丰控股"></label>
|
||||||
|
<label class="form-field"><span>动作</span><select id="tradeLogAction"><option value="buy">买入</option><option value="sell">卖出</option><option value="add">加仓</option><option value="trim">减仓</option><option value="watch">观察</option></select></label>
|
||||||
|
<label class="form-field"><span>成交价格</span><input id="tradeLogPrice" type="number" min="0.001" step="0.001" required></label>
|
||||||
|
<label class="form-field"><span>数量</span><input id="tradeLogQuantity" type="number" min="0" step="100" value="0"></label>
|
||||||
|
<label class="form-field"><span>仓位 %</span><input id="tradeLogPosition" type="number" min="0" max="100" step="0.1" value="0"></label>
|
||||||
|
<label class="form-field"><span>盈亏金额(可选)</span><input id="tradeLogPnlAmount" type="number" step="0.01"></label>
|
||||||
|
<label class="form-field"><span>盈亏 %(可选)</span><input id="tradeLogPnlPct" type="number" step="0.01"></label>
|
||||||
|
<label class="form-field"><span>当时情绪</span><select id="tradeLogEmotion"><option value="calm">平静</option><option value="confident">笃定</option><option value="hesitant">犹豫</option><option value="anxious">焦虑</option><option value="impulsive">冲动</option></select></label>
|
||||||
|
<label class="form-field trade-tags-field"><span>标签</span><input id="tradeLogTags" maxlength="160" placeholder="计划内, 追高, 止损"></label>
|
||||||
|
</div>
|
||||||
|
<div class="trade-log-text-grid">
|
||||||
|
<label class="form-field"><span>交易逻辑</span><textarea id="tradeLogThesis" maxlength="2000" placeholder="为什么在这里采取动作"></textarea></label>
|
||||||
|
<label class="form-field"><span>执行复核</span><textarea id="tradeLogExecution" maxlength="2000" placeholder="是否按计划执行,偏差在哪里"></textarea></label>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-actions"><button id="cancelTradeEdit" class="button" type="button">取消</button><button id="saveTradeLog" class="button primary" type="submit">保存交易</button></div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<div id="stockPreviewBackdrop" class="stock-preview-backdrop" hidden></div>
|
<div id="stockPreviewBackdrop" class="stock-preview-backdrop" hidden></div>
|
||||||
<aside id="stockPreview" class="stock-preview" aria-label="个股行情预览" aria-live="polite" hidden>
|
<aside id="stockPreview" class="stock-preview" aria-label="个股行情预览" aria-live="polite" hidden>
|
||||||
<header class="stock-preview-header">
|
<header class="stock-preview-header">
|
||||||
|
|||||||
@@ -11475,6 +11475,11 @@ button.account-role-badge:focus-visible { outline: 2px solid var(--blue); outlin
|
|||||||
|
|
||||||
.trade-journal-section { grid-column: 1 / -1; padding: 0; }
|
.trade-journal-section { grid-column: 1 / -1; padding: 0; }
|
||||||
.trade-journal-section > .workspace-heading { padding: 16px 16px 0; }
|
.trade-journal-section > .workspace-heading { padding: 16px 16px 0; }
|
||||||
|
.trade-log-heading > div { display: flex; align-items: baseline; gap: 9px; min-width: 0; }
|
||||||
|
.trade-log-heading > div > span { color: var(--text-secondary); font-size: 11px; }
|
||||||
|
.trade-log-heading .button { flex: 0 0 auto; }
|
||||||
|
.trade-log-dialog { width: min(1020px, calc(100vw - 24px)); overflow-y: auto; }
|
||||||
|
.trade-log-dialog .trade-log-form { border: 0; }
|
||||||
.trade-log-form { padding: 16px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); background: var(--surface-muted); }
|
.trade-log-form { padding: 16px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); background: var(--surface-muted); }
|
||||||
.trade-log-form-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
|
.trade-log-form-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
|
||||||
.trade-tags-field { grid-column: span 2; }
|
.trade-tags-field { grid-column: span 2; }
|
||||||
@@ -11524,6 +11529,7 @@ button.account-role-badge:focus-visible { outline: 2px solid var(--blue); outlin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
.trade-log-dialog { width: calc(100vw - 16px); margin: 8px auto; }
|
||||||
.trade-log-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.trade-log-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
.trade-log-summary > div { border-bottom: 1px solid var(--border); }
|
.trade-log-summary > div { border-bottom: 1px solid var(--border); }
|
||||||
.trade-log-summary > div:nth-child(2n) { border-right: 0; }
|
.trade-log-summary > div:nth-child(2n) { border-right: 0; }
|
||||||
|
|||||||
@@ -234,6 +234,20 @@ test("new review workflows render account-scoped records", async ({ page }) => {
|
|||||||
await page.locator('[data-view="reviewWorkspaceView"]').first().click();
|
await page.locator('[data-view="reviewWorkspaceView"]').first().click();
|
||||||
await expect(page.locator("#tradeLogTableBody tr")).toHaveCount(1);
|
await expect(page.locator("#tradeLogTableBody tr")).toHaveCount(1);
|
||||||
await expect(page.locator("#tradeLogEmpty")).toBeHidden();
|
await expect(page.locator("#tradeLogEmpty")).toBeHidden();
|
||||||
|
await expect(page.locator("#tradeLogForm")).toBeHidden();
|
||||||
|
await page.locator("#openTradeLogDialog").click();
|
||||||
|
await expect(page.locator("#tradeLogDialog")).toBeVisible();
|
||||||
|
await page.locator("#tradeLogCode").fill("002141");
|
||||||
|
await page.locator("#tradeLogName").fill("Test Stock");
|
||||||
|
await page.locator("#tradeLogPrice").fill("10.8");
|
||||||
|
await page.locator("#saveTradeLog").click();
|
||||||
|
await expect(page.locator("#tradeLogDialog")).toBeHidden();
|
||||||
|
await expect(page.locator("#reviewWorkspaceView")).toHaveClass(/active-view/);
|
||||||
|
await expect(page.locator("#tradeLogTableBody tr")).toHaveCount(1);
|
||||||
|
await page.locator('[data-trade-action="edit"]').click();
|
||||||
|
await expect(page.locator("#tradeLogDialogTitle")).toHaveText("编辑交易日志");
|
||||||
|
await page.locator("#cancelTradeEdit").click();
|
||||||
|
await expect(page.locator("#tradeLogDialog")).toBeHidden();
|
||||||
|
|
||||||
await page.locator("#alertButton").click();
|
await page.locator("#alertButton").click();
|
||||||
await expect(page.locator("#alertList .alert-item")).toHaveCount(1);
|
await expect(page.locator("#alertList .alert-item")).toHaveCount(1);
|
||||||
@@ -258,7 +272,14 @@ for (const viewport of [
|
|||||||
await page.locator('[data-view="reviewWorkspaceView"]').first().click();
|
await page.locator('[data-view="reviewWorkspaceView"]').first().click();
|
||||||
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
|
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
|
||||||
expect(overflow).toBeLessThanOrEqual(1);
|
expect(overflow).toBeLessThanOrEqual(1);
|
||||||
|
await expect(page.locator("#openTradeLogDialog")).toBeVisible();
|
||||||
|
await expect(page.locator("#tradeLogForm")).toBeHidden();
|
||||||
|
await page.locator("#openTradeLogDialog").click();
|
||||||
|
await expect(page.locator("#tradeLogDialog")).toBeVisible();
|
||||||
await expect(page.locator("#tradeLogForm")).toBeVisible();
|
await expect(page.locator("#tradeLogForm")).toBeVisible();
|
||||||
|
const dialogWidth = await page.locator("#tradeLogDialog").evaluate((dialog) => dialog.getBoundingClientRect().width);
|
||||||
|
expect(dialogWidth).toBeLessThanOrEqual(viewport.width);
|
||||||
|
await page.locator("#closeTradeLogDialog").click();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,12 @@ class FrontendContractTests(unittest.TestCase):
|
|||||||
self.assertIn('elements.assistantDialog.classList.toggle("member-locked", !unlocked);', self.script)
|
self.assertIn('elements.assistantDialog.classList.toggle("member-locked", !unlocked);', self.script)
|
||||||
self.assertIn('button.disabled = !unlocked || state.assistantLoading;', self.script)
|
self.assertIn('button.disabled = !unlocked || state.assistantLoading;', self.script)
|
||||||
|
|
||||||
|
def test_trade_log_editor_is_dialog_based(self):
|
||||||
|
self.assertIn('id="openTradeLogDialog"', self.html)
|
||||||
|
self.assertIn('id="tradeLogDialog" class="settings-dialog trade-log-dialog"', self.html)
|
||||||
|
self.assertIn('elements.tradeLogDialog.showModal()', self.script)
|
||||||
|
self.assertIn('renderTradeLog();\n closeTradeLogDialog();', self.script)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user