diff --git a/docs/user-stories.md b/docs/user-stories.md index 10cc1ff..9935630 100644 --- a/docs/user-stories.md +++ b/docs/user-stories.md @@ -10,7 +10,7 @@ > > **v1.16(2026-08-17)· B 交互(UE)优化(rant `2026-08-17T15:50:05`).** **toast 分级**(成功 success / 错误 error / 信息 info,39 个调用点全部分级着色);**按钮 loading 态**(充值 / 加额 / 部门确认 / 上架提交 → 转圈 + 禁用,模拟反馈后恢复);**API Key 复制反馈**(复制后按钮短暂变「已复制 ✓」,1.2s 恢复,降级路径「请 Ctrl+C」);**键盘可达**(充值金额 / 加额金额·原因 / 部门名称·配额 Enter 提交;topup/raise/dept 行内卡片 Esc 关闭;打开时自动聚焦)。**rant 15:50:05 全部验收项满足,UI 美化与 UE 优化波收官(PR #38/#39/#40,docs v1.14→v1.16)。** > **v1.17(2026-08-17)· 多角度 UI/UE 深化(rant `2026-08-17T16:57:17`,分批 PR).** **A. 清除原生弹窗 ✓(PR #41)**:约 5 处原生确认/输入弹窗(key 删除、共享下架、部门删除、API Key 新建/改名、运营者/成员充值)全部改为**行内二次确认**(按钮变「确认删除?」红色态,3 秒无操作或 Esc 还原,再次点击执行)与**行内编辑**(行内展开输入框,Enter 确认 / Esc 取消);抽象可复用 `confirmInline` / `inlineForm` 组件全站统一;`grep ui/` 无原生弹窗残留。**B. 相对时间显示 ✓(PR #42)**:新增 `timeAgo` 格式化(`刚刚 / N 分钟前 / N 小时前 / 昨天 / MM-DD`,hover `title` 显示完整绝对时间),交易列表、加额申请列表、共享列表(新增「上架时间」列)与 API Key 最近使用时间统一使用。**C. 数字列对齐 ✓(PR #43)**:价格 / 点数 / 已用 / 额度 / 余额等**数字列统一右对齐 + 等宽字体**(`--mono` + `tabular-nums`,新增 `.table .num` 类,覆盖市场输入/输出价与上下文、交易 Token 用量/点数、共享已用/额度·单价·收益、成员配额/剩余、部门成员数·月分配·已用·剩余、运营者余额、加额申请点数),时间列 / 状态列保持左对齐;金额统一 `D.fmt`(整数千分位、小数 2 位)全覆盖检查无遗漏。其余 D–G(`:focus-visible` 与快捷键、行内校验错误、视图过渡、细节一致性)分批落地。**D. 键盘可达性 / 无障碍 ✓(PR #44)**:全局 `:focus-visible` 焦点环(accent 色 outline,Tab 导航清晰可见;输入类控件沿用边框高亮不叠加);**全局快捷键**:`/` 聚焦市场搜索框(输入框内不触发)、数字 **1-7** 切换侧边栏视图(仪表盘/市场/共享/钱包/交易/管理/设置,带 title 与右侧键位角标提示,Cmd/Ctrl+数字 不劫持)、Esc 关闭行内新建 Key;侧边栏导航按钮补 `title`(快捷键提示)。**E. 表单行内校验错误 ✓(PR #45)**:新增 `setFieldError(input, msg)` / `clearFieldError(input)`(输入框**红边框** + 字段下方**红字小号行内文案**,输入修正自动清除,打开表单时重置);覆盖**充值金额**(自定义金额非法)、**申请加额**(点数/原因)、**部门表单**(名称/配额/重名)、**共享上架表单**(API Key/厂商·Plan·模型·额度),提交失败不再仅依赖 toast,聚焦首个错误字段。**F. 视图切换过渡 ✓(PR #46)**:页面切换加轻量动画(`.view:not(.hidden)` **fade + 轻微上移 6px,150ms** `viewIn`,替代生硬切换);数据表格更新无闪烁(`.table tbody` **fade-in** `tbodyIn`,新增 `pulseTbody` 在静态 tbody 重渲染后重启动画,动态表格整表重建自动生效)。**G. 细节一致性 ✓(PR #47)**:一致性审计确认——**所有数字/金额统一 `D.fmt`(千分位/2 位小数)**(侧边栏余额、统计卡、表格、钱包点数格式一致)、已用/额度展示统一;表格操作按钮统一小号样式(`padding:4px 10px;font-size:12px`);**行内编辑校验统一走行内错误**(inlineForm 校验失败由 toast 改为 `setFieldError` 红边框+行内文案,API Key 改名/运营者充值/成员充值 3 处对齐 item E);子文本字号统一(11px→12px);上架 toast 单价措辞统一「点/1M(自动)」;移除 3 处「(未生效)」残留文案。**rant 16:57:17 全部验收项满足(A–G 七项,PR #41–#47),UI/UE 深化波收官。** -> **v1.19(2026-08-17)· UI/UE 第四轮多角度深化(rant `2026-08-17T20:39:30`,分批 PR,进行中).** **A. URL hash 路由 ✓(待合入)**:视图与地址栏 hash 联动——`#/marketplace`、`#/wallet` 等视图 hash 可**直接收藏 / 分享 / 刷新恢复**;规则:① 视图切换(导航 / 快捷键 1-7 / 登录恢复)同步 `history.pushState` 更新 hash(已相同不重复入栈,不触发 hashchange 回环);② **前进 / 后退**(hashchange)视图跟随切换;③ **非法 hash** 回退仪表盘视图但**不重写 URL**(避免 pushState 污染历史、后退需两次);④ 空 hash 不动作(无 hash 时保持默认行为);⑤ 刷新后登录自动恢复 hash 对应视图(游客浏览 / 未登录态无冲突);⑥ 游客受限视图 hash 被拦截时保持原视图、不入栈。其余 B–G(交易记录汇总条等)分批落地。 +> **v1.19(2026-08-17)· UI/UE 第四轮多角度深化(rant `2026-08-17T20:39:30`,分批 PR,进行中).** **A. URL hash 路由 ✓(PR #55)**:视图与地址栏 hash 联动——`#/marketplace`、`#/wallet` 等视图 hash 可**直接收藏 / 分享 / 刷新恢复**;规则:① 视图切换(导航 / 快捷键 1-7 / 登录恢复)同步 `history.pushState` 更新 hash(已相同不重复入栈,不触发 hashchange 回环);② **前进 / 后退**(hashchange)视图跟随切换;③ **非法 hash** 回退仪表盘视图但**不重写 URL**(避免 pushState 污染历史、后退需两次);④ 空 hash 不动作(无 hash 时保持默认行为);⑤ 刷新后登录自动恢复 hash 对应视图(游客浏览 / 未登录态无冲突);⑥ 游客受限视图 hash 被拦截时保持原视图、不入栈。**B. 交易记录汇总条 ✓(待合入)**:交易页卡片顶部加**紧凑汇总条**(`.tx-summary`,三列 inline:总收入 / 总支出 / 净变化,+绿 −红,零值中性显示 `0`);**随 tab 过滤联动**——「全部 / 消费 / 收益」tab 切换即时重算,且**与表格列筛选一致**(抽公共 `filterRows(rows, columns, filters)` 供 `buildDataTable` 与汇总共用,汇总反映与表格可见行相同的过滤集,不受分页影响);窄屏自动换行收窄。其余 C–G(select 美化 / toast 队列 / ? 帮助 / 行展开 / 登录页)分批落地。 > **v1.18(2026-08-17)· UI/UE 第三轮多角度深化(rant `2026-08-17T18:06:09`,分批 PR).** **A. 仪表盘数据可视化 ✓(PR #48)**:纯 SVG sparkline(零外部依赖,手写 path,颜色用现有 CSS 变量):「本月点数变化」卡片按天聚合近 7 日净变化画迷你折线图(渐变填充,每点 `` hover 显示当天数值);「我的共享」卡显示共享收益累计趋势 sparkline(`--ok` 色),无上架 key 时保留空状态不画图。**B. 亮色主题 ✓(PR #49)**:CSS 变量重构——新增 `:root[data-theme="light"]` 全套变量重定义(--bg/--bg-card/--border/--text 等),并把原先硬编码颜色(表格行边框/斑马纹、卡片 hover 阴影、确认态、spinner、遮罩)抽为语义变量(--table-row-border/--table-stripe/--card-hover-border/--card-shadow/--danger-soft/--danger-text/--spin-track/--overlay)双主题共用;侧边栏加**日/月 SVG 切换按钮**(按当前主题显隐图标),**localStorage 记忆**(atp-theme),首次加载尊重 **`prefers-color-scheme`**;两主题对比度均达标(正文/次要文字/边框层次清晰)。**C. 移动端表格卡片化 ✓(PR #50)**:`@media (max-width: 560px)` 下关键表格(市场/共享/交易/管理/设置 API Key)**隐藏表头**,每行渲染为**卡片**(`label: value` 布局,`td::before` 取 `data-label`),不再横向滚动;卡片内主字段加粗、状态徽章保留、操作按钮整行宽(`flex: 1 1 45%` 换行);全部表格 td 补齐 `data-label`(含 buildDataTable 动态列用 col.title)。**D. 搜索增强 ✓(PR #51)**:全部搜索框(市场 `#mk-search` / API Key `#ak-search` / 部门 `#od-search` / 运营者用户 `#ops-search`)统一接入 **~150ms 输入防抖**(避免每次按键整表重绘闪烁,连续输入只渲染一次);渲染结果对匹配关键词**大小写不敏感 `<mark>` 高亮**(先 HTML 转义再包裹,`--accent-soft` 底 + `--accent-text` 字,双主题对比度达标;清空后自动清除);搜索框加**「清空 ×」小按钮**(有内容时显示,点击清空立即重绘并聚焦输入框;空状态「清除搜索/清除筛选」按钮同样同步 × 态)。**E. 动效与系统偏好 ✓(PR #52)**:按钮 **`:active` 轻微下压**(`scale(0.98)`,disabled 不触发);统计卡 **hover 微抬升**(`translateY(-2px)` + 边框/阴影提升,与卡片 hover 语言一致);**数字/点数变化轻微跳动动画**(新增 `bump(el)` 助手——remove→reflow→add `.bump` 类重放 `numJump`(`translateY(-3px) scale(1.02)`,0.35s);接入 4 处余额变化点:钱包充值(侧边栏+钱包余额)、聊天消费扣款、加额批准、运营者给自己充值);**尊重 `prefers-reduced-motion: reduce`**——全局 `animation-duration/transition-duration` 压到 0.01ms,禁用过渡/动画但保留全部功能。**F. 动态文档标题 ✓(PR #53)**:`document.title` **跟随视图切换**(`switchView` 内统一设置「视图标题 · AITokenPool」,如「模型市场 Marketplace · AITokenPool」;`VIEW_TITLE` 7 个视图全覆盖;未知视图回退「AITokenPool」);**默认「AITokenPool」**——首次加载(DOMContentLoaded)与登录页/无视图态回默认标题,HTML `<title>` 同步改为「AITokenPool」;游客受限视图被拦截时标题保持不变。**G. 其他细节 ✓(验证确认,PR #54 收尾)**:① **视图切换滚动复位**——`switchView` 内 `$("#main").scrollTop = 0`(`.main` 即 `overflow-y:auto` 滚动容器,渲染后复位,多次切换均生效,DOM 冒烟验证);② **复制按钮「已复制 ✓」态**——`copyKey` 的 flash 反馈(复制成功按钮短暂变「已复制 ✓」并禁用,1.2s 恢复;降级路径「请 Ctrl+C」;v1.16 已实现,DOM 冒烟验证)。**rant 18:06:09 全部验收项满足(A–G 七项,PR #48–#54),UI/UE 第三轮深化波收官,docs v1.18。** > **v1.12(2026-08-17)· 移除「组织设置 Organization settings」表单.** 组织管理页只保留部门列表(部门 CRUD + 每月点数分配),删除底部「组织设置」表单(组织名称 / 默认成员配额 / 开关)。「关闭外部注册」属**部署配置项,不在 UI 中设置**(US-16 标注);「成员自助申请加额需管理员审批」开关移除后,申请加额流程**默认需审批**(US-20 说明)。 > **v1.11(2026-08-17)· 管理视图移除独立「Key 池」管理,统一走「上架 key」.** 管理员与普通用户一样通过**共享管理页「上架新 key」**(选厂商 → Plan → 模型、填 key、声明额度、可用时间段)配置上游 key——企业场景下管理员上架、员工消费,本质上就是 key 池;不再有独立的管理员 Key 池界面。移除 Admin「Key 池」tab(成员管理 / 用量报表 / 组织管理 / 平台运营保留)。「Key 池」保留为**数据层概念**(平台持有的上游 key 集合,见 docs/architecture.md 4.2)。更新 US-12 / 企业流程 / 对齐表。 diff --git a/ui/README.md b/ui/README.md index ef809bb..62ed75c 100644 --- a/ui/README.md +++ b/ui/README.md @@ -175,6 +175,12 @@ ui/ - **刷新恢复**:DOMContentLoaded 先 `pendingHashView = viewFromHash()`;登录成功恢复 `pendingHashView || "dashboard"`(游客浏览 / 未登录态不冲突); - **游客拦截**:游客访问受限 hash(如 `#/settings`)→ `switchView` 内 `GUEST_VIEWS` 拦截(toast 提示),视图与 URL 均保持原状、不入栈新条目。 +## 交易汇总条约定(v1.19,rant 2026-08-17T20:39:30 B) + +- 交易记录页卡片顶部(表格上方)有**紧凑汇总条** `#tx-summary`(`.tx-summary`,三列 inline,不引入新布局):**总收入 / 总支出 / 净变化**,正数 `+` 绿(`var(--ok)`)、负数 `-` 红(`var(--danger-text)`)、零值中性显示 `0`; +- **随 tab 过滤联动**:切换「全部 / 消费 / 收益」tab 即时重算;且与表格**列筛选**一致——汇总基于 `filterRows(list, TX_COLUMNS, txTable.filters)`(`buildDataTable` 的筛选逻辑抽出的公共函数),反映与表格可见行相同的过滤集,**不受分页影响**; +- 窄屏(≤560px)自动收窄间距 / 字号,`flex-wrap` 换行兜底。 + ## 数据说明 - 点数规则与机制细节见 `docs/user-stories.md`(v1.8:机制说明不再进入面向用户的界面文案);UI 只呈现结果(余额数字、模型价格点数、交易金额/类型/状态、可用/繁忙) diff --git a/ui/css/style.css b/ui/css/style.css index b23a97a..61c9b34 100644 --- a/ui/css/style.css +++ b/ui/css/style.css @@ -249,6 +249,14 @@ select.input option { background: var(--bg-card); } .stat .sub { color: var(--text-dim); font-size: 12px; } .stat.accent .value { color: var(--accent-text); } +/* 交易汇总条(rant 20:39:30 B:紧凑 inline 条,随 tab/筛选联动) */ +.tx-summary { display: flex; flex-wrap: wrap; gap: 28px; padding: 12px 16px 10px; border-bottom: 1px solid var(--border); } +.tx-summary .ts-item { display: flex; flex-direction: column; gap: 2px; } +.tx-summary .ts-label { color: var(--text-mute); font-size: 12px; } +.tx-summary .ts-value { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; } +.tx-summary .ts-value.ok { color: var(--ok); } +.tx-summary .ts-value.danger { color: var(--danger-text); } + .table-wrap { overflow-x: auto; } .table-wrap.compact { margin-top: 4px; } .table { width: 100%; border-collapse: collapse; font-size: 13px; } @@ -681,6 +689,9 @@ mark { .stat-grid { grid-template-columns: 1fr 1fr; } .card-grid { grid-template-columns: 1fr; } .api-key-card { margin-top: 12px; } + /* 交易汇总条窄屏(rant 20:39:30 B) */ + .tx-summary { gap: 18px; padding: 10px 12px 8px; } + .tx-summary .ts-value { font-size: 16px; } /* 移动端表格卡片化(rant 18:06:09 C:隐藏表头,行 → label:value 卡片,不再横滚) */ .table { display: block; } .table thead { display: none; } diff --git a/ui/index.html b/ui/index.html index a2c8a4c..37e9b99 100644 --- a/ui/index.html +++ b/ui/index.html @@ -277,6 +277,7 @@ <h2>交易记录 <span class="en">Transactions</span></h2> <button class="tab" data-tx-tab="earn">收益</button> </div> <div class="card"> + <div class="tx-summary" id="tx-summary"></div> <div class="table-wrap compact" id="tx-table"></div> </div> </section> diff --git a/ui/js/app.js b/ui/js/app.js index db58080..dc07071 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -822,11 +822,26 @@ render: (t) => t.status === "处理中" ? '<span class="badge warn">' + esc(t.status) + "</span>" : esc(t.status) }, ]; + // 交易汇总条(rant 20:39:30 B:随 tab + 列筛选联动 — 总收入/总支出/净变化,+绿 −红,紧凑 inline 条) + function renderTxSummary(list) { + let income = 0, expense = 0; + list.forEach((t) => { if (t.pts > 0) income += t.pts; else expense += -t.pts; }); + const net = income - expense; + const cls = (n) => (n > 0 ? "ok" : n < 0 ? "danger" : ""); + const fmt = (n) => (n > 0 ? "+" : n < 0 ? "-" : "") + D.fmt(Math.abs(n)); + $("#tx-summary").innerHTML = + '<div class="ts-item"><span class="ts-label">总收入</span><span class="ts-value num ' + cls(income) + '">' + fmt(income) + "</span></div>" + + '<div class="ts-item"><span class="ts-label">总支出</span><span class="ts-value num ' + cls(-expense) + '">' + fmt(-expense) + "</span></div>" + + '<div class="ts-item"><span class="ts-label">净变化</span><span class="ts-value num ' + cls(net) + '">' + fmt(net) + "</span></div>"; + } + function renderTransactions() { $$("#tx-tabs .tab").forEach((b) => b.classList.toggle("active", b.dataset.txTab === txTab)); let list = D.TRANSACTIONS; if (txTab === "consume") list = list.filter((t) => t.type === "consume"); else if (txTab === "earn") list = list.filter((t) => t.type === "earn"); + // 交易汇总条:与 tab + 列筛选联动,与表格可见行一致(rant 20:39:30 B) + renderTxSummary(filterRows(list, TX_COLUMNS, txTable.filters)); buildDataTable({ container: $("#tx-table"), columns: TX_COLUMNS, @@ -840,13 +855,12 @@ cfg: { container, columns, rows, state, onState } columns: [{ key, title, sort?: "string"|"number", filter?: "text"|"select"|"number-range", options?, render? }] state: { sort: [{key,dir}], filters: {key:val}, page, pageSize }(原地更新,跨页保留) */ - function buildDataTable(cfg) { - const { container, columns, rows, state, onState } = cfg; - // 1) 筛选 - let data = rows.filter((row) => { - for (const key of Object.keys(state.filters)) { - const fv = state.filters[key]; + // 按列筛选条件过滤行(buildDataTable 与交易汇总条共用,保证汇总与表格可见行一致) + function filterRows(rows, columns, filters) { + return rows.filter((row) => { + for (const key of Object.keys(filters)) { + const fv = filters[key]; if (fv == null || fv === "") continue; const col = columns.find((c) => c.key === key); if (!col || !col.filter) continue; @@ -864,6 +878,13 @@ } return true; }); + } + + function buildDataTable(cfg) { + const { container, columns, rows, state, onState } = cfg; + + // 1) 筛选 + let data = filterRows(rows, columns, state.filters); // 2) 排序(多列:Shift 点击叠加) if (state.sort.length) {