@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 +1 -1
- package/page/app.js +132 -41
- package/page/groups.js +2 -2
- package/page/latest.js +2 -2
- package/page/market.css +57 -13
- package/page/style.css +6 -3
- package/page/version.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fish-under-sea/magpie-neartoken",
|
|
3
|
-
"version": "0.4.
|
|
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
|
-
/**
|
|
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
|
-
|
|
479
|
-
|
|
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', {
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
738
|
-
const total =
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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', {
|
|
746
|
-
el('span', { class: 'num', text:
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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:' +
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
/**
|
|
789
|
-
|
|
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
|
-
|
|
794
|
-
|
|
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
|
-
|
|
2
|
-
self.
|
|
1
|
+
// 由插件在拉取目录时覆盖写入;这里只是占位,免得页面加载时报 404。
|
|
2
|
+
self.NEARTOKEN_GROUPS = self.NEARTOKEN_GROUPS || null
|
package/page/latest.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
self.
|
|
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
|
-
|
|
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
|
|
159
|
-
|
|
160
|
-
.logs-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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.
|
|
2
|
+
self.NEARTOKEN_VERSION = "0.4.1"
|