diff --git a/design/CONTRACT.md b/design/CONTRACT.md index b17df3d..2332c55 100644 --- a/design/CONTRACT.md +++ b/design/CONTRACT.md @@ -58,10 +58,11 @@ - 搜索框:bg-subtle、radius md、padding `11 14`、maxWidth 380、marginBottom 14。 - 网格:**2 列 `1fr 1fr`,gap 11**;每格 padding `13 15`、minHeight 56、active=accent-subtle+accent-border:CC + 名(14.5/600) + 副 + ping(mono 12) + Signal + active check。 -### 统计视图 TabStats +### 统计视图 TabStats(上聚合 · 下分设备) - padding `6 28 24`。 -- 3 指标卡一行,gap 14,marginBottom 20;各 padding `18 20`:label(12.5/fg3/600) + 值(mono 26/fg1)。 -- 周柱卡 padding `20 22`:标题(13.5/600) + 7 柱(gap 22, 容器高150, 柱 maxWidth 40, accent opacity .85, radius `6 6 0 0`) + 值(mono 10.5) + 星期(11.5)。 +- **上聚合**:3 指标卡一行,gap 14,marginBottom 20;各 padding `18 20`:label(12.5/fg3/600) + 值(mono 26/fg1)。周柱卡 padding `20 22`:标题(13.5/600) + 7 柱(gap 22, 容器高150, 柱 maxWidth 40, accent opacity .85, radius `6 6 0 0`) + 值(mono 10.5) + 星期(11.5)。 +- **下分设备**「设备明细 / By device」(统一组件 `DeviceStatRow`,四端复用):周柱卡下 marginTop 20,标题(13/600/fg1);设备卡 surface+border+radius-lg+shadow-sm,行内分隔线 border。每行 padding `14 16/15 18`:左平台图标盒 38×38(accent-subtle, radius md, Lucide smartphone/laptop/monitor-smartphone, clay 2.2px) + 中(名 14.5–15/600 省略号 + 占比迷你条 height 4 radius 999, track=border, fill=accent .85, 宽=本设备流量/最忙设备) + 右(流量 mono 14.5–15 + " GB" caption;时长 caption "X h")。流量降序;空数据显示占位「暂无设备用量 / No device usage yet」。 + - 数据源:`GET /v1/usage/devices?days=N`(账户内每设备窗口聚合,busiest-first)→ `deviceUsageProvider`。 ### 账户视图 TabAccount - padding `6 28 24`,maxWidth 640(生产页功能更丰富,沿用同设计语言:套餐横幅 + 分区卡 + 行 + 段控/开关)。 diff --git a/design/ui_kits/desktop/dapp.jsx b/design/ui_kits/desktop/dapp.jsx index 5fa9e4e..c45dde7 100644 --- a/design/ui_kits/desktop/dapp.jsx +++ b/design/ui_kits/desktop/dapp.jsx @@ -135,6 +135,11 @@ function DServers({ t, lang, current, onPick }) { function DStats({ t, lang }) { const vals=[2.1,3.4,1.8,4.6,5.2,6.1,3.0]; const labels=t('days'); const max=6.1; + const devs=[ + { name:'iPhone 15', icon:'smartphone', gb:'11.3', h:'5.2', frac:1.0 }, + { name:'MacBook Pro', icon:'laptop', gb:'6.0', h:'3.1', frac:0.53 }, + { name:'Pixel 8', icon:'smartphone', gb:'1.6', h:'1.1', frac:0.14 }, + ]; return (
@@ -157,6 +162,26 @@ function DStats({ t, lang }) { ))}
+
{t('byDevice')}
+
+ {devs.map((d,i)=>( +
+
+ +
+
+
{d.name}
+
+
+
+
+
+
{d.gb} GB
+
{d.h} h
+
+
+ ))} +
); } diff --git a/design/ui_kits/desktop/dparts.jsx b/design/ui_kits/desktop/dparts.jsx index 9dd45b8..be21b38 100644 --- a/design/ui_kits/desktop/dparts.jsx +++ b/design/ui_kits/desktop/dparts.jsx @@ -70,6 +70,7 @@ const DSTRINGS = { search:{zh:'搜索国家 / 城市',en:'Search country / city'}, trafficMonth:{zh:'本月流量',en:'Traffic'}, avgPing:{zh:'平均延迟',en:'Avg ping'}, durMonth:{zh:'本月时长',en:'Time'}, weekTraffic:{zh:'本周流量 (GB)',en:'This week (GB)'}, + byDevice:{zh:'设备明细',en:'By device'}, noDeviceUsage:{zh:'暂无设备用量',en:'No device usage yet'}, autostart:{zh:'开机自启动',en:'Launch at login'}, autostartSub:{zh:'登录系统时自动启动穿山甲',en:'Start Pangolin when you log in'}, smartRoute:{zh:'智能分流',en:'Smart routing'}, smartRouteSub:{zh:'海外应用加速,本地直连',en:'Accelerate overseas apps, keep local direct'}, killSwitch:{zh:'Kill Switch',en:'Kill Switch'}, killSwitchSub:{zh:'断线时阻断网络,防止 IP 泄露',en:'Block all traffic if connection drops'}, diff --git a/design/ui_kits/mobile/parts.jsx b/design/ui_kits/mobile/parts.jsx index a8fb961..e876b59 100644 --- a/design/ui_kits/mobile/parts.jsx +++ b/design/ui_kits/mobile/parts.jsx @@ -144,6 +144,8 @@ const STRINGS = { durMonth: { zh: '本月时长', en: 'Time' }, weekTraffic: { zh: '本周流量 (GB)', en: 'This week (GB)' }, days7: { zh: ['一','二','三','四','五','六','日'], en: ['M','T','W','T','F','S','S'] }, + byDevice: { zh: '设备明细', en: 'By device' }, + noDeviceUsage:{ zh: '暂无设备用量', en: 'No device usage yet' }, proMember: { zh: 'PRO 会员', en: 'PRO member' }, expires: { zh: '有效期至', en: 'Expires' }, usage: { zh: '本月用量', en: 'Used this month' }, diff --git a/design/ui_kits/mobile/screens.jsx b/design/ui_kits/mobile/screens.jsx index d9bc97e..ac47343 100644 --- a/design/ui_kits/mobile/screens.jsx +++ b/design/ui_kits/mobile/screens.jsx @@ -298,6 +298,12 @@ function SettingsScreen({ t, lang, setLang, dark, setDark, free, setFree, onRede function StatsScreen({ t, lang }) { const vals = [2.1, 3.4, 1.8, 4.6, 5.2, 6.1, 3.0]; const max = 6.1; const labels = t('days7'); const card = { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-sm)' }; + // 分设备用量(下分设备):名称 + 占比迷你条 + 流量/时长,流量降序。 + const devs = [ + { name: 'iPhone 15', icon: 'smartphone', gb: '11.3', h: '5.2', frac: 1.0 }, + { name: 'MacBook Pro', icon: 'laptop', gb: '6.0', h: '3.1', frac: 0.53 }, + { name: 'Pixel 8', icon: 'smartphone', gb: '1.6', h: '1.1', frac: 0.14 }, + ]; return (
@@ -323,6 +329,26 @@ function StatsScreen({ t, lang }) { ))}
+
{t('byDevice')}
+
+ {devs.map((d, i) => ( +
+
+ +
+
+
{d.name}
+
+
+
+
+
+
{d.gb} GB
+
{d.h} h
+
+
+ ))} +
); diff --git a/design/ui_kits/tablet/tabapp.jsx b/design/ui_kits/tablet/tabapp.jsx index 880348f..31a4300 100644 --- a/design/ui_kits/tablet/tabapp.jsx +++ b/design/ui_kits/tablet/tabapp.jsx @@ -23,6 +23,8 @@ const TSTR = { statTime: { zh: '本月时长', en: 'Time this month' }, weekTraffic:{ zh: '本周流量 (GB)', en: 'This week (GB)' }, days: { zh: ['周一','周二','周三','周四','周五','周六','周日'], en: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] }, + byDevice: { zh: '设备明细', en: 'By device' }, + noDeviceUsage:{ zh: '暂无设备用量', en: 'No device usage yet' }, acctEmail: { zh: '邮箱', en: 'Email' }, language: { zh: '语言', en: 'Language' }, darkMode: { zh: '深色外观', en: 'Dark appearance' }, @@ -220,6 +222,11 @@ function TabStats({ tt, lang }) { const days = TSTR.days[lang]; const max = Math.max(...vals); const card = { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-sm)' }; + const devs = [ + { name: 'iPhone 15', icon: 'smartphone', gb: '11.3', h: '5.2', frac: 1.0 }, + { name: 'MacBook Pro', icon: 'laptop', gb: '6.0', h: '3.1', frac: 0.53 }, + { name: 'Pixel 8', icon: 'smartphone', gb: '1.6', h: '1.1', frac: 0.14 }, + ]; return (
@@ -242,6 +249,26 @@ function TabStats({ tt, lang }) { ))}
+
{tt('byDevice')}
+
+ {devs.map((d, i) => ( +
+
+ +
+
+
{d.name}
+
+
+
+
+
+
{d.gb} GB
+
{d.h} h
+
+
+ ))} +
); }