Skip to content

Commit 65a94a0

Browse files
authored
Feat/revamp (#32)
* init * revamp * revamp
1 parent 437e514 commit 65a94a0

24 files changed

Lines changed: 2461 additions & 1051 deletions

bun.lock

Lines changed: 103 additions & 140 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "pushy-admin",
33
"private": true,
44
"scripts": {
5-
"dev": "PUBLIC_API=https://5.rnupdate.online/api rsbuild dev",
5+
"dev": "PUBLIC_API=https://update.reactnative.cn/api rsbuild dev",
66
"dev:local": "PUBLIC_API=http://localhost:9000 rsbuild dev",
77
"build": "NODE_ENV=production rsbuild build",
88
"preview": "rsbuild preview",
@@ -12,10 +12,10 @@
1212
"dependencies": {
1313
"@ant-design/charts": "^2.6.7",
1414
"@ant-design/icons": "^6.1.1",
15-
"@rsbuild/core": "^1.7.5",
16-
"@rsbuild/plugin-react": "^1.4.6",
17-
"@rsbuild/plugin-svgr": "^1.3.1",
18-
"@tanstack/react-query": "^5.99.2",
15+
"@rsbuild/core": "^2.0.1",
16+
"@rsbuild/plugin-react": "^2.0.0",
17+
"@rsbuild/plugin-svgr": "^2.0.1",
18+
"@tanstack/react-query": "^5.100.5",
1919
"antd": "^6.3.6",
2020
"dayjs": "^1.11.20",
2121
"git-url-parse": "^16.1.0",
@@ -24,25 +24,25 @@
2424
"json-diff-kit": "^1.0.35",
2525
"react": "^19.2.5",
2626
"react-dom": "^19.2.5",
27-
"react-router-dom": "^7.14.1",
27+
"react-router-dom": "^7.14.2",
2828
"ua-parser-js": "^2.0.9",
2929
"vanilla-jsoneditor": "^3.12.0",
3030
"xlsx": "^0.18.5"
3131
},
3232
"devDependencies": {
33-
"@biomejs/biome": "2.4.12",
34-
"@tailwindcss/postcss": "^4.2.3",
33+
"@biomejs/biome": "2.4.13",
34+
"@tailwindcss/postcss": "^4.2.4",
3535
"@testing-library/react": "^16.3.2",
36-
"@types/bun": "^1.3.12",
36+
"@types/bun": "^1.3.13",
3737
"@types/git-url-parse": "^16.0.2",
3838
"@types/node": "^25.6.0",
3939
"@types/react": "^19",
4040
"@types/react-dom": "^19",
4141
"@types/react-router-dom": "^5.3.3",
42-
"@typescript/native-preview": "^7.0.0-dev.20260420.1",
42+
"@typescript/native-preview": "^7.0.0-dev.20260426.1",
4343
"happy-dom": "^20.9.0",
4444
"mitata": "^1.0.34",
45-
"tailwindcss": "^4.2.3",
45+
"tailwindcss": "^4.2.4",
4646
"typescript": "^6.0.3"
4747
},
4848
"trustedDependencies": [
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Form, Input, Modal, message, Select } from 'antd';
2+
import { api } from '@/services/api';
3+
import PlatformIcon from './platform-icon';
4+
5+
export const showCreateAppModal = ({
6+
onCreated,
7+
}: {
8+
onCreated?: (id: number) => void | Promise<void>;
9+
} = {}) => {
10+
let name = '';
11+
let platform = 'android';
12+
13+
Modal.confirm({
14+
icon: null,
15+
closable: true,
16+
maskClosable: true,
17+
content: (
18+
<Form initialValues={{ platform }}>
19+
<br />
20+
<Form.Item label="应用名称" name="name">
21+
<Input
22+
placeholder="请输入应用名称"
23+
onChange={({ target }) => {
24+
name = target.value;
25+
}}
26+
/>
27+
</Form.Item>
28+
<Form.Item label="选择平台" name="platform">
29+
<Select
30+
onSelect={(value: string) => {
31+
platform = value;
32+
}}
33+
options={[
34+
{
35+
value: 'android',
36+
label: (
37+
<>
38+
<PlatformIcon platform="android" className="mr-2" /> Android
39+
</>
40+
),
41+
},
42+
{
43+
value: 'ios',
44+
label: (
45+
<>
46+
<PlatformIcon platform="ios" className="mr-2" /> iOS
47+
</>
48+
),
49+
},
50+
{
51+
value: 'harmony',
52+
label: (
53+
<>
54+
<PlatformIcon platform="harmony" className="mr-[10px]" />
55+
HarmonyOS
56+
</>
57+
),
58+
},
59+
]}
60+
/>
61+
</Form.Item>
62+
</Form>
63+
),
64+
async onOk() {
65+
const trimmedName = name.trim();
66+
if (!trimmedName) {
67+
message.warning('请输入应用名称');
68+
return false;
69+
}
70+
71+
try {
72+
const id = await api.createApp({ name: trimmedName, platform });
73+
if (typeof id === 'number') {
74+
await onCreated?.(id);
75+
}
76+
} catch (error) {
77+
message.error((error as Error).message);
78+
return false;
79+
}
80+
},
81+
});
82+
};
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import { WarningOutlined } from '@ant-design/icons';
2+
import { Alert, Progress, Tag, Tooltip, Typography } from 'antd';
3+
import dayjs from 'dayjs';
4+
import { quotas } from '@/constants/quotas';
5+
import { useUserInfo } from '@/utils/hooks';
6+
7+
const { Text } = Typography;
8+
9+
interface DailyCheckQuotaProps {
10+
variant: 'account';
11+
}
12+
13+
const useDailyCheckQuotaState = () => {
14+
const { user } = useUserInfo();
15+
const quota = user
16+
? (user.quota ?? quotas[user.tier as keyof typeof quotas])
17+
: undefined;
18+
const dailyQuota = quota?.pv;
19+
const remaining = user?.checkQuota;
20+
const hasData = !!dailyQuota && typeof remaining === 'number';
21+
22+
const remainingRatio = hasData ? remaining / dailyQuota : 0;
23+
const percent = Math.max(0, Math.min(100, remainingRatio * 100));
24+
const isExceeded = hasData && remaining <= 0;
25+
const isLow = !isExceeded && remainingRatio <= 0.2;
26+
const status: 'exception' | 'normal' =
27+
isExceeded || isLow ? 'exception' : 'normal';
28+
const tooltip = (
29+
<div className="text-xs">
30+
{user && (
31+
<>
32+
<div>
33+
套餐:
34+
{quota?.title ??
35+
quotas[user.tier as keyof typeof quotas]?.title ??
36+
user.tier}
37+
</div>
38+
<div>
39+
到期:
40+
{user.tierExpiresAt
41+
? dayjs(user.tierExpiresAt).format('YYYY-MM-DD')
42+
: '无'}
43+
</div>
44+
</>
45+
)}
46+
{hasData && (
47+
<>
48+
<div>
49+
今日剩余额度:{Math.max(0, remaining).toLocaleString()} /{' '}
50+
{dailyQuota.toLocaleString()}
51+
</div>
52+
{remaining < 0 && (
53+
<div>已超出:{Math.abs(remaining).toLocaleString()}</div>
54+
)}
55+
</>
56+
)}
57+
{user?.last7dAvg !== undefined && (
58+
<div>7 日平均剩余额度:{user.last7dAvg.toLocaleString()}</div>
59+
)}
60+
</div>
61+
);
62+
63+
return {
64+
hasData,
65+
isExceeded,
66+
isLow,
67+
percent,
68+
status,
69+
tooltip,
70+
user,
71+
};
72+
};
73+
74+
export function DailyCheckQuotaUserTrigger({
75+
compact = false,
76+
showPlanDetails = false,
77+
userName,
78+
}: {
79+
compact?: boolean;
80+
showPlanDetails?: boolean;
81+
userName: string;
82+
}) {
83+
const quotaState = useDailyCheckQuotaState();
84+
const { user } = quotaState;
85+
const strokeColor = quotaState.isExceeded
86+
? '#ef4444'
87+
: quotaState.isLow
88+
? '#f59e0b'
89+
: '#2563eb';
90+
const tierTitle = user
91+
? (user.quota?.title ??
92+
quotas[user.tier as keyof typeof quotas]?.title ??
93+
user.tier)
94+
: '';
95+
const expireLabel = user?.tierExpiresAt
96+
? `${dayjs(user.tierExpiresAt).format('YYYY-MM-DD')} 到期`
97+
: '无到期';
98+
const warningIcon = (quotaState.isExceeded || quotaState.isLow) && (
99+
<WarningOutlined
100+
className={`shrink-0 ${
101+
quotaState.isExceeded ? 'text-red-500' : 'text-amber-500'
102+
}`}
103+
/>
104+
);
105+
const progress = quotaState.hasData && (
106+
<Progress
107+
className="mt-0.5"
108+
percent={quotaState.percent}
109+
showInfo={false}
110+
size="small"
111+
status={quotaState.status}
112+
strokeColor={strokeColor}
113+
trailColor="#e5e7eb"
114+
/>
115+
);
116+
const content = compact ? (
117+
<span className="flex h-10 w-16 min-w-0 flex-col justify-center text-left">
118+
<span className="flex min-w-0 items-center gap-1">
119+
<span className="truncate font-medium text-[11px] text-slate-600 leading-4">
120+
{userName}
121+
</span>
122+
{warningIcon}
123+
</span>
124+
{progress}
125+
</span>
126+
) : (
127+
<span className="flex min-w-0 items-center gap-2.5 text-left">
128+
<span className="min-w-0 flex-1">
129+
<span className="block truncate font-medium text-slate-800 text-sm leading-5">
130+
{userName}
131+
</span>
132+
{showPlanDetails && user && (
133+
<span className="block truncate text-[11px] text-slate-500 leading-4">
134+
{tierTitle} · {expireLabel}
135+
</span>
136+
)}
137+
{progress}
138+
</span>
139+
{warningIcon}
140+
</span>
141+
);
142+
143+
if (!quotaState.hasData) {
144+
return content;
145+
}
146+
147+
return <Tooltip title={quotaState.tooltip}>{content}</Tooltip>;
148+
}
149+
150+
export default function DailyCheckQuota(_props: DailyCheckQuotaProps) {
151+
const quotaState = useDailyCheckQuotaState();
152+
const { user } = quotaState;
153+
if (!user) {
154+
return null;
155+
}
156+
157+
if (!quotaState.hasData) {
158+
return (
159+
<Text type="secondary">
160+
暂无今日检查额度数据。额度用于客户端查询是否有可用热更新,按账户下所有应用汇总。
161+
</Text>
162+
);
163+
}
164+
165+
const message = quotaState.isExceeded
166+
? '今日检查额度已用尽,请升级套餐或等待每日额度重置。'
167+
: quotaState.isLow
168+
? '今日检查额度偏低,继续发布或大量客户端查询时需要留意。'
169+
: '今日检查额度状态正常。';
170+
171+
return (
172+
<div className="space-y-3">
173+
<div className="rounded-lg border border-slate-200 bg-slate-50 p-4">
174+
<div className="mb-2 flex flex-wrap items-center justify-between gap-2">
175+
<div>
176+
<div className="font-medium">每日检查额度</div>
177+
<div className="text-gray-500 text-sm">
178+
客户端检查热更新时消耗,按账户下所有应用合并计算,每日重置。
179+
</div>
180+
</div>
181+
<Tag
182+
color={
183+
quotaState.isExceeded
184+
? 'red'
185+
: quotaState.isLow
186+
? 'orange'
187+
: 'green'
188+
}
189+
>
190+
{quotaState.isExceeded
191+
? '已用尽'
192+
: quotaState.isLow
193+
? '偏低'
194+
: '正常'}
195+
</Tag>
196+
</div>
197+
<Tooltip title={quotaState.tooltip}>
198+
<Progress
199+
percent={quotaState.percent}
200+
showInfo={false}
201+
status={quotaState.status}
202+
strokeLinecap="round"
203+
/>
204+
</Tooltip>
205+
<div className="mt-2 text-gray-500 text-xs">
206+
页面不直接展示具体次数;鼠标悬停进度条可查看剩余额度、套餐上限和 7
207+
日平均。
208+
</div>
209+
</div>
210+
{(quotaState.isExceeded || quotaState.isLow) && (
211+
<Alert
212+
showIcon
213+
type={quotaState.isExceeded ? 'error' : 'warning'}
214+
message={message}
215+
/>
216+
)}
217+
</div>
218+
);
219+
}

src/components/footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Layout, Typography } from 'antd';
22

33
const Footer = () => (
4-
<Layout.Footer className="text-center">
4+
<Layout.Footer className="shrink-0 text-center">
55
<Typography.Paragraph type="secondary">
66
React Native 中文网 © {new Date().getFullYear()} 武汉青罗网络科技有限公司
77
</Typography.Paragraph>

0 commit comments

Comments
 (0)