diff --git a/ui/css/style.css b/ui/css/style.css index 8407912..b578a60 100644 --- a/ui/css/style.css +++ b/ui/css/style.css @@ -344,29 +344,24 @@ select.input option { background: var(--bg-card); } .tsw-btn.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-text); } .tx-trend-chart { position: relative; } .tx-trend svg { width: 100%; height: auto; display: block; } -.tx-trend-grid { stroke: var(--border); stroke-width: 1; stroke-dasharray: 3 4; } -.tx-trend-axis { fill: var(--text-mute); font-size: 10px; } -/* 平滑折线 + 渐变面积(指标色随切换,rant 2026-08-23T16:17:18 需求 1) */ -.tx-trend svg .trend-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } +.tx-trend-axis { fill: var(--text-mute); font-size: 9px; } +/* 平滑折线 + 渐变面积(sparkline 化,rant 2026-08-24T10:51:57.821209: + 无网格线、无数据点圆、渐变 0.35→0、teal 主题色系——消费用主强调色区分) */ +.tx-trend svg .trend-line { fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } .tx-trend svg .trend-area { stroke: none; } -.tx-trend svg .trend-dot { fill: var(--bg-card); stroke-width: 1.5; } .tx-trend svg .tg-0 { stop-opacity: 0; } .tx-trend svg .tg-1 { stop-opacity: 0; } -.tx-trend svg.m-exp .trend-line { stroke: var(--danger-text); } -.tx-trend svg.m-exp .trend-dot { stroke: var(--danger-text); } -.tx-trend svg.m-exp .tg-0 { stop-color: var(--danger-text); stop-opacity: 0.22; } -.tx-trend svg.m-exp .tg-1 { stop-color: var(--danger-text); } +.tx-trend svg.m-exp .trend-line { stroke: var(--accent); } +.tx-trend svg.m-exp .tg-0 { stop-color: var(--accent); stop-opacity: 0.35; } +.tx-trend svg.m-exp .tg-1 { stop-color: var(--accent); } .tx-trend svg.m-inc .trend-line { stroke: var(--ok); } -.tx-trend svg.m-inc .trend-dot { stroke: var(--ok); } -.tx-trend svg.m-inc .tg-0 { stop-color: var(--ok); stop-opacity: 0.22; } +.tx-trend svg.m-inc .tg-0 { stop-color: var(--ok); stop-opacity: 0.35; } .tx-trend svg.m-inc .tg-1 { stop-color: var(--ok); } .tx-trend svg.m-net .trend-line { stroke: var(--accent-text); } -.tx-trend svg.m-net .trend-dot { stroke: var(--accent-text); } -.tx-trend svg.m-net .tg-0 { stop-color: var(--accent-text); stop-opacity: 0.22; } +.tx-trend svg.m-net .tg-0 { stop-color: var(--accent-text); stop-opacity: 0.35; } .tx-trend svg.m-net .tg-1 { stop-color: var(--accent-text); } .tx-trend svg.m-tok .trend-line { stroke: var(--warn); } -.tx-trend svg.m-tok .trend-dot { stroke: var(--warn); } -.tx-trend svg.m-tok .tg-0 { stop-color: var(--warn); stop-opacity: 0.22; } +.tx-trend svg.m-tok .tg-0 { stop-color: var(--warn); stop-opacity: 0.35; } .tx-trend svg.m-tok .tg-1 { stop-color: var(--warn); } /* 悬停引导线 + tooltip */ .tx-trend-guide { position: absolute; top: 2px; bottom: 6px; width: 1px; background: var(--text-dim); opacity: 0.45; display: none; pointer-events: none; } @@ -377,9 +372,9 @@ select.input option { background: var(--bg-card); } box-shadow: var(--shadow); z-index: 5; white-space: nowrap; line-height: 1.5; } .tx-trend-tip b { color: var(--accent-text); font-weight: 600; } -.tx-trend-legend { display: flex; gap: 18px; font-size: 12px; color: var(--text-mute); padding-top: 4px; align-items: center; } -.tx-trend-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: middle; } -.tx-trend-dot.exp { background: var(--danger-text); } +.tx-trend-legend { display: flex; gap: 12px; font-size: 11px; color: var(--text-mute); padding-top: 4px; align-items: center; } +.tx-trend-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 4px; vertical-align: middle; } +.tx-trend-dot.exp { background: var(--accent); } .tx-trend-dot.inc { background: var(--ok); } .tx-trend-dot.net { background: var(--accent-text); } .tx-trend-dot.tok { background: var(--warn); } diff --git a/ui/index.html b/ui/index.html index 1f812cb..6902a9e 100644 --- a/ui/index.html +++ b/ui/index.html @@ -5,7 +5,7 @@ AITokenPool - + @@ -683,9 +683,9 @@

使用模型

- - - - + + + + diff --git a/ui/js/app.js b/ui/js/app.js index a3a0cb7..60a6bd3 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -1241,9 +1241,10 @@ $("#tx-summary").innerHTML = html; } - // 交易趋势图(rant 2026-08-23T16:01:07 需求 2 + 2026-08-23T16:17:18 优化):手写 SVG(无外部依赖), - // 数据源 = /api/transactions/trend(跟随 tab + 外部时间段筛选,与 summary 同口径); - // 指标可切换(消费/收入/净变化/Token,默认消费点数);平滑折线 + 渐变面积 + 自适应刻度 + 悬停 tooltip。 + // 交易趋势图(rant 2026-08-23T16:01:07 需求 2 + 16:17:18 优化 + 2026-08-24T10:51:57 重做): + // 参考仪表盘 sparkline():渐变面积(0.35→0)、连续平滑曲线(round 连接)、无网格极简坐标、 + // teal 主题配色、紧凑高度(viewBox 85,原 170 减半,rant 10:51:57.821928); + // 保留指标切换(消费/收入/净变化/Token,默认消费)与悬停 tooltip。 let _trendCtx = null; // 悬停 tooltip 上下文(renderTxTrend 写入,事件委托读取) function renderTxTrend() { const el = $("#tx-trend"); @@ -1279,7 +1280,8 @@ const nm = Math.max(absMax, 1e-9) / np; const maxV = Math.max((nm <= 1 ? 1 : nm <= 2 ? 2 : nm <= 5 ? 5 : 10) * np, 4); const minV = neg ? -maxV : 0; - const W = 640, H = 190, PL = 46, PR = 14, PT = 14, PB = 26; + // 紧凑几何(rant 10:51:57.821928:高 170 → 85 减半,无网格预留边距) + const W = 640, H = 85, PL = 32, PR = 8, PT = 6, PB = 14; const iw = W - PL - PR, ih = H - PT - PB; const X = (i) => PL + (buckets.length === 1 ? iw / 2 : iw * i / (buckets.length - 1)); const Y = (v) => PT + ih * (maxV - v) / (maxV - minV || 1); @@ -1297,33 +1299,22 @@ } return d; }; - // 网格 + 纵轴刻度(4 等分,5 条线,负值指标含负刻度) - let grid = ""; - for (let g = 0; g <= 4; g++) { - const gy = PT + ih * g / 4; - const gv = minV + (maxV - minV) * (4 - g) / 4; - grid += '' + - '' + fmtAxis(gv) + ""; - } - // 横轴标签:首 / 中 / 尾 + // 极简坐标(rant 10:51:57.821209:去生硬网格):仅顶/底 2 个纵轴刻度标签,无网格线 + let axis = + '' + fmtAxis(maxV) + "" + + '' + fmtAxis(minV) + ""; + // 横轴标签:首 / 中 / 尾(紧凑小字号) const idxs = [0, Math.floor((buckets.length - 1) / 2), buckets.length - 1].filter((v, i, a) => a.indexOf(v) === i); let xlabels = ""; idxs.forEach((i) => { - xlabels += '' + esc(lbl(buckets[i])) + ""; + xlabels += '' + esc(lbl(buckets[i])) + ""; }); - // 折线 + 渐变面积 + 数据点(面积基线:负值指标取零轴,正值指标取图表底) + // 渐变面积(基线:负值指标取零轴,正值指标取图表底)+ 平滑曲线; + // 不画数据点圆(rant 10:51:57.821209:空白圆点造成折线断点/锯齿,sparkline 化优雅降级) const yBase = Y(neg ? 0 : minV); const areaPath = pts.length ? smoothPath(pts) + " L" + pts[pts.length - 1][0].toFixed(1) + "," + yBase.toFixed(1) + " L" + pts[0][0].toFixed(1) + "," + yBase.toFixed(1) + " Z" : ""; - const lineHtml = () => { - let dots = ""; - pts.forEach((p) => { - dots += ''; - }); - return '' + - '' + dots; - }; const switchHtml = '
' + METRICS.map((x) => @@ -1337,7 +1328,9 @@ '
' + '' + '' + - grid + xlabels + lineHtml() + + axis + xlabels + + '' + + '' + "" + '
' + '
' +