diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000..9cad8a5 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,60 @@ +# AITokenPool UI 原型(Static HTML Prototype) + +纯静态 HTML + CSS + JS 原型,无框架、无构建工具、无外部 CDN 依赖。 +浏览器直接打开即可浏览与演示。 + +## 产品模型(重要) + +**一套产品,两种部署场景,角色区分**: + +- **公共版**:部署在公网,任何人都能注册、分享 key、赚点数、消费别人共享的 key; +- **企业版**:部署在企业内网,管理员(IT)把采购的 key 放进池子、给成员分配点数; +- 两者的**功能集合完全相同**(市场 / 共享 / 钱包 / 交易 / 设置),不存在"企业版专属功能"; +- 差异只在**谁会主动分享**(公共版人人可分享;企业版只有管理员有动力采购/分享 key); +- **角色是权限差异,不是产品差异**:管理员额外拥有"管理视图"(key 池 / 成员点数 / 用量报表 / 组织设置),普通用户没有;员工就是普通用户,用同一套界面。 + +本原型据此实现:单一登录入口(角色由账号决定),一套导航(仪表盘 / 模型市场 / 共享管理 / 钱包 / 交易记录 / 设置 + 管理员角色视图)。 + +## 如何浏览 + +**方式一(推荐)**:双击 `index.html`,用浏览器打开即可。 + +**方式二**:本地起一个静态服务器(可选): + +```bash +python3 -m http.server 8000 --directory ui +# 然后访问 http://localhost:8000 +``` + +## 页面清单 + +登录页:单一入口(邮箱登录占位),不做公共版/企业版二选一。 + +同一套界面(角色视图): + +1. 仪表盘 Dashboard — 点数余额、本月用量、共享收益、最近交易 +2. 模型市场 Marketplace — 模型浏览、搜索、厂商筛选、排序(按价格/上下文) +3. 共享管理 Sharing — 默认只显示统计 + 我的共享列表(key 脱敏展示,可暂停/恢复/重新上架/彻底删除);点击"+ 添加 / 上架新 key"展开上架表单(须填 API Key,平台加密托管;分享者只填声明额度,单价由平台按模型定价自动计算并展示参考价),提交成功或取消后自动收起 +4. 钱包 Wallet — 点数余额、充值/提现占位入口、收支明细(MRT 风格表格:列排序/列筛选/分页/每页行数切换) +5. 交易记录 Transactions — 消费/收益/充值/提现,Tab 筛选 + MRT 风格表格(列排序/列筛选/分页,与 Tab 叠加生效) +6. 设置 Settings — 账户、API Key 管理(可生成)、通知、偏好 +7. 管理视图 Admin(管理员角色专属)— Key 池管理(添加/撤销)、成员管理(配额调整)、用量报表(按模型/成员)、组织设置(含"关闭外部注册"企业部署开关) + +## 文件结构 + +``` +ui/ +├── index.html # 入口(登录页 + 应用外壳 + 全部视图) +├── css/style.css # 设计系统(深色主题 · 强调色 #4ecdc4 · 响应式预留) +├── js/data.js # 内嵌 mock 数据(模型价格、交易、共享、成员等) +├── js/app.js # 交互逻辑(导航、筛选、表单、分页、Toast) +└── README.md # 本文件 +``` + +## 数据说明 + +- 点数规则:1 USD = 1000 点;模型价格折算自 `data/models.example.json`(CNY 按 ~7.2 汇率示例折算) +- 上架单价不由分享者手填:单价是模型×厂商的客观属性,平台按模型价格表自动计算(参考单价 = 该模型输出价 点数/1M);模型无定价数据时给出"按默认价"兜底,不报错 +- 上架需提交 API Key(password 输入):平台加密托管、仅用于代理调用;共享列表只展示脱敏值(如 sk-****1234,前 3 后 4),不展示明文 +- 删除 = 彻底下架(key 从平台移除,不可恢复);暂停 = 临时不接单,可恢复 +- 全部数据为前端常量(mock),无后端、无真实调用 diff --git a/ui/css/style.css b/ui/css/style.css new file mode 100644 index 0000000..a114fa1 --- /dev/null +++ b/ui/css/style.css @@ -0,0 +1,399 @@ +/* ============================================================ + AITokenPool UI Prototype — 设计系统 + 深色主基调 · 克制配色 · 强调色 #4ecdc4(青) + ============================================================ */ + +:root { + --bg: #0e1116; + --bg-soft: #141922; + --bg-card: #1a2029; + --bg-hover: #222a36; + --border: #2a3342; + --text: #e6ebf2; + --text-dim: #9aa7b8; + --text-mute: #67748a; + --accent: #4ecdc4; + --accent-soft: rgba(78, 205, 196, 0.12); + --accent-text: #6fe0d8; + --ok: #3ecf8e; + --warn: #e5b45b; + --danger: #e5666a; + --radius: 12px; + --radius-sm: 8px; + --sidebar-w: 240px; + --shadow: 0 8px 24px rgba(0,0,0,.35); + --font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; + --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +html, body { height: 100%; } + +body { + font-family: var(--font); + background: var(--bg); + color: var(--text); + font-size: 14px; + line-height: 1.6; +} + +.hidden { display: none !important; } +.muted { color: var(--text-mute); } +.en { color: var(--text-mute); font-size: 12px; font-weight: 400; } + +/* ---------- 通用组件 ---------- */ + +.btn { + border: 1px solid var(--border); + background: var(--bg-card); + color: var(--text); + padding: 8px 16px; + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 14px; + transition: all .15s; +} +.btn:hover { background: var(--bg-hover); border-color: #3a4658; } +.btn-primary { + background: var(--accent); + border-color: var(--accent); + color: #062021; + font-weight: 600; +} +.btn-primary:hover { background: #63ded6; } +.btn-ghost { background: transparent; } +.btn-danger { + background: transparent; + border-color: transparent; + color: var(--danger); +} +.btn-danger:hover { background: rgba(229, 102, 106, 0.12); border-color: var(--danger); } +.btn-block { width: 100%; } + +.input, select.input { + width: 100%; + background: var(--bg-soft); + border: 1px solid var(--border); + color: var(--text); + border-radius: var(--radius-sm); + padding: 9px 12px; + font-size: 14px; + outline: none; + transition: border-color .15s; +} +.input:focus { border-color: var(--accent); } + +/* 自动参考单价(只读展示,由平台按模型定价计算) */ +.price-view { + display: flex; + align-items: center; + min-height: 38px; + padding: 9px 12px; + border: 1px dashed var(--border); + border-radius: var(--radius-sm); + background: var(--bg-soft); + color: var(--text-dim); + font-size: 13px; +} + +/* 表单辅助说明 */ +.form-hint { + color: var(--text-mute); + font-size: 12px; + line-height: 1.5; +} +select.input option { background: var(--bg-card); } + +.card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 20px; +} +.card h3 { font-size: 15px; margin-bottom: 14px; color: var(--text); } + +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 16px; + margin-top: 16px; +} +.settings-grid { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); } + +.stat-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 14px; + margin-bottom: 4px; +} +.stat { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px 18px; +} +.stat .label { color: var(--text-mute); font-size: 12px; } +.stat .value { font-size: 24px; font-weight: 700; margin-top: 4px; } +.stat .sub { color: var(--text-dim); font-size: 12px; } +.stat.accent .value { color: var(--accent-text); } + +.table-wrap { overflow-x: auto; } +.table-wrap.compact { margin-top: 4px; } +.table { width: 100%; border-collapse: collapse; font-size: 13px; } +.table th { + text-align: left; + color: var(--text-mute); + font-weight: 500; + font-size: 12px; + padding: 8px 12px; + border-bottom: 1px solid var(--border); + white-space: nowrap; +} +.table td { padding: 10px 12px; border-bottom: 1px solid rgba(42,51,66,.5); white-space: nowrap; } +.table tbody tr:hover { background: var(--bg-hover); } + +.badge { + display: inline-block; + padding: 2px 10px; + border-radius: 999px; + font-size: 12px; + border: 1px solid var(--border); + color: var(--text-dim); +} +.badge.ok { color: var(--ok); border-color: rgba(62,207,142,.4); background: rgba(62,207,142,.08); } +.badge.warn { color: var(--warn); border-color: rgba(229,180,91,.4); background: rgba(229,180,91,.08); } +.badge.danger { color: var(--danger); border-color: rgba(229,102,106,.4); background: rgba(229,102,106,.08); } +.badge.accent { color: var(--accent-text); border-color: rgba(78,205,196,.4); background: var(--accent-soft); } +.badge.dim { color: var(--text-mute); } + +.toolbar { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; } +.toolbar .input { max-width: 240px; } +.toolbar-count { align-self: center; color: var(--text-mute); font-size: 12px; margin-left: auto; } + +.tabs { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; } +.tab { + border: 1px solid var(--border); + background: transparent; + color: var(--text-dim); + padding: 7px 16px; + border-radius: 999px; + cursor: pointer; + font-size: 13px; +} +.tab:hover { color: var(--text); } +.tab.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-text); } + +.pager { display: flex; gap: 6px; align-items: center; justify-content: flex-end; margin-top: 12px; } +.pager button { + min-width: 30px; + height: 30px; + border: 1px solid var(--border); + background: var(--bg-card); + color: var(--text-dim); + border-radius: var(--radius-sm); + cursor: pointer; +} +.pager button.active { border-color: var(--accent); color: var(--accent-text); } +.pager span { color: var(--text-mute); font-size: 12px; } + +/* MRT 风格表格:表头排序 + 列筛选 + 每页行数 */ +.th-sort { + background: transparent; + border: none; + color: inherit; + font: inherit; + cursor: pointer; + padding: 0; + text-align: left; + white-space: nowrap; +} +.th-sort:hover { color: var(--accent-text); } +.th-filter { + width: 100%; + min-width: 72px; + background: var(--bg-soft); + border: 1px solid var(--border); + color: var(--text); + border-radius: var(--radius-sm); + padding: 5px 8px; + font-size: 12px; + outline: none; +} +.th-filter:focus { border-color: var(--accent); } +.range-filter { display: flex; gap: 4px; } +.range-filter .th-filter { min-width: 0; width: 100%; } +.pager-size { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 6px; + margin-top: 8px; + color: var(--text-mute); + font-size: 12px; +} +.pager-size select { + background: var(--bg-card); + border: 1px solid var(--border); + color: var(--text); + border-radius: var(--radius-sm); + padding: 3px 6px; + font-size: 12px; +} + +.form { display: flex; flex-direction: column; gap: 12px; } +.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } +.form label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--text-dim); } +.form label.check { flex-direction: row; align-items: center; gap: 8px; } +.form label.check input { accent-color: var(--accent); } + +.mini-list { display: flex; flex-direction: column; gap: 8px; } +.mini-item { + display: flex; justify-content: space-between; align-items: center; + gap: 10px; padding: 10px 12px; + background: var(--bg-soft); border: 1px solid var(--border); + border-radius: var(--radius-sm); +} +.mini-item .t { font-weight: 600; font-size: 13px; } +.mini-item .d { color: var(--text-mute); font-size: 12px; } +.mini-item .r { text-align: right; } +.mini-item .r .pts { color: var(--accent-text); font-weight: 700; } + +.bar-list { display: flex; flex-direction: column; gap: 12px; } +.bar-row .bar-label { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 4px; } +.bar-row .bar-label .n { color: var(--text-mute); } +.bar { + height: 8px; border-radius: 999px; + background: var(--bg-hover); overflow: hidden; +} +.bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--accent), #7ee8b8); border-radius: 999px; } + +/* ---------- 登录页 ---------- */ + +.login-view { + min-height: 100vh; + display: flex; align-items: center; justify-content: center; + padding: 24px; + background: + radial-gradient(600px 300px at 20% 0%, rgba(78,205,196,.08), transparent 60%), + radial-gradient(600px 400px at 90% 100%, rgba(78,205,196,.06), transparent 60%), + var(--bg); +} +.login-card { + width: 100%; max-width: 400px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 16px; + padding: 32px; + box-shadow: var(--shadow); +} +.login-brand { display: flex; align-items: center; gap: 14px; margin-bottom: 22px; } +.login-brand h1 { font-size: 20px; } +.login-brand p { color: var(--text-mute); font-size: 12px; } + +.logo { + width: 40px; height: 40px; border-radius: 10px; + background: linear-gradient(135deg, #4ecdc4, #2a9d8f); + color: #062021; font-weight: 800; + display: flex; align-items: center; justify-content: center; + font-size: 16px; flex-shrink: 0; +} + +.mode-switch { display: none; } + +.login-form { display: flex; flex-direction: column; gap: 12px; } +.login-form label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--text-dim); } +.login-divider { display: flex; align-items: center; gap: 12px; margin: 16px 0; color: var(--text-mute); font-size: 12px; } +.login-divider::before, .login-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); } +.login-foot { margin-top: 16px; text-align: center; font-size: 13px; color: var(--text-dim); } +.login-foot a { color: var(--accent-text); text-decoration: none; } +.login-tip { margin-top: 14px; text-align: center; color: var(--text-mute); font-size: 11px; } + +/* ---------- 应用布局 ---------- */ + +.app { display: flex; height: 100vh; overflow: hidden; } + +.sidebar { + width: var(--sidebar-w); + flex-shrink: 0; + background: var(--bg-soft); + border-right: 1px solid var(--border); + display: flex; flex-direction: column; + padding: 14px 12px; +} +.sidebar-top { display: flex; align-items: center; gap: 10px; padding: 2px 6px 14px; border-bottom: 1px solid var(--border); } +.brand-text { display: flex; flex-direction: column; line-height: 1.3; } +.brand-text strong { font-size: 14px; } +.brand-text span { color: var(--text-mute); font-size: 11px; } + +.nav { flex: 1; padding: 14px 0; display: flex; flex-direction: column; gap: 2px; overflow-y: auto; } +.nav-group { color: var(--text-mute); font-size: 11px; padding: 10px 8px 4px; } +.nav-item { + display: flex; align-items: center; gap: 10px; + padding: 9px 10px; border-radius: var(--radius-sm); + color: var(--text-dim); cursor: pointer; font-size: 13.5px; + border: none; background: transparent; text-align: left; width: 100%; +} +.nav-item .ico { width: 18px; text-align: center; font-size: 15px; } +.nav-tag { + margin-left: auto; + font-size: 10px; + padding: 1px 7px; + border-radius: 999px; + border: 1px solid rgba(78,205,196,.4); + color: var(--accent-text); + background: var(--accent-soft); +} +.nav-item:hover { background: var(--bg-hover); color: var(--text); } +.nav-item.active { background: var(--accent-soft); color: var(--accent-text); font-weight: 600; } + +.sidebar-bottom { border-top: 1px solid var(--border); padding-top: 12px; display: flex; flex-direction: column; gap: 8px; } +.user-chip { + display: flex; align-items: center; gap: 10px; + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius-sm); padding: 10px; +} +.user-chip .avatar { + width: 32px; height: 32px; border-radius: 50%; + background: linear-gradient(135deg, #4ecdc4, #2a9d8f); + color: #062021; font-weight: 700; + display: flex; align-items: center; justify-content: center; +} +.user-chip strong { font-size: 13px; display: block; } +.user-chip span { color: var(--text-mute); font-size: 11px; } +.user-chip b { color: var(--accent-text); } + +.main { flex: 1; overflow-y: auto; padding: 28px 32px 48px; } + +.page-head { margin-bottom: 18px; } +.page-head h2 { font-size: 22px; } +.page-head p { color: var(--text-mute); margin-top: 2px; } + +.wallet-hero { display: flex; justify-content: space-between; align-items: center; gap: 20px; flex-wrap: wrap; } +.balance-big { font-size: 42px; font-weight: 800; color: var(--accent-text); line-height: 1.2; } +.wallet-actions { display: flex; gap: 10px; } + +.toast { + position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%); + background: var(--bg-card); border: 1px solid var(--accent); + color: var(--text); padding: 12px 22px; border-radius: 999px; + box-shadow: var(--shadow); z-index: 999; font-size: 14px; + animation: toast-in .2s ease; +} +@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translate(-50%, 0); } } + +/* ---------- 响应式预留 ---------- */ + +@media (max-width: 900px) { + .sidebar { width: 64px; padding: 12px 8px; } + .sidebar .brand-text, .sidebar .nav-item .label, .sidebar .user-chip > div:last-child, + .sidebar #toggle-mode-btn, .sidebar #logout-btn { display: none; } + .nav-item { justify-content: center; } + .main { padding: 20px 16px 40px; } + .form-row { grid-template-columns: 1fr; } +} + +@media (max-width: 560px) { + .stat-grid { grid-template-columns: 1fr 1fr; } + .card-grid { grid-template-columns: 1fr; } +} diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..465331e --- /dev/null +++ b/ui/index.html @@ -0,0 +1,323 @@ + + + + + +AITokenPool — AI Token 共享平台 + + + + + +
+
+ + + + +
+
+ + + + + + + + + + diff --git a/ui/js/app.js b/ui/js/app.js new file mode 100644 index 0000000..fdbe9f7 --- /dev/null +++ b/ui/js/app.js @@ -0,0 +1,641 @@ +/* ============================================================ + AITokenPool UI Prototype — 交互逻辑(纯原生 JS) + 无框架 / 无构建 / 无外部依赖 + ============================================================ */ + +(function () { + "use strict"; + + const D = window.ATDATA; + const $ = (sel) => document.querySelector(sel); + const $$ = (sel) => Array.from(document.querySelectorAll(sel)); + const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); + + let activeView = "dashboard"; + let txTab = "all"; + + // MRT 风格表格状态(页面级变量:切换页面不丢失排序/筛选/分页) + const walletTable = { sort: [], filters: {}, page: 1, pageSize: 10 }; + const txTable = { sort: [], filters: {}, page: 1, pageSize: 10 }; + + /* ---------------- 工具 ---------------- */ + + function toast(msg) { + const el = $("#toast"); + el.textContent = msg; + el.classList.remove("hidden"); + clearTimeout(toast._t); + toast._t = setTimeout(() => el.classList.add("hidden"), 2400); + } + + function badge(status, labels) { + return '' + + esc(labels[status] ? labels[status].text : status) + ""; + } + + // 自动单价:单价是模型×厂商的客观属性,由平台按模型定价自动计算(输出 1M tokens 折算点数) + function autoPrice(model) { + const m = D.MODELS.find((x) => x.model === model); + if (m && typeof m.out === "number") return m.out; + // 兜底:取同厂商相近模型的输出价;仍无则用固定默认价 + const same = m ? D.MODELS.find((x) => x.provider === m.provider && typeof x.out === "number") : null; + return same ? same.out : 300; + } + + // key 脱敏展示:仅显示前 3 后 4(如 sk-****1234) + function maskKey(key) { + if (!key) return "—"; + if (key.length <= 8) return key.slice(0, 3) + "****" + key.slice(-4); + return key.slice(0, 3) + "****" + key.slice(-4); + } + + function showPriceHint(model) { + const el = $("#sf-price-view"); + if (!el) return; + const m = D.MODELS.find((x) => x.model === model); + if (!model) { el.textContent = "选择模型后自动计算"; return; } + if (m && typeof m.out === "number") { + el.textContent = D.fmt(m.out) + " 点 / 1M 输出(自动)"; + } else { + el.textContent = "按默认价:" + D.fmt(autoPrice(model)) + " 点 / 1M 输出(自动)"; + } + } + + /* ---------------- 导航 ---------------- */ + + const NAV = [ + { g: "主导航", items: [ + { id: "dashboard", ico: "📊", label: "仪表盘 Dashboard" }, + { id: "marketplace", ico: "🛒", label: "模型市场 Marketplace" }, + { id: "sharing", ico: "🔗", label: "共享管理 Sharing" }, + { id: "wallet", ico: "👛", label: "钱包 Wallet" }, + { id: "transactions", ico: "🧾", label: "交易记录 Transactions" }, + ]}, + { g: "角色视图", items: [ + { id: "admin", ico: "🛠️", label: "管理视图 Admin", role: "admin" }, + { id: "settings", ico: "⚙️", label: "设置 Settings" }, + ]}, + ]; + + const VIEW_TITLE = { + dashboard: "仪表盘 Dashboard", marketplace: "模型市场 Marketplace", sharing: "共享管理 Sharing", + wallet: "钱包 Wallet", transactions: "交易记录 Transactions", settings: "设置 Settings", + admin: "管理视图 Admin", + }; + + function renderNav() { + const nav = $("#nav"); + nav.innerHTML = ""; + NAV.forEach((group) => { + const g = document.createElement("div"); + g.className = "nav-group"; + g.textContent = group.g; + nav.appendChild(g); + group.items.forEach((item) => { + const b = document.createElement("button"); + b.className = "nav-item" + (item.id === activeView ? " active" : ""); + b.dataset.view = item.id; + b.innerHTML = '' + item.ico + '' + esc(item.label) + ""; + if (item.role) { + const tag = document.createElement("span"); + tag.className = "nav-tag"; + tag.textContent = "管理员"; + b.appendChild(tag); + } + b.addEventListener("click", () => switchView(item.id)); + nav.appendChild(b); + }); + }); + $("#mode-label").textContent = "共享市场 · 角色视图"; + } + + function switchView(id) { + activeView = id; + $$(".view").forEach((v) => v.classList.add("hidden")); + $("#view-" + id).classList.remove("hidden"); + renderNav(); + renderView(id); + $("#main").scrollTop = 0; + } + + /* ---------------- 视图渲染 ---------------- */ + + function renderView(id) { + if (id === "dashboard") renderDashboard(); + else if (id === "marketplace") renderMarketplace(); + else if (id === "sharing") renderSharing(); + else if (id === "wallet") renderWallet(); + else if (id === "transactions") renderTransactions(); + else if (id === "settings") renderSettings(); + else if (id === "admin") renderAdmin(); + } + + /* --- 仪表盘 --- */ + + function renderDashboard() { + const txs = D.TRANSACTIONS; + const monthUse = txs.filter((t) => t.type === "consume").reduce((a, t) => a + Math.abs(t.pts), 0); + const monthEarn = txs.filter((t) => t.type === "earn").reduce((a, t) => a + t.pts, 0); + const recent = txs.slice(0, 5); + + $("#dash-stats").innerHTML = [ + stat("点数余额 Points", D.fmt(D.USER.balance), "1 USD = 1000 点", "accent"), + stat("本月用量 Usage", D.fmt(monthUse) + " 点", "共 " + txs.filter((t) => t.type === "consume").length + " 笔消费"), + stat("共享收益 Earnings", "+" + D.fmt(monthEarn) + " 点", D.SHARINGS.filter((s) => s.status === "on").length + " 个 key 上架中"), + stat("交易笔数 Trades", txs.length + " 笔", "含充值 / 提现 / 消费 / 收益"), + ].join(""); + + $("#dash-recent").innerHTML = recent.map((t) => + '
' + esc(t.detail) + "
" + + '
' + esc(t.time) + " · " + esc(t.partner) + "
" + + '
' + (t.pts > 0 ? "+" : "") + D.fmt(t.pts) + "
" + ).join("") + (recent.length ? "" : '

暂无交易

'); + + const on = D.SHARINGS.filter((s) => s.status === "on"); + $("#dash-sharings").innerHTML = on.map((s) => + '
' + esc(s.model) + "
" + + '
已用 ' + D.fmt(s.used) + " / " + D.fmt(s.quota) + " 点 · 单价 " + D.fmt(s.price) + " 点/1M
" + + '
+' + D.fmt(s.earned) + "
累计收益
" + ).join("") + (on.length ? "" : '

还没有上架的 key

'); + } + + function stat(label, value, sub, cls) { + return '
' + esc(label) + + '
' + value + "
" + esc(sub) + "
"; + } + + /* --- 模型市场 --- */ + + function renderMarketplace() { + const q = ($("#mk-search").value || "").toLowerCase(); + const prov = $("#mk-provider").value; + const sort = $("#mk-sort").value; + + let list = D.MARKET.filter((m) => + (!q || m.model.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q)) && + (!prov || m.provider === prov) + ); + if (sort === "price-asc") list = [...list].sort((a, b) => a.in - b.in); + else if (sort === "price-desc") list = [...list].sort((a, b) => b.in - a.in); + else if (sort === "ctx-desc") list = [...list].sort((a, b) => b.ctx - a.ctx); + + $("#mk-count").textContent = list.length + " 个在售 key"; + $("#mk-body").innerHTML = list.length ? list.map((m) => + "" + esc(m.provider) + "" + esc(m.model) + "" + + "" + D.fmt(m.in) + " 点" + D.fmt(m.out) + " 点" + + "" + D.ctxFmt(m.ctx) + "" + + "" + (m.avail ? '可用' : '繁忙') + "" + + "成功率 " + m.success + "%" + ).join("") : '没有匹配的模型'; + } + + /* --- 共享管理 --- */ + + const SHARE_STATUS = { + on: { text: "上架中", cls: "ok" }, + paused: { text: "已暂停", cls: "warn" }, + off: { text: "已下线", cls: "dim" }, + }; + + function renderSharing() { + const on = D.SHARINGS.filter((s) => s.status === "on"); + const totalEarned = D.SHARINGS.reduce((a, s) => a + s.earned, 0); + const totalUsed = D.SHARINGS.reduce((a, s) => a + s.used, 0); + + $("#share-stats").innerHTML = [ + stat("上架中 Listings", on.length + " 个", D.SHARINGS.length + " 个历史"), + stat("累计收益 Earnings", "+" + D.fmt(totalEarned) + " 点", "全部 key"), + stat("已用量 Used", D.fmt(totalUsed) + " 点", D.SHARINGS.reduce((a, s) => a + s.quota, 0) + " 点总额度"), + ].join(""); + + // 表单下拉 + const selP = $("#sf-provider"); + if (!selP.dataset.init) { + selP.innerHTML = '' + D.PROVIDERS.map((p) => '").join(""); + const selM = $("#sf-model"); + const fillModels = () => { + const p = selP.value; + selM.innerHTML = '' + D.MODELS.filter((m) => !p || m.provider === p) + .map((m) => '").join(""); + showPriceHint(selM.value); + }; + selP.addEventListener("change", fillModels); + selM.addEventListener("change", () => showPriceHint(selM.value)); + selP.dataset.init = "1"; + fillModels(); + } + + $("#share-body").innerHTML = D.SHARINGS.map((s, i) => + "" + esc(s.model) + "" + + "" + esc(maskKey(s.key)) + "" + + "" + D.fmt(s.used) + " / " + D.fmt(s.quota) + "" + + "" + D.fmt(s.price) + " 点/1M" + + "+" + D.fmt(s.earned) + " 点" + + "" + badge(s.status, SHARE_STATUS) + "" + + " " + + "" + ).join(""); + } + + function deleteSharing(i) { + const s = D.SHARINGS[i]; + if (!s) return; + if (!window.confirm("确认彻底下架 " + s.model + " 的 key?删除后不再共享,不可恢复。")) return; + D.SHARINGS.splice(i, 1); + renderSharing(); + if (activeView === "dashboard") renderDashboard(); + toast("已删除 " + s.model + " 的 key(彻底下架)"); + } + + function toggleSharing(i) { + const s = D.SHARINGS[i]; + if (s.status === "on") { s.status = "paused"; toast("已暂停 " + s.model + " 的共享"); } + else if (s.status === "paused") { s.status = "on"; toast("已恢复 " + s.model + " 的共享"); } + else { s.status = "on"; s.quota = 50000; s.used = 0; toast("已重新上架 " + s.model); } + renderSharing(); + if (activeView === "dashboard") renderDashboard(); + } + + /* --- 钱包 --- */ + + const WALLET_COLUMNS = [ + { key: "time", title: "时间", sort: "string", filter: "text" }, + { key: "type", title: "类型", sort: "string", filter: "select", options: ["消费", "收益", "充值", "提现"], filterVal: (t) => TX_TYPE[t.type] || t.type, + render: (t) => esc(TX_TYPE[t.type] || t.type) }, + { key: "partner", title: "对象", sort: "string", filter: "text" }, + { key: "detail", title: "说明", sort: "string", filter: "text" }, + { key: "pts", title: "点数", sort: "number", filter: "number-range", + render: (t) => '' + (t.pts > 0 ? "+" : "") + D.fmt(t.pts) + "" }, + ]; + + function renderWallet() { + $("#side-balance").textContent = D.fmt(D.USER.balance); + $("#wallet-balance").textContent = D.fmt(D.USER.balance); + buildDataTable({ + container: $("#wallet-table"), + columns: WALLET_COLUMNS, + rows: D.TRANSACTIONS, + state: walletTable, + onState: renderWallet, + }); + } + + /* --- 交易记录 --- */ + + const TX_TYPE = { + consume: "消费", earn: "收益", topup: "充值", withdraw: "提现", + }; + + const TX_COLUMNS = [ + { key: "time", title: "时间", sort: "string", filter: "text" }, + { key: "type", title: "类型", sort: "string", filter: "select", options: ["消费", "收益", "充值", "提现"], filterVal: (t) => TX_TYPE[t.type] || t.type, + render: (t) => t.type === "earn" ? '收益' : t.type === "consume" ? '消费' : '' + esc(TX_TYPE[t.type] || t.type) + "" }, + { key: "partner", title: "模型 / Key", sort: "string", filter: "text" }, + { key: "tokens", title: "Token 用量", sort: "string", filter: "text" }, + { key: "pts", title: "点数", sort: "number", filter: "number-range", + render: (t) => '' + (t.pts > 0 ? "+" : "") + D.fmt(t.pts) + "" }, + { key: "status", title: "状态", sort: "string", filter: "select", options: ["成功", "入账", "处理中"], + render: (t) => t.status === "处理中" ? '' + esc(t.status) + "" : esc(t.status) }, + ]; + + 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"); + buildDataTable({ + container: $("#tx-table"), + columns: TX_COLUMNS, + rows: list, + state: txTable, + onState: renderTransactions, + }); + } + + /* --- 通用 MRT 风格数据表格渲染器 --- + 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]; + if (fv == null || fv === "") continue; + const col = columns.find((c) => c.key === key); + if (!col || !col.filter) continue; + const v = col.filterVal ? col.filterVal(row) : row[key]; + if (col.filter === "select") { if (String(v) !== String(fv)) return false; } + else if (col.filter === "number-range") { + const parts = String(fv).split(":"); + const min = parts[0] === "" ? NaN : Number(parts[0]); + const max = parts[1] === "" || parts[1] == null ? NaN : Number(parts[1]); + if (!isNaN(min) && Number(v) < min) return false; + if (!isNaN(max) && Number(v) > max) return false; + } else { + if (!String(v).toLowerCase().includes(String(fv).toLowerCase())) return false; + } + } + return true; + }); + + // 2) 排序(多列:Shift 点击叠加) + if (state.sort.length) { + data = data.slice().sort((a, b) => { + for (const sk of state.sort) { + const col = columns.find((c) => c.key === sk.key); + const av = a[sk.key], bv = b[sk.key]; + let cmp; + if (col && col.sort === "number") cmp = Number(av) - Number(bv); + else cmp = String(av).localeCompare(String(bv), "zh-CN"); + if (cmp !== 0) return sk.dir === "asc" ? cmp : -cmp; + } + return 0; + }); + } + + // 3) 分页 + const pages = Math.max(1, Math.ceil(data.length / state.pageSize)); + if (state.page > pages) state.page = pages; + const pageRows = data.slice((state.page - 1) * state.pageSize, state.page * state.pageSize); + + // 4) 渲染表头(排序按钮)+ 筛选行 + let html = ''; + columns.forEach((col) => { + const sk = state.sort.find((s) => s.key === col.key); + const arrow = sk ? (sk.dir === "asc" ? " ▲" : " ▼") : ""; + html += '"; + }); + html += ""; + columns.forEach((col) => { + const fv = state.filters[col.key] != null ? String(state.filters[col.key]) : ""; + if (col.filter === "select") { + const opts = (col.options || []).map((o) => + '").join(""); + html += '"; + } else if (col.filter === "number-range") { + const p = fv ? fv.split(":") : ["", ""]; + html += ''; + } else if (col.filter) { + html += ''; + } else { + html += ""; + } + }); + html += ""; + if (!pageRows.length) html += ''; + pageRows.forEach((row) => { + html += ""; + columns.forEach((col) => { + html += ""; + }); + html += ""; + }); + html += "
' + + '
没有匹配的记录
" + (col.render ? col.render(row) : esc(row[col.key] == null ? "" : row[col.key])) + "
"; + + // 5) 分页器 + 每页行数 + if (pages > 1) { + html += '
'; + for (let i = 1; i <= pages; i++) html += '"; + html += "" + state.page + " / " + pages + " · 共 " + data.length + " 条
"; + } + html += '
每页 条
'; + + container.innerHTML = html; + + // 6) 事件绑定 + container.querySelectorAll("[data-sort-key]").forEach((b) => { + b.addEventListener("click", (e) => { + const key = b.dataset.sortKey; + const ex = state.sort.find((s) => s.key === key); + if (ex) { + if (ex.dir === "asc") ex.dir = "desc"; + else state.sort = state.sort.filter((s) => s.key !== key); + } else { + if (!e.shiftKey) state.sort = []; + state.sort.push({ key, dir: "asc" }); + } + state.page = 1; + onState(); + }); + }); + container.querySelectorAll(".th-filter").forEach((el) => { + el.addEventListener("input", () => { + const key = el.dataset.filterKey; + const range = el.dataset.range; + if (range) { + const other = container.querySelector('[data-filter-key="' + key + '"][data-range="' + (range === "min" ? "max" : "min") + '"]'); + const min = range === "min" ? el.value : (other ? other.value : ""); + const max = range === "max" ? el.value : (other ? other.value : ""); + state.filters[key] = min + ":" + max; + } else { + state.filters[key] = el.value; + } + state.page = 1; + const focusSel = range ? '[data-filter-key="' + key + '"][data-range="' + range + '"]' : '[data-filter-key="' + key + '"]'; + onState(); + const n = container.querySelector(focusSel); + if (n) n.focus(); + }); + }); + container.querySelectorAll("[data-p]").forEach((b) => { + b.addEventListener("click", () => { state.page = Number(b.dataset.p); onState(); }); + }); + const ps = container.querySelector("[data-page-size]"); + if (ps) { + ps.value = state.pageSize; + ps.addEventListener("change", () => { state.pageSize = Number(ps.value); state.page = 1; onState(); }); + } + } + + /* --- 设置 --- */ + + function renderSettings() { + $("#api-keys").innerHTML = D.API_KEYS.map((k) => + '
' + esc(k.name) + "
" + + '
' + esc(k.id) + " · 创建于 " + esc(k.created) + "
" + + '
最近使用 ' + esc(k.last) + "
" + ).join(""); + } + + /* --- 管理员角色视图 --- */ + + const KEY_STATUS = { + ok: { text: "可用", cls: "ok" }, + limit: { text: "达限额", cls: "warn" }, + exhausted: { text: "已用尽", cls: "danger" }, + revoked: { text: "已撤销", cls: "dim" }, + }; + + function renderAdmin() { + const tab = $("#admin-tabs .tab.active").dataset.adminTab; + $$(".admin-pane").forEach((p) => p.classList.toggle("hidden", p.dataset.adminPane !== tab)); + + if (tab === "keys") { + const q = ($("#ak-search").value || "").toLowerCase(); + const rows = D.KEYS.filter((k) => !q || k.model.includes(q) || k.provider.includes(q) || k.key.includes(q)); + $("#keys-body").innerHTML = rows.map((k) => + "" + esc(k.provider) + "" + esc(k.model) + "" + + "" + esc(k.key) + "" + + "" + D.fmt(k.used) + " / " + D.fmt(k.quota) + "" + + "" + badge(k.status, KEY_STATUS) + "" + + "" + ).join(""); + } else if (tab === "employees") { + const total = D.EMPLOYEES.reduce((a, e) => a + e.quota, 0); + const used = D.EMPLOYEES.reduce((a, e) => a + e.used, 0); + $("#emp-stats").innerHTML = [ + stat("成员数 Members", D.EMPLOYEES.length + " 人", "2 个部门"), + stat("总配额 Quota", D.fmt(total) + " 点", "月"), + stat("已用 Usage", D.fmt(used) + " 点", Math.round((used / total) * 100) + "% 消耗率"), + stat("剩余 Remain", D.fmt(total - used) + " 点", "按成员分配"), + ].join(""); + $("#emp-body").innerHTML = D.EMPLOYEES.map((e, i) => + "" + esc(e.name) + "" + esc(e.dept) + "" + + "" + D.fmt(e.used) + " / " + D.fmt(e.quota) + "" + + "" + D.fmt(e.quota - e.used) + "" + + "" + (e.used / e.quota > 0.9 ? '接近限额' : '正常') + "" + + "" + ).join(""); + } else if (tab === "usage") { + const maxM = Math.max(...D.USAGE_MODEL.map((u) => u.pts)); + const maxE = Math.max(...D.USAGE_EMP.map((u) => u.pts)); + $("#usage-model").innerHTML = D.USAGE_MODEL.map((u) => barRow(u.name, u.pts, maxM, "点")).join(""); + $("#usage-emp").innerHTML = D.USAGE_EMP.map((u) => barRow(u.name, u.pts, maxE, "点")).join(""); + } + } + + function barRow(name, pts, max, unit) { + const pct = Math.round((pts / max) * 100); + return '
' + esc(name) + '' + D.fmt(pts) + " " + unit + "
" + + '
'; + } + + /* ---------------- 事件 ---------------- */ + + function bindEvents() { + // 登录(单一入口,角色由账号决定) + $("#login-form").addEventListener("submit", (e) => { + e.preventDefault(); + $("#login-view").classList.add("hidden"); + $("#app").classList.remove("hidden"); + switchView("dashboard"); + toast("欢迎回来,阿零(演示账号)"); + }); + + $("#logout-btn").addEventListener("click", () => { + $("#app").classList.add("hidden"); + $("#login-view").classList.remove("hidden"); + toast("已退出(静态演示)"); + }); + + // 市场筛选 + $("#mk-search").addEventListener("input", renderMarketplace); + $("#mk-provider").addEventListener("change", renderMarketplace); + $("#mk-sort").addEventListener("change", renderMarketplace); + if (!$("#mk-provider").dataset.init) { + $("#mk-provider").innerHTML = '' + D.PROVIDERS.map((p) => '").join(""); + $("#mk-provider").dataset.init = "1"; + } + + // 共享上架表单(默认收起;点添加展开,提交成功或取消后收起) + const shareFormCard = () => $("#share-form-card"); + const showShareForm = () => { shareFormCard().hidden = false; $("#sf-key").focus(); }; + const hideShareForm = () => { shareFormCard().hidden = true; }; + + $("#share-add-btn").addEventListener("click", showShareForm); + $("#sf-cancel").addEventListener("click", hideShareForm); + + // 共享上架表单(须填 API Key;单价由平台按模型定价自动计算) + $("#share-form").addEventListener("submit", (e) => { + e.preventDefault(); + const model = $("#sf-model").value; + const quota = Number($("#sf-quota").value || 0); + const key = $("#sf-key").value.trim(); + if (!key) { toast("请填写 API Key(上架 key 必须提供真实密钥)"); return; } + if (!model || quota <= 0) { toast("请选择模型并填写有效额度"); return; } + const price = autoPrice(model); + D.SHARINGS.unshift({ id: Date.now(), model, quota, used: 0, price, earned: 0, status: "on", key }); + renderSharing(); + if (activeView === "dashboard") renderDashboard(); + toast("已上架 " + model + "(key 已加密托管,单价 " + D.fmt(price) + " 点/1M 自动)"); + e.target.reset(); + const p = $("#sf-provider"); p.value = ""; p.dispatchEvent(new Event("change")); + $("#sf-quota").value = 5000; + hideShareForm(); + }); + + // 共享列表操作(事件委托:暂停/恢复/重新上架 + 删除) + $("#share-body").addEventListener("click", (e) => { + const b = e.target.closest("[data-share-toggle]"); + if (b) { toggleSharing(Number(b.dataset.shareToggle)); return; } + const d = e.target.closest("[data-share-delete]"); + if (d) deleteSharing(Number(d.dataset.shareDelete)); + }); + + // 钱包按钮 + $("#topup-btn").addEventListener("click", () => toast("充值入口为占位(静态原型)")); + $("#withdraw-btn").addEventListener("click", () => toast("提现入口为占位(静态原型)")); + + // 交易 Tab + $$("#tx-tabs .tab").forEach((b) => b.addEventListener("click", () => { txTab = b.dataset.txTab; txTable.page = 1; renderTransactions(); })); + + // API Key 生成 + $("#new-api-key-btn").addEventListener("click", () => { + const id = "atk_live_" + Math.random().toString(16).slice(2, 10) + "…" + Math.random().toString(16).slice(2, 6); + D.API_KEYS.unshift({ id, name: "新 Key(未命名)", created: "2026-08-13", last: "从未" }); + renderSettings(); + toast("已生成新 API Key(演示)"); + }); + + // 管理台 Tabs + $$("#admin-tabs .tab").forEach((b) => b.addEventListener("click", () => { + $$("#admin-tabs .tab").forEach((x) => x.classList.remove("active")); + b.classList.add("active"); + renderAdmin(); + })); + + $("#add-key-btn").addEventListener("click", () => { + const m = D.MODELS[Math.floor(Math.random() * D.MODELS.length)]; + D.KEYS.unshift({ id: Date.now(), provider: m.provider, model: m.model, key: "sk-****-new1", quota: 100000, used: 0, status: "ok" }); + renderAdmin(); + toast("已添加上游 Key(演示:" + m.model + ")"); + }); + + $("#ak-search").addEventListener("input", renderAdmin); + + // 管理台事件委托(撤销 key / 员工加额) + $("#keys-body").addEventListener("click", (e) => { + const b = e.target.closest("[data-key-revoke]"); + if (!b) return; + const k = D.KEYS.find((x) => x.id === Number(b.dataset.keyRevoke)); + if (!k) return; + k.status = k.status === "revoked" ? "ok" : "revoked"; + renderAdmin(); + toast(k.status === "revoked" ? "已撤销 " + k.model + " 的 key" : "已恢复 " + k.model + " 的 key"); + }); + + $("#emp-body").addEventListener("click", (e) => { + const b = e.target.closest("[data-emp-add]"); + if (!b) return; + D.EMPLOYEES[Number(b.dataset.empAdd)].quota += 5000; + renderAdmin(); + toast("已为成员 +5000 点配额"); + }); + } + + /* ---------------- 初始化 ---------------- */ + + document.addEventListener("DOMContentLoaded", () => { + renderNav(); + bindEvents(); + renderView("dashboard"); + $("#side-balance").textContent = D.fmt(D.USER.balance); + }); +})(); diff --git a/ui/js/data.js b/ui/js/data.js new file mode 100644 index 0000000..fe9f03d --- /dev/null +++ b/ui/js/data.js @@ -0,0 +1,124 @@ +/* ============================================================ + AITokenPool UI Prototype — 内嵌 mock 数据 + 点数规则:1 USD = 1000 点;CNY 按 ~7.2 CNY/USD 折算(示例) + ============================================================ */ + +(function () { + "use strict"; + + const USD = (usd) => Math.round(usd * 1000); + const CNY = (cny) => Math.round((cny / 7.2) * 1000); + + // 模型价格(来自 data/models.example.json,折算为点数 / 1M tokens) + const MODELS = [ + { provider: "deepseek", model: "deepseek-v4-pro", in: USD(0.435), out: USD(0.87), ctx: 1048576, max: 384000, tag: "推理" }, + { provider: "deepseek", model: "deepseek-v4-flash", in: USD(0.14), out: USD(0.28), ctx: 1048576, max: 384000, tag: "通用" }, + { provider: "zhipu", model: "glm-5.2", in: CNY(8.0), out: CNY(28.0), ctx: 1048576, max: null, tag: "通用" }, + { provider: "openai", model: "gpt-5.5-pro", in: USD(30.0), out: USD(180.0), ctx: 1050000, max: null, tag: "旗舰" }, + { provider: "anthropic",model: "claude-opus-4.7", in: USD(5.0), out: USD(25.0), ctx: 1000000, max: null, tag: "旗舰" }, + { provider: "google", model: "gemini-3.1-pro", in: USD(2.0), out: USD(12.0), ctx: 1048576, max: null, tag: "多模态" }, + { provider: "xai", model: "grok-4.6", in: USD(2.0), out: USD(6.0), ctx: 500000, max: null, tag: "通用" }, + { provider: "moonshot", model: "kimi-k3", in: CNY(20.0), out: CNY(100.0), ctx: 1048576, max: null, tag: "长文" }, + { provider: "bytedance-ark", model: "doubao-seed-2.1-pro", in: CNY(6.0), out: CNY(30.0), ctx: 262144, max: null, tag: "通用" }, + { provider: "minimax", model: "minimax-m3", in: USD(0.3), out: USD(1.2), ctx: 1048576, max: null, tag: "轻量" }, + ]; + + // 可用性(市场展示用) + MODELS.forEach((m) => { + m.availability = Math.random() < 0.85 ? "available" : "busy"; + }); + MODELS[2].availability = "available"; // glm + MODELS[4].availability = "busy"; // claude 演示"繁忙" + + const fmt = (n) => n.toLocaleString("zh-CN"); + const ctxFmt = (n) => (n >= 1000000 ? (n / 1000000).toFixed(1) + "M" : fmt(n)); + + window.ATDATA = { + fmt, ctxFmt, + + MODELS, + + PROVIDERS: [...new Set(MODELS.map((m) => m.provider))], + + // 当前用户(公共版) + USER: { name: "阿零", email: "demo@aitokenpool.local", balance: 12480 }, + + // 最近交易 / 收支明细(wallet 与 transactions 共用) + TRANSACTIONS: [ + { id: 1, time: "08-13 20:15", type: "consume", partner: "deepseek-v4-flash", detail: "消费 · 用 shared key", tokens: "1.2M", pts: -320, status: "成功" }, + { id: 2, time: "08-13 19:02", type: "earn", partner: "glm-5.2", detail: "收益 · 我的 key 被消费", tokens: "860K", pts: +420, status: "入账" }, + { id: 3, time: "08-13 17:44", type: "consume", partner: "gpt-5.5-pro", detail: "消费 · 用 shared key", tokens: "45K", pts: -860, status: "成功" }, + { id: 4, time: "08-13 15:10", type: "earn", partner: "kimi-k3", detail: "收益 · 我的 key 被消费", tokens: "1.5M", pts: +980, status: "入账" }, + { id: 5, time: "08-13 11:36", type: "topup", partner: "—", detail: "充值", tokens: "—", pts: +5000, status: "成功" }, + { id: 6, time: "08-12 21:08", type: "consume", partner: "gemini-3.1-pro", detail: "消费 · 用 shared key", tokens: "2.1M", pts: -310, status: "成功" }, + { id: 7, time: "08-12 14:22", type: "earn", partner: "deepseek-v4-flash", detail: "收益 · 我的 key 被消费", tokens: "3.0M", pts: +210, status: "入账" }, + { id: 8, time: "08-12 09:41", type: "consume", partner: "claude-opus-4.7", detail: "消费 · 用 shared key", tokens: "22K", pts: -640, status: "成功" }, + { id: 9, time: "08-11 18:30", type: "withdraw", partner: "—", detail: "提现(占位)", tokens: "—", pts: -2000, status: "处理中" }, + { id: 10, time: "08-11 10:12", type: "earn", partner: "glm-5.2", detail: "收益 · 我的 key 被消费", tokens: "700K", pts: +390, status: "入账" }, + { id: 11, time: "08-10 16:05", type: "consume", partner: "doubao-seed-2.1-pro", detail: "消费 · 用 shared key", tokens: "540K", pts: -260, status: "成功" }, + { id: 12, time: "08-10 08:50", type: "earn", partner: "minimax-m3", detail: "收益 · 我的 key 被消费", tokens: "2.4M", pts: +180, status: "入账" }, + ], + + // 我的共享(price = 按模型定价自动计算的输出单价 点数/1M;key 仅脱敏展示) + SHARINGS: [ + { id: 1, model: "glm-5.2", quota: 100000, used: 32800, price: 3889, earned: 1790, status: "on", key: "sk-zhipu-9f2c41ab7d22" }, + { id: 2, model: "deepseek-v4-flash", quota: 80000, used: 51200, price: 280, earned: 1240, status: "on", key: "sk-ds-3b88d077aa91" }, + { id: 3, model: "kimi-k3", quota: 50000, used: 800, price: 13889, earned: 60, status: "paused", key: "sk-ms-c31f2e8b4405" }, + { id: 4, model: "minimax-m3", quota: 20000, used: 20000, price: 1200, earned: 480, status: "off", key: "sk-mx-77d2a1c9e356" }, + ], + + // 市场在售 key(公共版浏览) + MARKET: [ + { id: 1, provider: "deepseek", model: "deepseek-v4-flash", in: USD(0.14), out: USD(0.28), ctx: 1048576, avail: true, success: 99.2 }, + { id: 2, provider: "zhipu", model: "glm-5.2", in: CNY(8.0), out: CNY(28.0), ctx: 1048576, avail: true, success: 98.6 }, + { id: 3, provider: "openai", model: "gpt-5.5-pro", in: USD(30.0), out: USD(180.0), ctx: 1050000, avail: true, success: 97.1 }, + { id: 4, provider: "anthropic",model: "claude-opus-4.7", in: USD(5.0), out: USD(25.0), ctx: 1000000, avail: false, success: 95.4 }, + { id: 5, provider: "google", model: "gemini-3.1-pro", in: USD(2.0), out: USD(12.0), ctx: 1048576, avail: true, success: 98.9 }, + { id: 6, provider: "moonshot", model: "kimi-k3", in: CNY(20.0), out: CNY(100.0), ctx: 1048576, avail: true, success: 96.8 }, + { id: 7, provider: "xai", model: "grok-4.6", in: USD(2.0), out: USD(6.0), ctx: 500000, avail: true, success: 93.7 }, + ], + + // 我的 API Keys(设置页) + API_KEYS: [ + { id: "atk_live_9f2c1a…e41b", name: "本地 CLI 工具", created: "2026-08-01", last: "08-13 20:11" }, + { id: "atk_live_3b88d0…77aa", name: "CI / 自动化", created: "2026-07-22", last: "08-12 15:40" }, + ], + + // —— 企业版 —— + + // Key 池 + KEYS: [ + { id: 1, provider: "openai", model: "gpt-5.5-pro", key: "sk-****-8f2a", quota: 300000, used: 214000, status: "ok" }, + { id: 2, provider: "anthropic",model: "claude-opus-4.7", key: "sk-****-1c77", quota: 200000, used: 62000, status: "ok" }, + { id: 3, provider: "deepseek", model: "deepseek-v4-flash", key: "sk-****-90bb", quota: 500000, used: 461000, status: "limit" }, + { id: 4, provider: "zhipu", model: "glm-5.2", key: "sk-****-44e9", quota: 150000, used: 150000, status: "exhausted" }, + { id: 5, provider: "google", model: "gemini-3.1-pro", key: "sk-****-77d2", quota: 180000, used: 15000, status: "ok" }, + { id: 6, provider: "moonshot", model: "kimi-k3", key: "sk-****-ab31", quota: 100000, used: 0, status: "revoked" }, + ], + + // 员工 + EMPLOYEES: [ + { id: 1, name: "陈默", dept: "研发", quota: 20000, used: 12400 }, + { id: 2, name: "林小满", dept: "研发", quota: 20000, used: 19850 }, + { id: 3, name: "苏航", dept: "产品", quota: 15000, used: 3200 }, + { id: 4, name: "周雨", dept: "市场", quota: 10000, used: 9800 }, + { id: 5, name: "何明", dept: "设计", quota: 8000, used: 2100 }, + ], + + // 用量报表 + USAGE_MODEL: [ + { name: "gpt-5.5-pro", pts: 18600 }, + { name: "deepseek-v4-flash", pts: 13200 }, + { name: "glm-5.2", pts: 9800 }, + { name: "claude-opus-4.7", pts: 7400 }, + { name: "gemini-3.1-pro", pts: 3100 }, + ], + USAGE_EMP: [ + { name: "林小满", pts: 19850 }, + { name: "陈默", pts: 12400 }, + { name: "周雨", pts: 9800 }, + { name: "苏航", pts: 3200 }, + { name: "何明", pts: 2100 }, + ], + }; +})();