diff --git a/app/frontend/m/css/features.css b/app/frontend/m/css/features.css
index 455d1a2..bb169e8 100644
--- a/app/frontend/m/css/features.css
+++ b/app/frontend/m/css/features.css
@@ -2765,6 +2765,40 @@
.m-num-strong { font-weight: 600; }
.m-cell-text--empty { color: var(--text-tertiary); }
+/* 详情 Sheet 内跟踪备注行 */
+.m-remark-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin: 12px 0 0;
+ padding: 10px 12px;
+ background: var(--surface);
+ border-radius: 8px;
+}
+.m-remark-label { flex: 0 0 auto; font-size: 12px; color: var(--text-secondary); }
+.m-remark-text {
+ flex: 1 1 auto;
+ min-width: 0;
+ font-size: 13.5px;
+ color: var(--text-primary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.m-remark-edit {
+ flex: 0 0 auto;
+ appearance: none;
+ border: none;
+ background: transparent;
+ color: var(--action);
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ padding: 4px 2px;
+ -webkit-tap-highlight-color: transparent;
+}
+.m-remark-edit:active { opacity: 0.7; }
+
/* 自选股:颜色圆点标记 */
.m-stock--watch { flex-direction: row; align-items: center; gap: 8px; }
.m-stock-copy { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; min-width: 0; }
diff --git a/app/frontend/m/js/pages.js b/app/frontend/m/js/pages.js
index 251a665..6ca154f 100644
--- a/app/frontend/m/js/pages.js
+++ b/app/frontend/m/js/pages.js
@@ -3392,6 +3392,63 @@
});
}
+ function openRemarkEditSheet() {
+ const detail = state.detail;
+ if (!detail || !detail.code) return;
+ const item = detail.watchItem || {};
+ const current = item.remark || "";
+ openSheet(
+ '
编辑跟踪备注
' +
+ '" +
+ '' +
+ '
" +
+ '
' +
+ "
",
+ { detail: false }
+ );
+ const input = document.getElementById("m-remark-input");
+ if (input) {
+ input.addEventListener("input", function () {
+ const counter = document.querySelector('[data-counter-for="m-remark-input"]');
+ if (counter) counter.textContent = input.value.length + "/240";
+ });
+ }
+ }
+
+ function saveRemark() {
+ const detail = state.detail;
+ if (!detail || !detail.code) return;
+ const item = detail.watchItem || {};
+ const code = detail.code;
+ const remark = document.getElementById("m-remark-input").value.trim();
+ const payload = {
+ code: code,
+ name: item.name || detail.name || code,
+ sector: item.sector || detail.sector || "其他",
+ color: item.color || "red",
+ remark: remark
+ };
+ global.MobileAPI.request("/api/watchlist", "POST", payload).then(function () {
+ const refresh = state.review && state.review.watchlist
+ ? global.MobileAPI.request("/api/watchlist?trade_date=" + encodeURIComponent(todayString())).then(function (res) {
+ state.review.watchlist = res || state.review.watchlist;
+ renderReviewWatchlist();
+ })
+ : Promise.resolve();
+ return refresh;
+ }).then(function () {
+ closeSheet();
+ openDetailSheet(code);
+ showToast("备注已保存");
+ }).catch(function (error) {
+ showToast(error && error.message ? error.message : "保存失败");
+ });
+ }
+
function showToast(message) {
const root = document.getElementById("m-sheet-root");
if (!root) return;
@@ -3427,6 +3484,7 @@
name: name === "--" ? "" : name,
sector: industry === "其他" ? "" : industry,
watchlist: Boolean(stock.watchlist),
+ watchItem: stock.watchlist || null,
tab: "intraday",
payload: payload,
};
@@ -3440,6 +3498,15 @@
const intradayOk = meta.intraday_status === "available";
const chartHtml = intradayOk ? intradayChart(payload) : emptyChart(meta.intraday_notice || "分时数据暂不可用");
+ const watchItem = stock.watchlist || null;
+ const remarkRow = watchItem
+ ? '"
+ : "";
+
body.innerHTML =
'' +
'
' +
@@ -3454,6 +3521,7 @@
'' + escapeHtml(chartCaptionHtml("intraday")) + "" +
"
" +
'
' + chartHtml + "
" +
+ remarkRow +
'
点按图面可读数值 · 数据来源:本地行情
';
}
@@ -4207,7 +4275,7 @@
'
' + escapeHtml(item.title) + "
" +
'
' + escapeHtml(displayCompactDate(item.available_date)) + (upcoming ? "(未到期)" : "") + "
" +
(item.content ? '
' + escapeHtml(item.content) + "
" : "") +
- (item.code ? '
" : "") +
+ (item.code ? '
" : "") +
"
" +
'' +
(canRead ? '' : "") +
@@ -4258,7 +4326,8 @@
/* ----- 全屏表单页 ----- */
function openForm(kind, opts) {
- state.review.form = { kind: kind, opts: opts || {}, action: "buy", emotion: "calm", stock: null };
+ const t = (opts && opts.trade) || {};
+ state.review.form = { kind: kind, opts: opts || {}, action: t.action || "buy", emotion: t.emotion || "calm", stock: null };
state.review.dirty = false;
const title = kind === "trade" ? ((opts && opts.trade) ? "编辑交易" : "新增交易") : kind === "daily" ? "写复盘" : "新增笔记";
global.MobileRouter.updateHeader({ title: title, back: true, actions: "" });
@@ -4946,12 +5015,12 @@
if (reviewDeleteNote) { deleteNote(reviewDeleteNote.dataset.noteId); return; }
const noteCard = event.target.closest("[data-note-id]");
if (noteCard) { openNoteDetail(noteCard.dataset.noteId); return; }
- const alertRow = event.target.closest("[data-alert-id]");
- if (alertRow) { openAlertDetail(alertRow.dataset.alertId); return; }
const alertRead = event.target.closest("[data-review-alert-read]");
if (alertRead) { markAlertRead(alertRead.dataset.alertId); return; }
const alertDelete = event.target.closest("[data-review-alert-delete]");
if (alertDelete) { deleteAlert(alertDelete.dataset.alertId); return; }
+ const alertRow = event.target.closest(".m-alert-row[data-alert-id]");
+ if (alertRow) { openAlertDetail(alertRow.dataset.alertId); return; }
const alertFilter = event.target.closest("[data-review-alert-filter]");
if (alertFilter) {
state.review.alertStatus = alertFilter.dataset.reviewAlertFilter === "unread" ? "unread" : "all";
@@ -4964,6 +5033,10 @@
if (alertSave) { submitAlertAdd(); return; }
const alertStock = event.target.closest("[data-alert-stock]");
if (alertStock) { openDetailSheet(alertStock.dataset.code); return; }
+ const remarkEdit = event.target.closest("[data-remark-edit]");
+ if (remarkEdit) { openRemarkEditSheet(); return; }
+ const remarkSave = event.target.closest("[data-remark-save]");
+ if (remarkSave) { saveRemark(); return; }
const row = event.target.closest("[data-code]");
if (row) {