@fish-under-sea/magpie-neartoken 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fish-under-sea/magpie-neartoken",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "NearToken for magpie: a provider whose sign-in is the site's own device authorization, models listed per billing group with their multiplier, balance and vouchers on magpie's usage card, plus a page for spending and top-ups.",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
package/page/app.js CHANGED
@@ -86,6 +86,8 @@
86
86
  let lastError = ''
87
87
  function showError(error) {
88
88
  const said = String(error && error.message ? error.message : error)
89
+ // options 写入限流由 saveDisabled 自己合并重试,不用挂到页脚
90
+ if (/once a second at most|sets its options/i.test(said)) return
89
91
  lastError = said
90
92
  // 宿主侧仍在建 key+URL 供应商时,会撞上 magpie 的内置订阅 id。
91
93
  // 这不是页面能修的,给一句能照做的指引。
@@ -205,6 +207,17 @@
205
207
  if (pendingAct) frag.append(el('p', { class: 'note', text: '正在处理:' + pendingAct }))
206
208
  body.replaceChildren(frag)
207
209
  if (view.pending) paintCountdown()
210
+ // 首页四张卡等高:以账户卡为基准锁死高度,多出来的内容在卡片内滚动
211
+ if (tab === 'home' && view.configured) equalizeHome()
212
+ }
213
+
214
+ /** equalizeHome 量第一张卡(账户)的高度,锁到同组所有卡上。 */
215
+ function equalizeHome() {
216
+ const cards = document.querySelectorAll('.home-grid > .card')
217
+ if (cards.length < 2) return
218
+ const h = Math.round(cards[0].getBoundingClientRect().height)
219
+ if (!h) return
220
+ cards.forEach((c) => { c.style.height = h + 'px' })
208
221
  }
209
222
 
210
223
  /** tabsBar 是三个视角:原本界面、模型广场、使用日志。 */
@@ -432,6 +445,10 @@
432
445
  let marketMode = 'standard'
433
446
  let marketSort = 'default'
434
447
  let marketLayout = 'grid'
448
+ let logQuery = ''
449
+ let logGroup = ''
450
+ let logKind = 'all'
451
+ let logExpanded = false
435
452
  const marketVendors = new Set()
436
453
  const MARKET_KINDS = [['all', '全部'], ['chat', '对话'], ['image', '生图'], ['video', '视频'], ['audio', '语音'], ['embedding', '向量']]
437
454
 
@@ -467,7 +484,14 @@
467
484
  ]
468
485
  const PRICE_TAGS = ['低价', '满血', '促销', '备用', '官Key', '官key', '国模', '自有', '反重力', '神秘折扣', 'CC', 'Kiro', '灵车', 'Argon', 'GPT Pro', 'Claude', 'DeepSeek', 'GLM', 'Gemini', 'Kimi', 'GPT', 'Suno', 'AIS']
469
486
 
470
- /** groupTags 把 [低价][生图] 生图特惠 拆成功能 / 价格 / 余下文字,供排序与重绘。 */
487
+ /** 第二级里的首标签次序:满血 > 官Key > 低价 > 其他。满血排在低价前面。 */
488
+ const LEAD_ORDER = ['满血', '官Key', '官key', '低价', '促销', '备用', '国模', '自有', '反重力', '神秘折扣']
489
+ const leadRank = (w) => {
490
+ const i = LEAD_ORDER.findIndex((k) => w.toLowerCase().includes(k.toLowerCase()))
491
+ return i === -1 ? 99 : i
492
+ }
493
+
494
+ /** groupTags 把 [低价][满血] GPT Pro 2 拆成功能 / 价格 / 余下文字,供排序与重绘。 */
471
495
  function groupTags(name) {
472
496
  const raw = String(name || '')
473
497
  const parts = (raw.match(/\[[^\]]+\]/g) || []).map((p) => p.slice(1, -1))
@@ -475,17 +499,21 @@
475
499
  const all = parts.concat(rest ? [rest] : []).join(' ')
476
500
  let func = '对话'
477
501
  for (const [label, re] of FUNC_TAGS) { if (re.test(all)) { func = label; break } }
478
- const price = parts.filter((p) => PRICE_TAGS.some((k) => p.toLowerCase().includes(k.toLowerCase())))
479
- return { func, price, rest }
502
+ // 官Key / 官key 走官方通道,自动补一个「满血」标签
503
+ const official = /官Key|官key/i.test(all)
504
+ let price = parts.filter((p) => PRICE_TAGS.some((k) => p.toLowerCase().includes(k.toLowerCase())))
505
+ if (official && !price.some((p) => p.includes('满血'))) price = ['满血'].concat(price)
506
+ price.sort((a, b) => leadRank(a) - leadRank(b))
507
+ return { func, price, rest, lead: price[0] || '' }
480
508
  }
481
509
 
482
- /** groupBadge 按「功能 → 价格 → 组名」两级重绘:功能标签在前、价格标签在后。 */
510
+ /** groupBadge 按「功能 → 价格」两级重绘;首标签单独加 .lead 作视觉区分。 */
483
511
  function groupBadge(name) {
484
512
  const t = groupTags(name)
485
513
  const tone = { '生图': 'pink', '视频': 'blue', '语音': 'purple', '向量': 'teal', '对话': 'plain' }
486
514
  const colorOf = (w) => {
487
- if (/低价|反重力|神秘|自有|国模/i.test(w)) return 'green'
488
515
  if (/满血|原生|特惠/i.test(w)) return 'red'
516
+ if (/低价|反重力|神秘|自有|国模/i.test(w)) return 'green'
489
517
  if (/促销|生图|Kiro|灵车/i.test(w)) return 'pink'
490
518
  if (/备用|官Key|官key|CC|Argon|GPT Pro|视频|向量/i.test(w)) return 'blue'
491
519
  return 'plain'
@@ -493,7 +521,10 @@
493
521
  return el('span', { class: 'gb' },
494
522
  // 功能标签只在非对话类显示;对话是默认,不重复挂
495
523
  t.func === '对话' ? null : el('span', { class: 'gb-tag lvl1 ' + (tone[t.func] || 'plain'), text: t.func }),
496
- t.price.map((w) => el('span', { class: 'gb-tag lvl2 ' + colorOf(w), text: w })),
524
+ t.price.map((w, i) => el('span', {
525
+ class: 'gb-tag lvl2 ' + colorOf(w) + (i === 0 ? ' lead' : ''),
526
+ text: w,
527
+ })),
497
528
  t.rest ? el('span', { class: 'gb-rest', text: t.rest }) : null)
498
529
  }
499
530
 
@@ -628,7 +659,14 @@
628
659
  /** marketCard 是广场整页:顶部工具条 + 左筛选 + 右卡片网格。 */
629
660
  function marketCard() {
630
661
  const p = pricing()
631
- const groups = Object.keys(p?.groups || {}).sort((a, b) => (p.groups[a] - p.groups[b]) || a.localeCompare(b, 'zh'))
662
+ // 先按功能分栏,栏内按首标签(满血 → 官Key → 低价 → …)再按倍率排
663
+ const FUNC_ORDER = ['对话', '生图', '视频', '语音', '向量']
664
+ const rankOf = (g) => {
665
+ const t = groupTags(g)
666
+ return (FUNC_ORDER.indexOf(t.func) === -1 ? 99 : FUNC_ORDER.indexOf(t.func)) * 1000
667
+ + leadRank(t.lead) * 10 + (Number(p.groups[g]) || 0)
668
+ }
669
+ const groups = Object.keys(p?.groups || {}).sort((a, b) => rankOf(a) - rankOf(b) || a.localeCompare(b, 'zh'))
632
670
  const vendors = (p?.vendors || []).slice().sort((a, b) => String(a.name).localeCompare(String(b.name), 'zh'))
633
671
  const search = el('input', {
634
672
  class: 'market-search', type: 'search', placeholder: '搜索模型名称、供应商、端点或标签...',
@@ -665,7 +703,6 @@
665
703
  if (!byFunc.has(f)) byFunc.set(f, [])
666
704
  byFunc.get(f).push(g)
667
705
  }
668
- const FUNC_ORDER = ['对话', '生图', '视频', '语音', '向量']
669
706
  const groupChips = el('div', { class: 'market-chips' },
670
707
  el('button', {
671
708
  class: 'market-chip' + (!marketGroup ? ' on' : ''), onclick: () => { marketGroup = ''; render() },
@@ -720,7 +757,7 @@
720
757
  return wrap
721
758
  }
722
759
 
723
- /** 使用日志:这个窗口里每个模型花了多少、用了多少 token。 */
760
+ /** 使用日志:仿站点日志页——筛选条 + 统计条 + 全宽明细表。 */
724
761
  function logsCard() {
725
762
  const data = windows.get(span)
726
763
  const seg = el('div', { class: 'seg' },
@@ -729,42 +766,76 @@
729
766
  key === 'day' ? '日' : key === 'week' ? '周' : '月')))
730
767
  const head = el('div', { class: 'row' },
731
768
  el('h2', { class: 'grow', text: '使用日志', style: 'margin:0' }), seg)
732
- if (!data) return card(null, head, el('p', { class: 'note', text: '正在读取用量…' }))
769
+ if (!data) return el('section', { class: 'logs' }, card(null, head, el('p', { class: 'note', text: '正在读取用量…' })))
733
770
  if (!data.available) {
734
- return card(null, head, el('p', { class: 'note warn', text: data.reason === 'unreachable'
735
- ? '暂时读不到站点的用量数据。' : '站点未提供用量统计。' }))
771
+ return el('section', { class: 'logs' }, card(null, head,
772
+ el('p', { class: 'note warn', text: data.reason === 'unreachable'
773
+ ? '暂时读不到站点的用量数据。' : '站点未提供用量统计。' })))
736
774
  }
737
- const rows = (data.models || []).slice().sort((a, b) => b.tokens - a.tokens)
738
- const total = rows.reduce((sum, m) => sum + m.tokens, 0) || 1
739
- const table = el('div', { class: 'log-table' },
740
- el('div', { class: 'log-row head' },
741
- el('span', { text: '模型' }), el('span', { text: 'Token' }), el('span', { text: '花费' }), el('span', { text: '占比' })),
742
- rows.map((m) => el('div', { class: 'log-row' },
775
+ const all = (data.models || []).slice().sort((a, b) => b.tokens - a.tokens)
776
+ const total = all.reduce((sum, m) => sum + m.tokens, 0) || 1
777
+ const q = logQuery.trim().toLowerCase()
778
+ let rows = q ? all.filter((m) => String(m.model).toLowerCase().includes(q)) : all
779
+ if (logKind !== 'all') rows = rows.filter((m) => {
780
+ const snap = (pricing()?.models || []).find((x) => x.name === m.model)
781
+ return snap ? marketKindOf(snap) === logKind : logKind === 'chat'
782
+ })
783
+ if (logGroup) rows = rows.filter((m) => {
784
+ const snap = (pricing()?.models || []).find((x) => x.name === m.model)
785
+ return snap && (snap.enable_groups || []).includes(logGroup)
786
+ })
787
+ const groups = Object.keys(pricing()?.groups || {}).sort((a, b) => a.localeCompare(b, 'zh'))
788
+ const rangeText = span === 'day' ? '今日' : span === 'week' ? '本周' : '本月'
789
+ const toolbar = el('div', { class: 'logs-toolbar' },
790
+ el('div', { class: 'lt-field grow' }, el('span', { class: 'lt-ico', text: '▦' }),
791
+ el('span', { class: 'lt-range', text: rangeText + ' · 00:00 ~ 现在' })),
792
+ el('input', {
793
+ class: 'lt-input', type: 'search', placeholder: '模型名称', value: logQuery,
794
+ oninput: (e) => { logQuery = e.target.value; render() },
795
+ }),
796
+ el('select', { class: 'lt-select', onchange: (e) => { logGroup = e.target.value; render() } },
797
+ el('option', { value: '', text: '分组' }),
798
+ groups.map((g) => el('option', { value: g, text: g, selected: logGroup === g }))),
799
+ el('select', { class: 'lt-select', onchange: (e) => { logKind = e.target.value; render() } },
800
+ el('option', { value: 'all', text: '所有类型' }),
801
+ MARKET_KINDS.slice(1).map(([k, label]) => el('option', { value: k, text: label, selected: logKind === k }))),
802
+ el('button', { class: 'lt-btn', onclick: () => { logExpanded = !logExpanded; render() } }, logExpanded ? '收起 ▴' : '展开 ▾'))
803
+ const statbar = el('div', { class: 'logs-statbar' },
804
+ el('div', { class: 'ls-chips' },
805
+ el('span', { class: 'ls-chip' }, el('i', { class: 'ls-dot blue' }), '用量 ', el('b', { text: data.spent || '—' })),
806
+ el('span', { class: 'ls-chip' }, el('i', { class: 'ls-dot red' }), 'RPM ', el('b', { text: '0' })),
807
+ el('span', { class: 'ls-chip' }, el('i', { class: 'ls-dot blue' }), 'TPM ', el('b', { text: compact(data.tokens || 0) }))),
808
+ el('div', { class: 'ls-actions' },
809
+ el('button', { class: 'lt-btn', title: '10 秒刷新', onclick: () => { load().catch(() => {}) } }, '↻ 10s'),
810
+ el('button', { class: 'lt-btn', onclick: () => { logQuery = ''; logGroup = ''; logKind = 'all'; render() } }, '重置'),
811
+ el('button', { class: 'lt-btn primary', onclick: () => render() }, '搜索'),
812
+ el('button', { class: 'lt-btn', onclick: () => showError('逐条导出需要宿主提供日志端点。') }, '导出')))
813
+ const cols = ['时间', '令牌', '模型', '流', 'Token', '请求体大小', '费用', '耗时', '详情']
814
+ const table = el('div', { class: 'logs-table' },
815
+ el('div', { class: 'lt-row head' }, cols.map((c) => el('span', { text: c }))),
816
+ rows.length ? rows.map((m) => el('div', { class: 'lt-row' },
817
+ el('span', { text: rangeText }),
818
+ el('span', { text: view.tokenName || '—' }),
743
819
  el('span', { class: 'who', title: m.model, text: m.model }),
820
+ el('span', { text: '—' }),
744
821
  el('span', { class: 'num', text: compact(m.tokens) }),
745
- el('span', { class: 'num', text: m.costText || '' }),
746
- el('span', { class: 'num', text: Math.round((m.tokens / total) * 100) + '%' }))))
747
- const names = rows.slice(0, 8)
748
- const palette = ['#5b8def', '#43b8a4', '#9b7ce0', '#54b26a', '#e0a44a', '#d47a9a', '#6fa8d6', '#c08bd0']
749
- const dist = el('div', { class: 'log-dist' },
750
- el('h3', { text: '按模型分布' }),
751
- names.map((m, i) => el('div', { class: 'dist-row' },
822
+ el('span', { text: '—' }),
823
+ el('span', { class: 'num', text: m.costText || '—' }),
824
+ el('span', { text: '—' }),
825
+ el('span', { class: 'num', text: Math.round((m.tokens / total) * 100) + '%' })))
826
+ : el('div', { class: 'lt-empty', text: '这个筛选下没有记录。' }))
827
+ // 分布不再是下面的一张卡:折进「展开」,默认不占版面
828
+ const dist = logExpanded ? el('div', { class: 'logs-dist' },
829
+ rows.slice(0, 8).map((m, i) => el('div', { class: 'dist-row' },
752
830
  el('div', { class: 'dist-name', title: m.model, text: m.model }),
753
831
  el('div', { class: 'dist-track' },
754
- el('i', { style: 'width:' + Math.max(2, Math.round((m.tokens / total) * 100)) + '%;background:' + palette[i % palette.length] })),
755
- el('div', { class: 'dist-val', text: compact(m.tokens) + ' · ' + Math.round((m.tokens / total) * 100) + '%' }))))
832
+ el('i', { style: 'width:' + Math.max(2, Math.round((m.tokens / total) * 100)) + '%;background:' + ['#5b8def', '#43b8a4', '#9b7ce0', '#54b26a', '#e0a44a', '#d47a9a', '#6fa8d6', '#c08bd0'][i % 8] })),
833
+ el('div', { class: 'dist-val', text: compact(m.tokens) + ' · ' + Math.round((m.tokens / total) * 100) + '%' })))) : null
756
834
  return el('section', { class: 'logs' },
757
835
  el('div', { class: 'logs-head' }, head),
758
- el('div', { class: 'logs-body' },
759
- el('div', { class: 'card' },
760
- el('div', { class: 'stats' },
761
- el('div', { class: 'stat' }, el('div', { class: 'k', text: '消耗' }), el('div', { class: 'v', text: data.spent })),
762
- el('div', { class: 'stat' }, el('div', { class: 'k', text: '当前余额' }), el('div', { class: 'v', text: data.balance || '—' })),
763
- el('div', { class: 'stat' }, el('div', { class: 'k', text: 'Token' }), el('div', { class: 'v', text: compact(data.tokens) }))),
764
- table,
765
- el('p', { class: 'note', text: '数字来自站点用量端点;这个窗口里没有调用就不列模型。' })),
766
- el('div', { class: 'card' }, dist,
767
- el('p', { class: 'note', text: '按 token 占比排,最多列前 8 个模型。' }))))
836
+ toolbar, statbar, dist,
837
+ el('div', { class: 'logs-table-wrap' }, table),
838
+ el('p', { class: 'note', text: '数字来自站点用量端点,按模型汇总;站点未提供逐条流水,故「流 / 请求体大小 / 耗时」留空。' }))
768
839
  }
769
840
  /** toggleModel 翻转一个模型的启用状态,并写回 plugin options。 */
770
841
  async function toggleModel(name) {
@@ -785,15 +856,35 @@
785
856
  await saveDisabled()
786
857
  }
787
858
 
788
- /** saveDisabled 把「关掉的模型」写进 plugin options,provider 插件按它过滤清单。 */
789
- async function saveDisabled() {
859
+ /**
860
+ * saveDisabled 把「关掉的模型」写进 plugin options,provider 插件按它过滤清单。
861
+ *
862
+ * magpie 限制页面每秒最多写一次 options,连点卡片会被拒("a page sets its
863
+ * options once a second at most")。所以这里合并写入:1.1 秒内的多次改动只发
864
+ * 最后一次,正在发送时再排队补一次;限流错误不往页脚挂。
865
+ */
866
+ let saveTimer = null
867
+ let sending = false
868
+ let sendAgain = false
869
+ async function flushDisabled() {
870
+ if (sending) { sendAgain = true; return }
871
+ sending = true
790
872
  try {
791
873
  const next = await bridge.options.set({ ...options, disabledModels: [...disabledModels] })
792
874
  if (next && typeof next === 'object') options = next
793
- } catch (error) {
794
- showError(error)
875
+ lastError = ''
876
+ } catch {
877
+ // 写失败时保留本地状态,下一拍重试;不把限流提示挂到页脚
878
+ sendAgain = true
879
+ } finally {
880
+ sending = false
881
+ if (sendAgain) { sendAgain = false; setTimeout(flushDisabled, 1100) }
795
882
  }
796
883
  }
884
+ function saveDisabled() {
885
+ if (saveTimer) clearTimeout(saveTimer)
886
+ saveTimer = setTimeout(() => { saveTimer = null; flushDisabled() }, 1150)
887
+ }
797
888
 
798
889
  /** ratioText 是倍率的写法:0.1 → 0.1,1 → 1。 */
799
890
  function ratioText(v) {
package/page/groups.js CHANGED
@@ -1,2 +1,2 @@
1
- self.NEARTOKEN_GROUPS = [{"name":"[低价] 灵车 K3.1 Pro 专用","ratio":0.3,"models":1,"ref":"#49"},{"name":"[低价] 神秘折扣模型","ratio":0.12,"models":7,"ref":"#24"},{"name":"[低价] 视频分组","ratio":1.5,"models":2,"ref":"#31"},{"name":"[低价] 语音合成 TTS","ratio":0.3,"models":5,"ref":"#35"},{"name":"[低价] 语音转文字 ASR","ratio":1,"models":1,"ref":"#27"},{"name":"[低价] DeepSeek","ratio":0.4,"models":3,"ref":"#16"},{"name":"[低价] GPT 福利","ratio":0.2,"models":6,"ref":"#56"},{"name":"[低价] Grok","ratio":0.1,"models":13,"ref":"#37"},{"name":"[低价] Seedance","ratio":1,"models":2,"ref":"#19"},{"name":"[低价][备用] GPT Pro","ratio":0.3,"models":8,"ref":"#13"},{"name":"[低价][促销] GPT Image","ratio":0.5,"models":2,"ref":"#39"},{"name":"[低价][反重力] Gemini","ratio":0.5,"models":18,"ref":"#5"},{"name":"[低价][满血] 国模","ratio":0.6,"models":9,"ref":"#6"},{"name":"[低价][满血] Claude 特惠","ratio":1.1,"models":6,"ref":"#22"},{"name":"[低价][满血] Claude 原生","ratio":1.5,"models":6,"ref":"#61"},{"name":"[低价][满血] DeepSeek 1017","ratio":0.5,"models":2,"ref":"#38"},{"name":"[低价][满血] GPT Pro 2","ratio":0.26,"models":8,"ref":"#14"},{"name":"[低价][满血][自有] GLM 官方","ratio":0.25,"models":2,"ref":"#17"},{"name":"[低价][生图] 生图特惠","ratio":0.2,"models":14,"ref":"#9"},{"name":"[低价][Kiro] 高智企业 Claude","ratio":1,"models":6,"ref":"#42"},{"name":"[官key] GPT","ratio":0.9,"models":5,"ref":"#28"},{"name":"[满血] Argon 专用","ratio":1,"models":1,"ref":"#47"},{"name":"[满血] CC 不降智精选","ratio":1.8,"models":10,"ref":"#7"},{"name":"[满血] GPT 不降智精选","ratio":0.35,"models":8,"ref":"#2"},{"name":"[满血] GPT Pro 500 高速","ratio":0.5,"models":2,"ref":"#57"},{"name":"[满血] Kimi K3.1 Pro 专用","ratio":0.6,"models":1,"ref":"#48"},{"name":"[满血] Suno","ratio":1,"models":5,"ref":"#67"},{"name":"[满血][官Key] DeepSeek","ratio":0.8,"models":2,"ref":"#15"},{"name":"[满血][官Key] Gemini","ratio":2,"models":6,"ref":"#8"},{"name":"[生图] Gemini","ratio":1,"models":7,"ref":"#29"},{"name":"[生图] GPT Image","ratio":1,"models":2,"ref":"#4"},{"name":"[AIS] Gemini 向量模型","ratio":1,"models":3,"ref":"#34"}]
2
- self.NEARTOKEN_GROUPS_AT = 1793780165230
1
+ // 由插件在拉取目录时覆盖写入;这里只是占位,免得页面加载时报 404。
2
+ self.NEARTOKEN_GROUPS = self.NEARTOKEN_GROUPS || null
package/page/latest.js CHANGED
@@ -1,2 +1,2 @@
1
- self.NEARTOKEN_LATEST = "0.3.0"
2
- self.NEARTOKEN_LATEST_AT = 1793779995515
1
+ // 由插件在拉取目录时覆盖写入;这里只是占位。
2
+ self.NEARTOKEN_LATEST = self.NEARTOKEN_LATEST || ''
package/page/market.css CHANGED
@@ -97,6 +97,7 @@
97
97
  /* 两级标签:功能在前(饱和色),价格/品质在后(柔和色) */
98
98
  .gb-tag.lvl1 { font-weight: 700; box-shadow: 0 0 0 1px rgba(255,255,255,.12); }
99
99
  .gb-tag.lvl2 { opacity: .82; }
100
+ .gb-tag.lead { font-weight: 700; opacity: 1; box-shadow: 0 0 0 1px rgba(255,255,255,.32); }
100
101
  .gb-tag.purple { background: #7b52b8; }
101
102
  .gb-tag.teal { background: #2f8f8a; }
102
103
  .chip-group { width: 100%; margin: 2px 0 4px; }
@@ -147,20 +148,63 @@
147
148
  .mc-bar i.bad { background: #d08a3c; }
148
149
  .mc-foot { margin-top: 8px; text-align: right; }
149
150
 
150
- @media (max-width: 900px) {
151
- .market-body { grid-template-columns: 1fr; }
152
- .market-aside { position: static; max-height: none; }
153
- .logs-body { grid-template-columns: 1fr; }
154
- }
155
-
156
- /* 使用日志:两栏铺满 */
151
+ /* 使用日志:仿站点日志页,筛选条 + 统计条 + 全宽明细表 */
157
152
  .logs { width: 100%; }
158
- .logs-head > .row { padding: 12px 14px; border: 1px solid var(--nt-line); border-radius: 12px; margin-bottom: 12px; }
159
- .logs-body { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 12px; align-items: start; }
160
- .logs-body > .card { margin: 0; }
161
- .log-dist h3 { font-size: 12px; margin: 0 0 12px; }
162
- .dist-row { display: grid; grid-template-columns: 110px minmax(0, 1fr) 92px; gap: 8px; align-items: center; margin: 8px 0; font-size: 10.5px; }
153
+ .logs-head > .row { padding: 10px 12px; border: 1px solid var(--nt-line); border-radius: 12px; margin-bottom: 10px; }
154
+
155
+ .logs-toolbar {
156
+ display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
157
+ padding: 10px 12px; border: 1px solid var(--nt-line); border-radius: 12px; margin-bottom: 8px;
158
+ background: rgba(255,255,255,.02);
159
+ }
160
+ .lt-field { display: inline-flex; align-items: center; gap: 8px; min-width: 200px; height: 32px; padding: 0 10px;
161
+ border: 1px solid var(--nt-line); border-radius: 9px; color: var(--nt-ink); font-size: 11.5px; }
162
+ .lt-ico { color: var(--nt-muted); }
163
+ .lt-input { height: 32px; flex: 1 1 160px; min-width: 120px; padding: 0 10px;
164
+ border: 1px solid var(--nt-line); border-radius: 9px; background: rgba(0,0,0,.2);
165
+ color: var(--nt-ink); font-size: 11.5px; }
166
+ .lt-input::placeholder { color: var(--nt-faint); }
167
+ .lt-select { height: 32px; flex: 0 1 150px; padding: 0 8px;
168
+ border: 1px solid var(--nt-line); border-radius: 9px; background: #1c2027;
169
+ color: var(--nt-ink); font-size: 11.5px; }
170
+ .lt-btn { height: 32px; padding: 0 11px; border: 1px solid var(--nt-line); border-radius: 9px;
171
+ background: transparent; color: var(--nt-ink); font-size: 11.5px; white-space: nowrap; }
172
+ .lt-btn:hover { border-color: var(--nt-accent); }
173
+ .lt-btn.primary { background: var(--nt-accent); border-color: var(--nt-accent); color: #fff; }
174
+
175
+ .logs-statbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
176
+ padding: 8px 12px; border: 1px solid var(--nt-line); border-radius: 12px; margin-bottom: 10px; }
177
+ .ls-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex: 1; }
178
+ .ls-chip { display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 10px;
179
+ border: 1px solid var(--nt-line); border-radius: 999px; font-size: 10.5px; color: var(--nt-muted); }
180
+ .ls-chip b { color: var(--nt-ink); font-family: var(--nt-code); font-variant-numeric: tabular-nums; }
181
+ .ls-dot { width: 2px; height: 12px; border-radius: 1px; display: inline-block; }
182
+ .ls-dot.blue { background: #5b8def; }
183
+ .ls-dot.red { background: #d05a6a; }
184
+ .ls-actions { display: flex; align-items: center; gap: 6px; }
185
+
186
+ .logs-table-wrap { width: 100%; border: 1px solid var(--nt-line); border-radius: 12px; overflow: hidden; }
187
+ .logs-table { width: 100%; }
188
+ .lt-row { display: grid; grid-template-columns: 90px 90px minmax(140px, 1.6fr) 70px 100px 110px 100px 80px 70px;
189
+ gap: 8px; align-items: center; padding: 9px 12px; border-bottom: 1px solid var(--nt-line); font-size: 11px; }
190
+ .lt-row:last-child { border-bottom: 0; }
191
+ .lt-row.head { color: var(--nt-muted); font-size: 10.5px; background: rgba(255,255,255,.02); }
192
+ .lt-row .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--nt-ink); }
193
+ .lt-row .num { text-align: right; font-family: var(--nt-code); font-variant-numeric: tabular-nums; color: var(--nt-muted); }
194
+ .lt-row > span:not(.who):not(.num) { color: var(--nt-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
195
+ .lt-empty { padding: 26px; text-align: center; color: var(--nt-muted); font-size: 11.5px; }
196
+
197
+ .logs-dist { padding: 10px 12px; border: 1px solid var(--nt-line); border-radius: 12px; margin-bottom: 10px; }
198
+ .dist-row { display: grid; grid-template-columns: 140px minmax(0, 1fr) 110px; gap: 8px; align-items: center; margin: 8px 0; font-size: 10.5px; }
163
199
  .dist-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--nt-muted); }
164
200
  .dist-track { height: 8px; background: rgba(255,255,255,.05); border-radius: 5px; overflow: hidden; }
165
201
  .dist-track i { display: block; height: 100%; border-radius: 5px; }
166
- .dist-val { text-align: right; font-family: var(--nt-code); color: var(--nt-muted); font-variant-numeric: tabular-nums; }
202
+ .dist-val { text-align: right; font-family: var(--nt-code); color: var(--nt-muted); font-variant-numeric: tabular-nums; }
203
+ .logs > .note { margin: 8px 2px 0; }
204
+
205
+ @media (max-width: 900px) {
206
+ .market-body { grid-template-columns: 1fr; }
207
+ .market-aside { position: static; max-height: none; }
208
+ .lt-row { grid-template-columns: 80px minmax(120px, 1.5fr) 90px 90px 60px; }
209
+ .lt-row > span:nth-child(2), .lt-row > span:nth-child(4), .lt-row > span:nth-child(6), .lt-row > span:nth-child(8) { display: none; }
210
+ }
package/page/style.css CHANGED
@@ -42,10 +42,13 @@ html, body {
42
42
  padding: 20px 22px 48px;
43
43
  }
44
44
 
45
- /* 首页铺满:两张卡一行,随窗口宽度伸缩 */
45
+ /* 首页铺满:两张卡一行,随窗口宽度伸缩;以账户卡为基准等高锁定,
46
+ 内容超出时卡片内部滚动,不把整页撑长。 */
46
47
  .home-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; }
47
- .home-grid > .card { margin: 0; }
48
- @media (max-width: 720px) { .home-grid { grid-template-columns: 1fr; } }
48
+ .home-grid > .card { margin: 0; overflow-y: auto; scrollbar-width: thin; }
49
+ .home-grid > .card::-webkit-scrollbar { width: 6px; }
50
+ .home-grid > .card::-webkit-scrollbar-thumb { background: var(--nt-line); border-radius: 3px; }
51
+ @media (max-width: 720px) { .home-grid { grid-template-columns: 1fr; } .home-grid > .card { height: auto !important; } }
49
52
 
50
53
  /* 顶部:品牌 + 连接状态。整页只这里允许一点装饰。 */
51
54
  .head {
package/page/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // 由 scripts/stamp.mjs 生成,别手改:版本号在 package.json。
2
- self.NEARTOKEN_VERSION = "0.4.0"
2
+ self.NEARTOKEN_VERSION = "0.4.1"