v1.8.0: 🌙 静默期 - 舆情监控/持仓跟踪/定时报告三大自动化任务独立配置静默时段, 命中时段不发送邮件(多段/跨午夜, 日志标quiet, 水位照常推进不补发积压)
This commit is contained in:
@@ -200,6 +200,8 @@ cd works/stock-advisor
|
||||
|
||||
**防打扰机制**:`monitor_last_news_id` 水位只扫新增;首次启动只建水位不通知历史;单封邮件最多 20 条。
|
||||
|
||||
**🌙 静默期**:可配置某些时段不发送邮件(如夜间/午休),支持多段 `23:00-07:30,12:00-13:00`(跨午夜)。命中静默时段时扫描照常、仅抑制发送(日志标记为 `quiet`,水位正常推进,不会在结束后补发积压)。在 ⏰ 自动化页开启。
|
||||
|
||||
### 5. 持仓跟踪智能体(engine/agent.py + chain_data.py)
|
||||
|
||||
对**持仓(自选股)**与**目标概念/主题/股票**定期主动跟踪,由**大模型智能体 + 工作流**驱动:
|
||||
@@ -219,6 +221,7 @@ cd works/stock-advisor
|
||||
- **影响度判定**:大模型输出 JSON(significance / impact_score / change_kind / summary / chain_trend),前置输出防截断,容错解析
|
||||
- **主动调度**:后台调度器按间隔(默认 60 分钟)跟踪全部持仓+目标;支持「立即跟踪全部目标/单只」
|
||||
- **重大变化通知**:impact_score ≥ 阈值(默认 65)自动邮件(股票/概念通用)
|
||||
- **🌙 静默期**:可配置某些时段不发送重大变化邮件(跟踪报告照常生成入库),在 ⏰ 自动化页开启,多段/跨午夜均支持
|
||||
- **页面 `/tracking`**:股票+概念报告总览;**⏰ 自动化**页可管理持仓与目标、查看最近报告
|
||||
|
||||
### 6. 系统设置(/settings)
|
||||
@@ -244,6 +247,7 @@ cd works/stock-advisor
|
||||
- **全球市场数据** `global_markets` 表(模拟):道琼斯/纳斯达克/标普/恒生/日经/KOSPI/德法英等 9 大指数
|
||||
- 报告由 DeepSeek 基于当日数据生成研判(简版+详版两次调用),失败自动降级为数据速览
|
||||
- **手动触发**:数据管理页「定时报告」卡片(🌅/🌇 按钮)+ 发送日志 `report_log` 表
|
||||
- **🌙 静默期**:数据管理页可配置报告静默时段,命中时段内不生成、不发送(日志标记为 `quiet`)
|
||||
- CLI:`python3 reports.py premarket|postmarket`
|
||||
|
||||
---
|
||||
@@ -315,7 +319,7 @@ cd works/stock-advisor
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/api/settings` | 读取全部设置 |
|
||||
| POST | `/api/settings` | 保存设置 `{mail, llm, monitor}` |
|
||||
| POST | `/api/settings` | 保存设置 `{mail, llm, monitor, tracking, quiet}`(quiet 含 monitor/tracking/report 三任务静默期) |
|
||||
| POST | `/api/settings/test-email` | 发送测试邮件 |
|
||||
| POST | `/api/settings/test-llm` | 测试大模型连接 |
|
||||
| POST | `/api/monitor/scan` | 立即扫描新闻 |
|
||||
@@ -384,6 +388,7 @@ IS_MOCK = False
|
||||
| v1.5.0 | 舆情监控与持仓跟踪合并为「⏰ 自动化」导航入口(双选项卡);修复布尔设置存储兼容 |
|
||||
| v1.6.0 | 目标跟踪:概念/主题/股票(watch_targets),概念智能体分析+受益个股,持仓管理与目标管理,报告支持双类型 |
|
||||
| v1.7.0 | 每日定时报告:工作日 9:00 盘前 / 15:30 盘后,简版正文+详细版附件,全球市场数据,cron 定时+手动触发 |
|
||||
| v1.8.0 | 🌙 静默期:舆情监控/持仓跟踪/定时报告三大自动化任务均可独立配置静默时段(多段/跨午夜),命中时段不发送邮件(日志标记 quiet) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -561,7 +561,7 @@ def api_backtest_rebuild():
|
||||
@app.route("/api/settings")
|
||||
def api_settings():
|
||||
from settings import all_settings, mail_config, monitor_config, monitor_state, \
|
||||
tracking_config, tracking_state
|
||||
tracking_config, tracking_state, quiet_config
|
||||
from config import LLM_BASE_URL, LLM_API_KEY, LLM_MODEL
|
||||
s = all_settings()
|
||||
mail = mail_config()
|
||||
@@ -571,6 +571,11 @@ def api_settings():
|
||||
"mail": mail,
|
||||
"monitor": mono,
|
||||
"tracking": {**track, **tracking_state()},
|
||||
"quiet": { # 各自动化任务的静默期配置
|
||||
"monitor": quiet_config("monitor"),
|
||||
"tracking": quiet_config("tracking"),
|
||||
"report": quiet_config("report"),
|
||||
},
|
||||
"llm": {
|
||||
"base_url": s.get("llm_base_url", LLM_BASE_URL),
|
||||
"api_key": s.get("llm_api_key", LLM_API_KEY),
|
||||
@@ -614,6 +619,14 @@ def api_settings_save():
|
||||
set_setting("tracking_enabled", _bool_str(track["tracking_enabled"]))
|
||||
if "tracking_notify" in track:
|
||||
set_setting("tracking_notify", _bool_str(track["tracking_notify"]))
|
||||
# 静默期(各自动化任务独立配置)
|
||||
quiet = body.get("quiet") or {}
|
||||
for prefix in ("monitor", "tracking", "report"):
|
||||
q = quiet.get(prefix) or {}
|
||||
if "enabled" in q:
|
||||
set_setting(f"{prefix}_quiet_enabled", _bool_str(q["enabled"]))
|
||||
if "ranges" in q:
|
||||
set_setting(f"{prefix}_quiet_ranges", str(q["ranges"] or "").strip())
|
||||
return jsonify({"ok": True, "msg": "设置已保存"})
|
||||
|
||||
|
||||
|
||||
+7
-2
@@ -19,7 +19,8 @@ import threading
|
||||
import time
|
||||
|
||||
from database import query, query_one, execute
|
||||
from settings import tracking_config, tracking_state, set_tracking_state, mail_config
|
||||
from settings import tracking_config, tracking_state, set_tracking_state, mail_config, \
|
||||
quiet_config, in_quiet_period
|
||||
from engine.chain_data import get_chain
|
||||
from engine.indicators import compute_indicators
|
||||
from rag.vector_store import query_vectors
|
||||
@@ -287,10 +288,14 @@ def _first_heading(text):
|
||||
|
||||
|
||||
def _notify_if_significant(rid, stock, meta):
|
||||
"""影响度达阈值且开启通知 → 邮件(股票/概念通用)"""
|
||||
"""影响度达阈值且开启通知 → 邮件(股票/概念通用)。静默期内抑制发送。"""
|
||||
cfg = tracking_config()
|
||||
try:
|
||||
if cfg["notify"] and int(meta.get("impact_score", 0)) >= int(cfg["impact_threshold"]):
|
||||
qc = quiet_config("tracking")
|
||||
if qc["enabled"] and in_quiet_period(qc["ranges"]):
|
||||
log.info("tracking notify suppressed by quiet period: %s", stock.get("name"))
|
||||
return
|
||||
from engine.notifier import send_email
|
||||
mc = mail_config()
|
||||
nc = meta.get("news_counts") or {}
|
||||
|
||||
+30
-22
@@ -18,7 +18,8 @@ from email.mime.text import MIMEText
|
||||
from email.utils import formataddr, formatdate
|
||||
|
||||
from database import query, execute, query_one
|
||||
from settings import mail_config, monitor_config, monitor_state, set_monitor_state
|
||||
from settings import mail_config, monitor_config, monitor_state, set_monitor_state, \
|
||||
quiet_config, in_quiet_period
|
||||
|
||||
log = logging.getLogger("notifier")
|
||||
|
||||
@@ -180,27 +181,34 @@ def scan_news(force=False):
|
||||
|
||||
sent, failed = 0, 0
|
||||
if important:
|
||||
# 单封邮件最多带 20 条,避免超大邮件
|
||||
shown = important[:20]
|
||||
omitted = len(important) - len(shown)
|
||||
items = [{"news": it["news"], "score": it["score"],
|
||||
"stocks": _fetch_related_names([c for c in (it["news"]["related_stocks"] or "").split(",") if c])}
|
||||
for it in shown]
|
||||
subject = f"[舆情监控] {len(important)} 条重要资讯:{important[0]['news']['title'][:24]}" + \
|
||||
(f" 等{len(important)}条" if len(important) > 1 else "")
|
||||
try:
|
||||
html = build_email_html(items)
|
||||
if omitted:
|
||||
html += f"<p style='color:#888;font-size:12px'>……另有 {omitted} 条重要资讯已省略,详见系统。</p>"
|
||||
if cfg.get("email_enabled", True):
|
||||
send_email(subject, html)
|
||||
status, msg = "sent", ""
|
||||
else:
|
||||
status, msg = "skipped", "邮件功能未启用"
|
||||
except Exception as e:
|
||||
status, msg = "failed", str(e)
|
||||
failed = len(important)
|
||||
log.warning("notify email fail: %s", e)
|
||||
# 静默期判断:命中则本次不发送邮件(仍记账推进水位,避免静默结束后补发积压)
|
||||
qc = quiet_config("monitor")
|
||||
quiet = qc["enabled"] and in_quiet_period(qc["ranges"])
|
||||
omitted = 0
|
||||
if quiet:
|
||||
status, msg = "quiet", "静默期内已抑制发送"
|
||||
else:
|
||||
# 单封邮件最多带 20 条,避免超大邮件
|
||||
shown = important[:20]
|
||||
omitted = len(important) - len(shown)
|
||||
items = [{"news": it["news"], "score": it["score"],
|
||||
"stocks": _fetch_related_names([c for c in (it["news"]["related_stocks"] or "").split(",") if c])}
|
||||
for it in shown]
|
||||
subject = f"[舆情监控] {len(important)} 条重要资讯:{important[0]['news']['title'][:24]}" + \
|
||||
(f" 等{len(important)}条" if len(important) > 1 else "")
|
||||
try:
|
||||
html = build_email_html(items)
|
||||
if omitted:
|
||||
html += f"<p style='color:#888;font-size:12px'>……另有 {omitted} 条重要资讯已省略,详见系统。</p>"
|
||||
if cfg.get("email_enabled", True):
|
||||
send_email(subject, html)
|
||||
status, msg = "sent", ""
|
||||
else:
|
||||
status, msg = "skipped", "邮件功能未启用"
|
||||
except Exception as e:
|
||||
status, msg = "failed", str(e)
|
||||
failed = len(important)
|
||||
log.warning("notify email fail: %s", e)
|
||||
# 写日志
|
||||
for it in important:
|
||||
execute(
|
||||
|
||||
+10
-2
@@ -14,7 +14,7 @@ import logging
|
||||
import time
|
||||
|
||||
from database import query, query_one, execute
|
||||
from settings import mail_config
|
||||
from settings import mail_config, quiet_config, in_quiet_period
|
||||
from engine.analyst import llm_chat
|
||||
|
||||
log = logging.getLogger("report")
|
||||
@@ -428,8 +428,16 @@ th{{background:#f8fafc;color:#475569;}}
|
||||
|
||||
# ===================================================================== 发送
|
||||
def send_daily_report(kind="premarket"):
|
||||
"""生成并发送报告:正文=简版,附件=详细版 HTML。返回 dict 状态"""
|
||||
"""生成并发送报告:正文=简版,附件=详细版 HTML。静默期内抑制发送。返回 dict 状态"""
|
||||
from engine.notifier import send_email
|
||||
# 静默期判断:命中则不生成、不发送,仅记一条 quiet 日志
|
||||
qc = quiet_config("report")
|
||||
if qc["enabled"] and in_quiet_period(qc["ranges"]):
|
||||
meta = KIND_META.get(kind, KIND_META["premarket"])
|
||||
subject = f"[智能荐股] {meta['name']} {time.strftime('%Y-%m-%d')}"
|
||||
execute("INSERT INTO report_log(kind, subject, brief_len, detail_len, status, message) "
|
||||
"VALUES(?,?,0,0,'quiet',?)", (kind, subject, "静默期内已抑制发送"))
|
||||
return {"ok": False, "quiet": True, "msg": "静默期内已抑制发送"}
|
||||
mc = mail_config()
|
||||
brief_html, detail_html = generate_reports(kind)
|
||||
meta = KIND_META.get(kind, KIND_META["premarket"])
|
||||
|
||||
+48
@@ -4,6 +4,8 @@
|
||||
- get_setting(key, default) / set_setting(key, value) / all_settings() / save_all(dict)
|
||||
- 提供 LLM / 邮件 / 监控 三段配置的便捷读取
|
||||
"""
|
||||
import time
|
||||
|
||||
from database import query, execute, query_one
|
||||
from config import LLM_BASE_URL, LLM_API_KEY, LLM_MODEL, LLM_MAX_TOKENS, \
|
||||
LLM_TEMPERATURE, LLM_TIMEOUT, MAIL_DEFAULTS, MONITOR_DEFAULTS, TRACKING_DEFAULTS
|
||||
@@ -116,6 +118,52 @@ def tracking_config():
|
||||
}
|
||||
|
||||
|
||||
# ===================================================================== 静默期
|
||||
def quiet_config(prefix):
|
||||
"""某个自动化任务的静默期配置。prefix: monitor / tracking / report"""
|
||||
return {
|
||||
"enabled": _truthy(get_setting(f"{prefix}_quiet_enabled", "0")),
|
||||
"ranges": (get_setting(f"{prefix}_quiet_ranges", "") or "").strip(),
|
||||
}
|
||||
|
||||
|
||||
def in_quiet_period(ranges_str=None, now=None):
|
||||
"""判断当前时刻是否落在静默时段内。
|
||||
ranges 格式:"22:00-07:30"(支持跨午夜)或 "23:00-07:30,12:00-13:00"(多个时段逗号分隔)。
|
||||
任一时段命中即返回 True;解析失败/空配置返回 False。
|
||||
"""
|
||||
ranges_str = (ranges_str or "").strip()
|
||||
if not ranges_str:
|
||||
return False
|
||||
if now is None:
|
||||
now = time.localtime()
|
||||
if hasattr(now, "tm_hour"):
|
||||
cur = now.tm_hour * 60 + now.tm_min
|
||||
else: # datetime 对象
|
||||
cur = now.hour * 60 + now.minute
|
||||
for part in ranges_str.replace(",", ",").split(","):
|
||||
part = part.strip()
|
||||
if "-" not in part:
|
||||
continue
|
||||
try:
|
||||
a, b = part.split("-", 1)
|
||||
sh, sm = a.strip().split(":")
|
||||
eh, em = b.strip().split(":")
|
||||
start = int(sh) * 60 + int(sm)
|
||||
end = int(eh) * 60 + int(em)
|
||||
except Exception:
|
||||
continue
|
||||
if start == end:
|
||||
continue
|
||||
if start < end:
|
||||
if start <= cur < end:
|
||||
return True
|
||||
else: # 跨天时段,如 23:00-07:30
|
||||
if cur >= start or cur < end:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def tracking_state():
|
||||
return {
|
||||
"last_run": get_setting("tracking_last_run", ""),
|
||||
|
||||
+22
-1
@@ -52,6 +52,26 @@ async function sendReport(kind) {
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
/* 定时报告 · 静默期 */
|
||||
async function loadReportQuiet() {
|
||||
try {
|
||||
const d = await api('/api/settings');
|
||||
const q = (d.quiet && d.quiet.report) || {};
|
||||
$('#reportQuietEnabled').checked = !!q.enabled;
|
||||
$('#reportQuietRanges').value = q.ranges || '';
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function saveReportQuiet() {
|
||||
try {
|
||||
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ quiet: { report: {
|
||||
enabled: $('#reportQuietEnabled').checked,
|
||||
ranges: $('#reportQuietRanges').value.trim()
|
||||
} } }) });
|
||||
toast(r.msg || '已保存');
|
||||
} catch (e) { toast('保存失败:' + e.message); }
|
||||
}
|
||||
|
||||
async function loadReportLog() {
|
||||
try {
|
||||
const d = await api('/api/report/log');
|
||||
@@ -62,7 +82,7 @@ async function loadReportLog() {
|
||||
<td style="color:var(--muted)">${r.sent_at}</td>
|
||||
<td>${r.kind === 'premarket' ? '🌅 盘前' : '🌇 盘后'}</td>
|
||||
<td>${escapeHtml(r.subject)}</td>
|
||||
<td><span class="tag ${r.status === 'sent' ? 'tag-推荐' : 'tag-负'}">${r.status}</span></td>
|
||||
<td><span class="tag ${r.status === 'sent' ? 'tag-推荐' : r.status === 'quiet' ? 'tag-关注' : 'tag-负'}">${r.status === 'sent' ? '已发送' : r.status === 'quiet' ? '🌙 静默' : '失败'}</span></td>
|
||||
<td style="color:var(--text2);font-size:12px">${escapeHtml(r.message || '')}</td>
|
||||
</tr>`).join('');
|
||||
} catch (e) {}
|
||||
@@ -96,3 +116,4 @@ async function health() {
|
||||
|
||||
refreshStats();
|
||||
loadReportLog();
|
||||
loadReportQuiet();
|
||||
+28
-3
@@ -21,12 +21,20 @@ async function loadAuto() {
|
||||
const cats = mono.categories || [];
|
||||
$$('#monitorCats input').forEach(c => c.checked = cats.includes(c.value));
|
||||
renderMonitorState(autoSettings.state);
|
||||
// 静默期(舆情)
|
||||
const mq = (autoSettings.quiet && autoSettings.quiet.monitor) || {};
|
||||
$('#monitorQuietEnabled').checked = !!mq.enabled;
|
||||
$('#monitorQuietRanges').value = mq.ranges || '';
|
||||
// 持仓跟踪
|
||||
$('#trackingEnabled').checked = !!tr.enabled;
|
||||
$('#trackingInterval').value = tr.interval_min;
|
||||
$('#trackingImpact').value = tr.impact_threshold;
|
||||
$('#trackingNotify').checked = !!tr.notify;
|
||||
renderTrackingState(tr);
|
||||
// 静默期(跟踪)
|
||||
const tq = (autoSettings.quiet && autoSettings.quiet.tracking) || {};
|
||||
$('#trackingQuietEnabled').checked = !!tq.enabled;
|
||||
$('#trackingQuietRanges').value = tq.ranges || '';
|
||||
loadLog();
|
||||
loadTrackReports();
|
||||
loadPositions();
|
||||
@@ -61,10 +69,27 @@ function collectMonitor() {
|
||||
};
|
||||
}
|
||||
|
||||
function collectMonitorQuiet() {
|
||||
return {
|
||||
enabled: $('#monitorQuietEnabled').checked,
|
||||
ranges: $('#monitorQuietRanges').value.trim()
|
||||
};
|
||||
}
|
||||
|
||||
function collectTrackingQuiet() {
|
||||
return {
|
||||
enabled: $('#trackingQuietEnabled').checked,
|
||||
ranges: $('#trackingQuietRanges').value.trim()
|
||||
};
|
||||
}
|
||||
|
||||
/* ===================== 保存 ===================== */
|
||||
async function saveMonitor() {
|
||||
try {
|
||||
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ monitor: collectMonitor() }) });
|
||||
const r = await api('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
|
||||
monitor: collectMonitor(),
|
||||
quiet: { monitor: collectMonitorQuiet() }
|
||||
}) });
|
||||
toast(r.msg || '已保存');
|
||||
} catch (e) { toast('保存失败:' + e.message); }
|
||||
}
|
||||
@@ -76,7 +101,7 @@ async function saveTracking() {
|
||||
tracking_interval: $('#trackingInterval').value,
|
||||
tracking_impact_threshold: $('#trackingImpact').value,
|
||||
tracking_notify: $('#trackingNotify').checked
|
||||
} }) });
|
||||
}, quiet: { tracking: collectTrackingQuiet() } }) });
|
||||
toast(r.msg || '已保存');
|
||||
} catch (e) { toast('保存失败:' + e.message); }
|
||||
}
|
||||
@@ -206,7 +231,7 @@ async function loadLog() {
|
||||
<td><span class="cat-tag ${n.category}">${n.category}</span></td>
|
||||
<td class="num ${pctClass(n.sentiment)}">${n.sentiment > 0 ? '利好' : n.sentiment < 0 ? '利空' : '中性'} (${Number(n.sentiment).toFixed(2)})</td>
|
||||
<td class="num"><b>${n.importance}</b></td>
|
||||
<td><span class="tag ${n.status === 'sent' ? 'tag-推荐' : n.status === 'skipped' ? 'tag-关注' : 'tag-负'}">${n.status}</span></td>
|
||||
<td><span class="tag ${n.status === 'sent' ? 'tag-推荐' : n.status === 'skipped' ? 'tag-关注' : n.status === 'quiet' ? 'tag-关注' : 'tag-负'}">${n.status === 'sent' ? '已发送' : n.status === 'skipped' ? '已跳过' : n.status === 'quiet' ? '🌙 静默' : '失败'}</span></td>
|
||||
</tr>`).join('');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,17 @@
|
||||
自动触发已配置系统 cron:<code>0 9 * * 1-5</code> 盘前 · <code>30 15 * * 1-5</code> 盘后。<br>
|
||||
每期发送两封形态:<b>简单版</b>(邮件正文速览)+ <b>详细版</b>(HTML 附件)。发送记录见下方表格。
|
||||
</div>
|
||||
<div class="mt16" style="border-top:1px solid var(--border);padding-top:10px">
|
||||
<div class="between mt16">
|
||||
<div class="card-title" style="font-size:14px;margin-bottom:0"><span class="bar" style="background:#f472b6"></span>🌙 静默期 —— 某些时段不发送报告邮件</div>
|
||||
<label class="switch"><input type="checkbox" id="reportQuietEnabled"><span></span></label>
|
||||
</div>
|
||||
<div class="mt8 settings-form">
|
||||
<div class="sf-row" style="align-items:flex-start"><label>静默时段</label><input class="input" id="reportQuietRanges" placeholder="如 09:30-10:00,11:30-13:00(多段逗号分隔,支持跨午夜)" style="width:100%"></div>
|
||||
</div>
|
||||
<div class="mt8"><button class="btn btn-primary" onclick="saveReportQuiet()">💾 保存报告静默期</button></div>
|
||||
<div style="color:var(--muted);font-size:12px;line-height:1.8">开启后,落在静默时段内的定时/手动报告将<span style="color:var(--gold)">不生成、不发送</span>(发送记录标记为“静默”)。</div>
|
||||
</div>
|
||||
<div class="card-title mt16" style="font-size:14px"><span class="bar" style="background:#8b5cf6"></span>发送记录</div>
|
||||
<div style="overflow:auto;max-height:300px">
|
||||
<table>
|
||||
|
||||
@@ -29,6 +29,16 @@
|
||||
</div>
|
||||
<div class="sf-row" style="align-items:flex-start"><label>关键词(逗号分隔)</label><textarea class="input" id="monitorKeywords" rows="2" style="width:100%"></textarea></div>
|
||||
</div>
|
||||
<div class="mt16" style="border-top:1px solid var(--border);padding-top:6px">
|
||||
<div class="between mt16">
|
||||
<div class="card-title" style="font-size:14px;margin-bottom:0"><span class="bar" style="background:#f472b6"></span>🌙 静默期 —— 某些时段不发送邮件通知</div>
|
||||
<label class="switch"><input type="checkbox" id="monitorQuietEnabled"><span></span></label>
|
||||
</div>
|
||||
<div class="mt8 settings-form">
|
||||
<div class="sf-row" style="align-items:flex-start"><label>静默时段</label><input class="input" id="monitorQuietRanges" placeholder="如 22:00-07:30(跨午夜)或 23:00-07:30,12:00-13:00(多段逗号分隔)" style="width:100%"></div>
|
||||
</div>
|
||||
<div style="color:var(--muted);font-size:12px;line-height:1.8">开启后,落在静默时段内的扫描将<span style="color:var(--gold)">不发送邮件</span>(通知日志标记为“静默”,水位正常推进,不会在结束后补发积压)。</div>
|
||||
</div>
|
||||
<div class="flex mt16 wrap">
|
||||
<button class="btn btn-primary" onclick="saveMonitor()">💾 保存监控设置</button>
|
||||
<button class="btn" onclick="scanNow()">⚡ 立即扫描</button>
|
||||
@@ -58,6 +68,16 @@
|
||||
<label class="switch"><input type="checkbox" id="trackingNotify"><span></span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt16" style="border-top:1px solid var(--border);padding-top:6px">
|
||||
<div class="between mt16">
|
||||
<div class="card-title" style="font-size:14px;margin-bottom:0"><span class="bar" style="background:#f472b6"></span>🌙 静默期 —— 某些时段不发送邮件通知</div>
|
||||
<label class="switch"><input type="checkbox" id="trackingQuietEnabled"><span></span></label>
|
||||
</div>
|
||||
<div class="mt8 settings-form">
|
||||
<div class="sf-row" style="align-items:flex-start"><label>静默时段</label><input class="input" id="trackingQuietRanges" placeholder="如 22:00-07:30(跨午夜)或 23:00-07:30,12:00-13:00(多段逗号分隔)" style="width:100%"></div>
|
||||
</div>
|
||||
<div style="color:var(--muted);font-size:12px;line-height:1.8">开启后,落在静默时段内发现的重大变化将<span style="color:var(--gold)">不发送邮件</span>(跟踪报告照常生成入库)。</div>
|
||||
</div>
|
||||
<div class="flex mt16 wrap">
|
||||
<button class="btn btn-primary" onclick="saveTracking()">💾 保存跟踪设置</button>
|
||||
<button class="btn" onclick="runTrackAll()">⚡ 立即跟踪全部目标</button>
|
||||
|
||||
Reference in New Issue
Block a user