@skyroc/scripts 2.5.0 → 2.5.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/dist/cli.mjs CHANGED
@@ -4,7 +4,7 @@ import { blue, lightGreen, red } from "kolorist";
4
4
  import { cac } from "cac";
5
5
 
6
6
  //#region package.json
7
- var version = "2.5.0";
7
+ var version = "2.5.1";
8
8
 
9
9
  //#endregion
10
10
  //#region src/cli.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyroc/scripts",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "type": "module",
5
5
  "description": "Skyroc admin CLI - changelog, release, git-commit, cleanup and other project automation scripts",
6
6
  "keywords": [
@@ -67,8 +67,8 @@
67
67
  "tsx": "^4.21.0",
68
68
  "typescript": "^6.0.3",
69
69
  "vitest": "^4.0.18",
70
- "@skyroc/tsconfig": "1.0.0",
71
- "@skyroc/config": "1.0.0"
70
+ "@skyroc/config": "1.0.0",
71
+ "@skyroc/tsconfig": "1.0.0"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsdown",
@@ -43,11 +43,9 @@
43
43
  "@skyroc/color": "workspace:*",
44
44
  "@skyroc/core-state": "workspace:*",
45
45
  "@skyroc/hooks": "workspace:*",
46
- "@skyroc/logger": "workspace:*",
47
46
  "@skyroc/service": "workspace:*",
48
47
  "@skyroc/utils": "workspace:*",
49
48
  "@skyroc/web-admin-devtools": "workspace:*",
50
- "@skyroc/web-admin-shell": "workspace:*",
51
49
  "@skyroc/web-ui": "workspace:*",
52
50
  "@tanstack/react-query": "catalog:core",
53
51
  "@tanstack/react-router": "catalog:web",
@@ -1,15 +1,3 @@
1
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
2
- viewBox="0 0 160 160" >
3
- <path d="M57.8,92.8c1.8-2.1,3.5-4.4,5.4-6.4c7.2-7.8,16.8-11.2,26.8-13.6c11-2.6,22.2-4.8,33.2-7.5c4.9-1.2,9.6-3.2,15.2-5.1
4
- c-1.8,3.4-3.1,6.5-5,9.3c-7.7,11.5-19,17.4-32.4,19.6c-5.8,1-11.8,0.9-17.6,1.7c-13.3,1.8-22.4,9.1-26.8,21.5
5
- c-3,8.5-5,17.3-7.5,25.9c-0.4,1.3-0.2,2.1,1.2,2.7c21.6,9.5,42.9,8.6,63.7-2.3c0.4-0.2,0.7-0.5,1.5-1c-14.4-13.6-27-29.3-47.3-35.1
6
- c0,0.1,0-0.2,0.1-0.4c8.3-5.8,22.5-8.6,31.9-2.4c5.4,3.6,10.1,8.4,15.1,12.7c5,4.3,10,8.7,15.1,13.1c3.1-3.2,6.9-7.8,9.7-13.2
7
- c9-17,10.6-34.8,4.9-53.1c-0.6-1.8-0.5-3.2,0.8-4.9c2.1-2.7,3.8-5.8,6.1-9.4c1.8,4.5,3.7,8.6,4.9,12.8c12,42.2-10.7,84.2-51.6,97.7
8
- c-25.5,8.4-49.7,4.9-71.7-10.4c-18.1-12.6-29.3-30-32.4-51.7c-3.2-22.3,0.5-43.4,14.4-61.8c12.7-17,29.8-27.3,51-30.5
9
- c24.1-3.7,45.8,2.1,64.9,17.1c0.5,0.4,1,0.8,1.4,1.2c0.3,0.3,0.6,0.6,1.3,1.3c-3.2,2.1-6.2,4.3-9.3,6.2c-0.4,0.3-1.6-0.3-2.3-0.8
10
- c-25-17.8-59.4-19-84.6,0.2c-14.3,10.9-22.4,25.6-24.9,43c-2.6,18.2,0.8,35.2,11.8,50.3c2.4,3.3,5.4,6.3,8.5,9.9
11
- c1.5-5.9,2.8-11.2,4.2-16.4c2.8-10.4,5.2-20.9,8.5-31.1c4.8-15.1,15.2-25.5,30-31.2c9-3.5,18.5-6,27.8-8.8
12
- c11.4-3.5,22.8-6.8,32.6-13.8c5.2-3.7,9.8-7.9,13.6-13.6c0.1,3.2,0.6,6.5,0.4,9.7c-0.6,10.7-4.3,19.9-13,26.8
13
- c-7.3,5.7-15.8,8.9-24.7,11c-9.4,2.2-18.9,4-28.2,6.4c-8.9,2.3-16.9,6.6-22.3,14.3c-2.1,2.9-3.4,6.4-5.1,9.7
14
- C57.3,92.6,57.5,92.7,57.8,92.8z" fill="currentColor"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" role="img" aria-label="Skyroc 天鹏">
2
+ <path fill="#6366F1" fill-rule="evenodd" d="M 8.31 49.69 Q 8.00 51.38 8.00 54.15 Q 8.00 56.92 8.46 59.08 Q 8.92 61.23 9.85 63.54 Q 10.77 65.85 12.31 68.15 Q 13.85 70.46 15.77 72.38 Q 17.69 74.31 19.85 75.85 Q 22.00 77.38 24.46 78.62 Q 26.92 79.85 30.85 81.00 Q 34.77 82.15 31.92 82.23 Q 29.08 82.31 27.38 82.00 Q 25.69 81.69 23.85 81.08 Q 22.00 80.46 19.23 78.92 Q 16.46 77.38 16.38 78.08 Q 16.31 78.77 16.77 80.15 Q 17.23 81.54 18.15 83.23 Q 19.08 84.92 22.08 87.92 Q 25.08 90.92 28.15 92.46 Q 31.23 94.00 36.00 94.77 Q 40.77 95.54 44.62 95.23 Q 48.46 94.92 53.85 93.69 Q 59.23 92.46 62.15 92.46 Q 65.08 92.46 67.38 93.38 Q 69.69 94.31 70.85 95.46 Q 72.00 96.62 72.46 97.54 Q 72.92 98.46 72.77 100.77 Q 72.62 103.08 71.54 105.38 Q 70.46 107.69 66.92 113.23 Q 63.38 118.77 62.92 120.31 Q 62.46 121.85 62.46 123.54 Q 62.46 125.23 63.08 126.92 Q 63.69 128.62 64.69 129.62 Q 65.69 130.62 69.00 123.92 Q 72.31 117.23 73.92 111.15 Q 75.54 105.08 75.62 108.38 Q 75.69 111.69 74.31 118.31 Q 72.92 124.92 72.77 126.92 Q 72.62 128.92 72.92 130.62 Q 73.23 132.31 74.15 134.31 Q 75.08 136.31 77.31 139.00 Q 79.54 141.69 80.00 141.69 Q 80.46 141.69 81.31 140.85 Q 82.15 140.00 83.85 137.69 Q 85.54 135.38 86.46 132.46 Q 87.38 129.54 87.23 127.08 Q 87.08 124.62 85.69 117.54 Q 84.31 110.46 84.38 107.62 Q 84.46 104.77 86.23 111.31 Q 88.00 117.85 91.00 124.23 Q 94.00 130.62 95.00 129.77 Q 96.00 128.92 96.77 127.23 Q 97.54 125.54 97.54 123.38 Q 97.54 121.23 97.08 119.85 Q 96.62 118.46 93.38 113.54 Q 90.15 108.62 88.77 105.54 Q 87.38 102.46 87.23 100.77 Q 87.08 99.08 88.00 97.38 Q 88.92 95.69 90.08 94.69 Q 91.23 93.69 93.23 92.92 Q 95.23 92.15 97.54 92.15 Q 99.85 92.15 107.08 93.69 Q 114.31 95.23 118.31 95.23 Q 122.31 95.23 126.31 94.15 Q 130.31 93.08 133.08 91.38 Q 135.85 89.69 138.69 86.54 Q 141.54 83.38 142.69 80.38 Q 143.85 77.38 139.08 79.54 Q 134.31 81.69 132.62 82.00 Q 130.92 82.31 128.23 82.23 Q 125.54 82.15 129.00 81.15 Q 132.46 80.15 135.54 78.62 Q 138.62 77.08 140.31 75.85 Q 142.00 74.62 144.69 71.77 Q 147.38 68.92 148.77 66.46 Q 150.15 64.00 150.92 61.54 Q 151.69 59.08 151.85 55.08 Q 152.00 51.08 151.54 49.08 Q 151.08 47.08 150.77 47.08 Q 150.46 47.08 147.85 51.08 Q 145.23 55.08 140.69 59.15 Q 136.15 63.23 131.08 66.00 Q 126.00 68.77 119.69 70.92 Q 113.38 73.08 109.69 74.77 Q 106.00 76.46 101.85 79.85 Q 97.69 83.23 97.46 83.15 Q 97.23 83.08 98.77 80.92 Q 100.31 78.77 103.62 75.92 Q 106.92 73.08 110.62 71.08 Q 114.31 69.08 119.54 67.08 Q 124.77 65.08 129.54 62.46 Q 134.31 59.85 137.31 57.15 Q 140.31 54.46 141.85 52.31 Q 143.38 50.15 144.77 46.31 Q 146.15 42.46 146.15 38.62 Q 146.15 34.77 145.38 32.00 Q 144.62 29.23 143.54 26.92 Q 142.46 24.62 139.92 21.46 Q 137.38 18.31 136.54 22.69 Q 135.69 27.08 134.62 29.85 Q 133.54 32.62 130.00 38.00 Q 126.46 43.38 121.62 48.08 Q 116.77 52.77 108.15 59.38 Q 99.54 66.00 95.08 70.77 Q 90.62 75.54 89.92 75.00 Q 89.23 74.46 88.46 73.08 Q 87.69 71.69 87.38 68.92 Q 87.08 66.15 88.15 63.69 Q 89.23 61.23 90.54 60.08 Q 91.85 58.92 93.15 58.54 Q 94.46 58.15 93.77 57.15 Q 93.08 56.15 88.92 56.31 Q 84.77 56.46 82.62 57.69 Q 80.46 58.92 78.69 60.85 Q 76.92 62.77 77.85 62.77 Q 78.77 62.77 76.62 66.77 Q 74.46 70.77 72.85 72.38 Q 71.23 74.00 70.15 74.46 Q 69.08 74.92 64.46 70.15 Q 59.85 65.38 51.23 58.77 Q 42.62 52.15 38.08 47.77 Q 33.54 43.38 31.54 40.77 Q 29.54 38.15 27.85 35.08 Q 26.15 32.00 25.08 28.92 Q 24.00 25.85 23.31 22.08 Q 22.62 18.31 20.23 21.31 Q 17.85 24.31 16.77 26.46 Q 15.69 28.62 14.77 31.85 Q 13.85 35.08 13.85 38.77 Q 13.85 42.46 15.23 46.31 Q 16.62 50.15 18.15 52.31 Q 19.69 54.46 23.62 57.77 Q 27.54 61.08 30.77 62.77 Q 34.00 64.46 40.00 66.77 Q 46.00 69.08 50.46 71.54 Q 54.92 74.00 57.77 76.69 Q 60.62 79.38 61.77 81.46 Q 62.92 83.54 60.46 81.23 Q 58.00 78.92 55.23 77.08 Q 52.46 75.23 49.69 74.00 Q 46.92 72.77 39.85 70.46 Q 32.77 68.15 27.69 65.23 Q 22.62 62.31 19.00 59.00 Q 15.38 55.69 12.38 51.31 Q 9.38 46.92 9.00 47.46 Q 8.62 48.00 8.31 49.69 Z"/>
15
3
  </svg>
@@ -18,6 +18,9 @@ import { Route as authLoginOutRouteImport } from './../../pages/(auth)/login-out
18
18
  import { Route as authLoginLayoutRouteImport } from './../../pages/(auth)/login/layout'
19
19
  import { Route as authLoginIndexRouteImport } from './../../pages/(auth)/login/index'
20
20
  import { Route as adminHomeIndexRouteImport } from './../../pages/(admin)/home/index'
21
+ import { Route as authLoginResetPwdRouteImport } from './../../pages/(auth)/login/reset-pwd'
22
+ import { Route as authLoginRegisterRouteImport } from './../../pages/(auth)/login/register'
23
+ import { Route as authLoginCodeLoginRouteImport } from './../../pages/(auth)/login/code-login'
21
24
 
22
25
  const adminLayoutRoute = adminLayoutRouteImport.update({
23
26
  id: '/(admin)',
@@ -63,6 +66,21 @@ const adminHomeIndexRoute = adminHomeIndexRouteImport.update({
63
66
  path: '/home/',
64
67
  getParentRoute: () => adminLayoutRoute,
65
68
  } as any)
69
+ const authLoginResetPwdRoute = authLoginResetPwdRouteImport.update({
70
+ id: '/reset-pwd',
71
+ path: '/reset-pwd',
72
+ getParentRoute: () => authLoginLayoutRoute,
73
+ } as any)
74
+ const authLoginRegisterRoute = authLoginRegisterRouteImport.update({
75
+ id: '/register',
76
+ path: '/register',
77
+ getParentRoute: () => authLoginLayoutRoute,
78
+ } as any)
79
+ const authLoginCodeLoginRoute = authLoginCodeLoginRouteImport.update({
80
+ id: '/code-login',
81
+ path: '/code-login',
82
+ getParentRoute: () => authLoginLayoutRoute,
83
+ } as any)
66
84
 
67
85
  export interface FileRoutesByFullPath {
68
86
  '/': typeof IndexRoute
@@ -71,6 +89,9 @@ export interface FileRoutesByFullPath {
71
89
  '/403': typeof errors403Route
72
90
  '/404': typeof errors404Route
73
91
  '/500': typeof errors500Route
92
+ '/login/code-login': typeof authLoginCodeLoginRoute
93
+ '/login/register': typeof authLoginRegisterRoute
94
+ '/login/reset-pwd': typeof authLoginResetPwdRoute
74
95
  '/home/': typeof adminHomeIndexRoute
75
96
  '/login/': typeof authLoginIndexRoute
76
97
  }
@@ -80,6 +101,9 @@ export interface FileRoutesByTo {
80
101
  '/403': typeof errors403Route
81
102
  '/404': typeof errors404Route
82
103
  '/500': typeof errors500Route
104
+ '/login/code-login': typeof authLoginCodeLoginRoute
105
+ '/login/register': typeof authLoginRegisterRoute
106
+ '/login/reset-pwd': typeof authLoginResetPwdRoute
83
107
  '/home': typeof adminHomeIndexRoute
84
108
  '/login': typeof authLoginIndexRoute
85
109
  }
@@ -92,6 +116,9 @@ export interface FileRoutesById {
92
116
  '/(errors)/403': typeof errors403Route
93
117
  '/(errors)/404': typeof errors404Route
94
118
  '/(errors)/500': typeof errors500Route
119
+ '/(auth)/login/code-login': typeof authLoginCodeLoginRoute
120
+ '/(auth)/login/register': typeof authLoginRegisterRoute
121
+ '/(auth)/login/reset-pwd': typeof authLoginResetPwdRoute
95
122
  '/(admin)/home/': typeof adminHomeIndexRoute
96
123
  '/(auth)/login/': typeof authLoginIndexRoute
97
124
  }
@@ -104,10 +131,23 @@ export interface FileRouteTypes {
104
131
  | '/403'
105
132
  | '/404'
106
133
  | '/500'
134
+ | '/login/code-login'
135
+ | '/login/register'
136
+ | '/login/reset-pwd'
107
137
  | '/home/'
108
138
  | '/login/'
109
139
  fileRoutesByTo: FileRoutesByTo
110
- to: '/' | '/login-out' | '/403' | '/404' | '/500' | '/home' | '/login'
140
+ to:
141
+ | '/'
142
+ | '/login-out'
143
+ | '/403'
144
+ | '/404'
145
+ | '/500'
146
+ | '/login/code-login'
147
+ | '/login/register'
148
+ | '/login/reset-pwd'
149
+ | '/home'
150
+ | '/login'
111
151
  id:
112
152
  | '__root__'
113
153
  | '/'
@@ -117,6 +157,9 @@ export interface FileRouteTypes {
117
157
  | '/(errors)/403'
118
158
  | '/(errors)/404'
119
159
  | '/(errors)/500'
160
+ | '/(auth)/login/code-login'
161
+ | '/(auth)/login/register'
162
+ | '/(auth)/login/reset-pwd'
120
163
  | '/(admin)/home/'
121
164
  | '/(auth)/login/'
122
165
  fileRoutesById: FileRoutesById
@@ -196,6 +239,27 @@ declare module '@tanstack/react-router' {
196
239
  preLoaderRoute: typeof adminHomeIndexRouteImport
197
240
  parentRoute: typeof adminLayoutRoute
198
241
  }
242
+ '/(auth)/login/reset-pwd': {
243
+ id: '/(auth)/login/reset-pwd'
244
+ path: '/reset-pwd'
245
+ fullPath: '/login/reset-pwd'
246
+ preLoaderRoute: typeof authLoginResetPwdRouteImport
247
+ parentRoute: typeof authLoginLayoutRoute
248
+ }
249
+ '/(auth)/login/register': {
250
+ id: '/(auth)/login/register'
251
+ path: '/register'
252
+ fullPath: '/login/register'
253
+ preLoaderRoute: typeof authLoginRegisterRouteImport
254
+ parentRoute: typeof authLoginLayoutRoute
255
+ }
256
+ '/(auth)/login/code-login': {
257
+ id: '/(auth)/login/code-login'
258
+ path: '/code-login'
259
+ fullPath: '/login/code-login'
260
+ preLoaderRoute: typeof authLoginCodeLoginRouteImport
261
+ parentRoute: typeof authLoginLayoutRoute
262
+ }
199
263
  }
200
264
  }
201
265
 
@@ -212,10 +276,16 @@ const adminLayoutRouteWithChildren = adminLayoutRoute._addFileChildren(
212
276
  )
213
277
 
214
278
  interface authLoginLayoutRouteChildren {
279
+ authLoginCodeLoginRoute: typeof authLoginCodeLoginRoute
280
+ authLoginRegisterRoute: typeof authLoginRegisterRoute
281
+ authLoginResetPwdRoute: typeof authLoginResetPwdRoute
215
282
  authLoginIndexRoute: typeof authLoginIndexRoute
216
283
  }
217
284
 
218
285
  const authLoginLayoutRouteChildren: authLoginLayoutRouteChildren = {
286
+ authLoginCodeLoginRoute: authLoginCodeLoginRoute,
287
+ authLoginRegisterRoute: authLoginRegisterRoute,
288
+ authLoginResetPwdRoute: authLoginResetPwdRoute,
219
289
  authLoginIndexRoute: authLoginIndexRoute,
220
290
  }
221
291
 
@@ -0,0 +1,79 @@
1
+ import { useCaptcha } from '@skyroc/hooks';
2
+ import { ButtonLink } from '@shell/ui/antd';
3
+ import { REG_PHONE } from '@skyroc/utils';
4
+ import { createFileRoute } from '@tanstack/react-router';
5
+
6
+ import { useAuthFormRules } from '@/features/auth/use-auth-form-rules';
7
+
8
+ type FormValues = {
9
+ code: string;
10
+ phone: string;
11
+ };
12
+
13
+ const CodeLogin = () => {
14
+ const [form] = AForm.useForm<FormValues>();
15
+
16
+ const { t } = useTranslation();
17
+
18
+ const { getCaptcha, isCounting, label, loading } = useCaptcha(t('page.login.codeLogin.getCode'), getCountingLabel);
19
+
20
+ const { formRules } = useAuthFormRules();
21
+
22
+ const phone = AForm.useWatch('phone', form);
23
+
24
+ const isValidPhone = REG_PHONE.test(phone);
25
+
26
+ function handleSubmit(_params: FormValues) {
27
+ // request to reset password
28
+ showSuccessMessage(t('page.login.common.validateSuccess'));
29
+ }
30
+
31
+ function getCountingLabel(count: number) {
32
+ return t('page.login.codeLogin.reGetCode', { time: count });
33
+ }
34
+
35
+ function sendCaptcha() {
36
+ getCaptcha(phone);
37
+ }
38
+
39
+ useKeyPress('enter', () => {
40
+ form.submit();
41
+ });
42
+
43
+ return (
44
+ <>
45
+ <h3 className="text-18px text-primary font-medium">{t('page.login.codeLogin.title')}</h3>
46
+ <AForm className="pt-24px" form={form} onFinish={handleSubmit}>
47
+ <AForm.Item name="phone" rules={formRules.phone}>
48
+ <AInput placeholder={t('page.login.common.phonePlaceholder')} />
49
+ </AForm.Item>
50
+
51
+ <AForm.Item name="code" rules={formRules.code}>
52
+ <div className="w-full flex-y-center gap-16px">
53
+ <AInput placeholder={t('page.login.common.codePlaceholder')} />
54
+ <AButton disabled={!isValidPhone || isCounting} loading={loading} size="large" onClick={sendCaptcha}>
55
+ {label}
56
+ </AButton>
57
+ </div>
58
+ </AForm.Item>
59
+ <ASpace className="w-full" orientation="vertical" size={18}>
60
+ <AButton block htmlType="submit" shape="round" size="large" type="primary">
61
+ {t('common.confirm')}
62
+ </AButton>
63
+
64
+ <ButtonLink block shape="round" size="large" to="..">
65
+ {t('page.login.common.back')}
66
+ </ButtonLink>
67
+ </ASpace>
68
+ </AForm>
69
+ </>
70
+ );
71
+ };
72
+
73
+ export const Route = createFileRoute('/(auth)/login/code-login')({
74
+ component: CodeLogin,
75
+ staticData: {
76
+ title: 'code-login',
77
+ i18nKey: 'route.login_code-login'
78
+ }
79
+ });
@@ -1,7 +1,8 @@
1
+ import { ButtonLink } from '@shell/ui/antd';
1
2
  import { createFileRoute } from '@tanstack/react-router';
2
3
 
3
- import { useAuthFormRules } from '@/features/auth/use-auth-form-rules';
4
4
  import { useInitLogin } from '@/features/auth/use-login';
5
+ import { useAuthFormRules } from '@/features/auth/use-auth-form-rules';
5
6
 
6
7
  type AccountKey = 'admin' | 'super' | 'user';
7
8
 
@@ -19,6 +20,11 @@ const INITIAL_VALUES = {
19
20
  userName: 'Soybean'
20
21
  };
21
22
 
23
+ const LOGIN_ACTION_I18N_KEYS = {
24
+ codeLogin: 'page.login.codeLogin.title',
25
+ register: 'page.login.register.title'
26
+ } as const satisfies Record<'codeLogin' | 'register', I18n.I18nKey>;
27
+
22
28
  const Login = () => {
23
29
  const { t } = useTranslation();
24
30
 
@@ -74,9 +80,24 @@ const Login = () => {
74
80
  <AInput.Password autoComplete="password" size="large" />
75
81
  </AForm.Item>
76
82
  <ASpace className="w-full" orientation="vertical" size={24}>
83
+ <div className="flex-y-center justify-between">
84
+ <ACheckbox>{t('page.login.pwdLogin.rememberMe')}</ACheckbox>
85
+
86
+ <ButtonLink btnType="text" to="/login/reset-pwd">
87
+ {t('page.login.pwdLogin.forgetPassword')}
88
+ </ButtonLink>
89
+ </div>
77
90
  <AButton block color="primary" htmlType="submit" loading={loading} shape="round" size="large" type="primary">
78
91
  {t('common.confirm')}
79
92
  </AButton>
93
+ <div className="flex-y-center justify-between gap-12px">
94
+ <ButtonLink block className="flex-1" to="/login/code-login">
95
+ {t(LOGIN_ACTION_I18N_KEYS.codeLogin) as string}
96
+ </ButtonLink>
97
+ <ButtonLink block className="flex-1" to="/login/register">
98
+ {t(LOGIN_ACTION_I18N_KEYS.register) as string}
99
+ </ButtonLink>
100
+ </div>
80
101
  <ADivider className="!m-0 !text-14px !text-#666">{t('page.login.pwdLogin.otherAccountLogin')}</ADivider>
81
102
  <div className="flex-center gap-12px">
82
103
  {accounts.map(item => {
@@ -1,20 +1,37 @@
1
- import { mixColor } from '@skyroc/color';
1
+ import { getPaletteColorByNumber, mixColor } from '@skyroc/color';
2
+ import { WaveBg } from '@shell/ui/compose';
2
3
  import { useSettingsTheme } from '@shell/theme';
3
4
  import { Outlet, createFileRoute, redirect, useLocation } from '@tanstack/react-router';
4
5
  import { AnimatePresence, motion } from 'motion/react';
5
6
  import { z } from 'zod';
6
7
 
8
+ import Header from './modules/Header';
9
+
7
10
  const COLOR_WHITE = '#ffffff';
8
11
 
9
12
  const LoginSearchSchema = z.object({
10
13
  redirect: z.string().startsWith('/').optional()
11
14
  });
12
15
 
13
- const LoginLayout = () => {
14
- const { t } = useTranslation();
16
+ export const Route = createFileRoute('/(auth)/login')({
17
+ component: LoginLayout,
18
+ validateSearch: LoginSearchSchema,
19
+ beforeLoad: async ({ context, search }) => {
20
+ if (context.isLoggedIn) {
21
+ throw redirect({ to: search.redirect || context.getHomeRoute() });
22
+ }
23
+ },
24
+ staticData: {
25
+ title: 'login',
26
+ i18nKey: 'route.login'
27
+ }
28
+ });
15
29
 
30
+ function LoginLayout() {
16
31
  const { darkMode, themeColor } = useSettingsTheme();
17
32
 
33
+ const bgThemeColor = darkMode ? getPaletteColorByNumber(themeColor, 600) : themeColor;
34
+
18
35
  const ratio = darkMode ? 0.5 : 0.2;
19
36
 
20
37
  const location = useLocation();
@@ -23,12 +40,11 @@ const LoginLayout = () => {
23
40
 
24
41
  return (
25
42
  <div className="relative size-full flex-center overflow-hidden bg-layout" style={{ backgroundColor: bgColor }}>
26
- <ACard className="relative z-4 w-auto rd-8px" variant="borderless">
43
+ <WaveBg themeColor={bgThemeColor} />
44
+
45
+ <ACard className="relative z-4 w-auto rd-12px" variant="borderless">
27
46
  <div className="w-400px lt-sm:w-300px">
28
- <div className="text-center">
29
- <h1 className="m-0 text-28px font-600 text-primary">{t('system.title')}</h1>
30
- <p className="m-0 mt-8px text-14px text-text-2">{t('page.login.common.loginOrRegister')}</p>
31
- </div>
47
+ <Header />
32
48
 
33
49
  <AnimatePresence initial={false} mode="wait">
34
50
  <motion.main
@@ -54,18 +70,4 @@ const LoginLayout = () => {
54
70
  </ACard>
55
71
  </div>
56
72
  );
57
- };
58
-
59
- export const Route = createFileRoute('/(auth)/login')({
60
- component: LoginLayout,
61
- validateSearch: LoginSearchSchema,
62
- beforeLoad: async ({ context, search }) => {
63
- if (context.isLoggedIn) {
64
- throw redirect({ to: search.redirect || context.getHomeRoute() });
65
- }
66
- },
67
- staticData: {
68
- title: 'login',
69
- i18nKey: 'route.login'
70
- }
71
- });
73
+ }
@@ -0,0 +1,25 @@
1
+ import { FlipText } from '@shell/ui/compose';
2
+ import { ThemeSchemaSwitch, useSettingsTheme } from '@shell/theme';
3
+
4
+ import SystemLogo from '@/components/SystemLogo';
5
+ import { LangSwitch } from '@shell/i18n';
6
+
7
+ const Header = memo(() => {
8
+ const { t } = useTranslation();
9
+ const { header } = useSettingsTheme();
10
+
11
+ return (
12
+ <header className="flex-y-center justify-between">
13
+ <SystemLogo className="size-5xl text-primary lt-sm:size-4xl" />
14
+
15
+ <FlipText className="text-3xl text-primary font-500 lt-sm:text-2xl" word={t('system.title')} />
16
+
17
+ <div className="i-flex-col">
18
+ <ThemeSchemaSwitch className="text-xl lt-sm:text-lg" showTooltip={false} />
19
+ <LangSwitch showTooltip={false} visible={header.multilingual.visible} />
20
+ </div>
21
+ </header>
22
+ );
23
+ });
24
+
25
+ export default Header;
@@ -0,0 +1,86 @@
1
+ import { useCaptcha } from '@skyroc/hooks';
2
+ import { ButtonLink } from '@shell/ui/antd';
3
+ import { REG_PHONE } from '@skyroc/utils';
4
+ import { createFileRoute } from '@tanstack/react-router';
5
+
6
+ import { useAuthFormRules } from '@/features/auth/use-auth-form-rules';
7
+
8
+ interface FormModel {
9
+ code: string;
10
+ confirmPassword: string;
11
+ password: string;
12
+ phone: string;
13
+ }
14
+
15
+ const Register = () => {
16
+ const { t } = useTranslation();
17
+
18
+ const { getCaptcha, isCounting, label, loading } = useCaptcha(t('page.login.codeLogin.getCode'), getCountingLabel);
19
+
20
+ const [form] = AForm.useForm<FormModel>();
21
+
22
+ const phone = AForm.useWatch('phone', form);
23
+
24
+ const isValidPhone = REG_PHONE.test(phone);
25
+
26
+ const { createConfirmPwdRule, formRules } = useAuthFormRules();
27
+
28
+ function handleSubmit(_params: FormModel) {
29
+ // request to reset password
30
+ showSuccessMessage(t('page.login.common.validateSuccess'));
31
+ }
32
+
33
+ function getCountingLabel(count: number) {
34
+ return t('page.login.codeLogin.reGetCode', { time: count });
35
+ }
36
+
37
+ function sendCaptcha() {
38
+ getCaptcha(phone);
39
+ }
40
+
41
+ useKeyPress('enter', () => {
42
+ form.submit();
43
+ });
44
+
45
+ return (
46
+ <>
47
+ <h3 className="text-18px text-primary font-medium">{t('page.login.register.title')}</h3>
48
+ <AForm className="pt-24px" form={form} onFinish={handleSubmit}>
49
+ <AForm.Item name="phone" rules={formRules.phone}>
50
+ <AInput placeholder={t('page.login.common.phonePlaceholder')} size="large" />
51
+ </AForm.Item>
52
+ <AForm.Item name="code" rules={formRules.code}>
53
+ <div className="w-full flex-y-center gap-16px">
54
+ <AInput placeholder={t('page.login.common.codePlaceholder')} size="large" />
55
+ <AButton disabled={!isValidPhone || isCounting} loading={loading} size="large" onClick={sendCaptcha}>
56
+ {label}
57
+ </AButton>
58
+ </div>
59
+ </AForm.Item>
60
+ <AForm.Item name="password" rules={formRules.pwd}>
61
+ <AInput placeholder={t('page.login.common.passwordPlaceholder')} />
62
+ </AForm.Item>
63
+ <AForm.Item name="confirmPassword" rules={createConfirmPwdRule(form)}>
64
+ <AInput placeholder={t('page.login.common.confirmPasswordPlaceholder')} size="large" />
65
+ </AForm.Item>
66
+ <ASpace className="w-full" orientation="vertical" size={18}>
67
+ <AButton block htmlType="submit" shape="round" size="large" type="primary">
68
+ {t('common.confirm')}
69
+ </AButton>
70
+
71
+ <ButtonLink block shape="round" size="large" to="..">
72
+ {t('page.login.common.back')}
73
+ </ButtonLink>
74
+ </ASpace>
75
+ </AForm>
76
+ </>
77
+ );
78
+ };
79
+
80
+ export const Route = createFileRoute('/(auth)/login/register')({
81
+ component: Register,
82
+ staticData: {
83
+ title: 'login_register',
84
+ i18nKey: 'route.login_register'
85
+ }
86
+ });
@@ -0,0 +1,73 @@
1
+ import { ButtonLink } from '@shell/ui/antd';
2
+ import { createFileRoute } from '@tanstack/react-router';
3
+
4
+ import { useAuthFormRules } from '@/features/auth/use-auth-form-rules';
5
+
6
+ interface FormModel {
7
+ code: string;
8
+ confirmPassword: string;
9
+ password: string;
10
+ phone: string;
11
+ }
12
+
13
+ const ResetPwd = () => {
14
+ const { t } = useTranslation();
15
+
16
+ const [form] = AForm.useForm<FormModel>();
17
+
18
+ const { createConfirmPwdRule, formRules } = useAuthFormRules();
19
+
20
+ function handleSubmit(_params: FormModel) {
21
+ // request to reset password
22
+ showSuccessMessage(t('page.login.common.validateSuccess'));
23
+ }
24
+
25
+ useKeyPress('enter', () => {
26
+ form.submit();
27
+ });
28
+
29
+ return (
30
+ <>
31
+ <h3 className="text-18px text-primary font-medium">{t('page.login.register.title')}</h3>
32
+ <AForm className="pt-24px" form={form} onFinish={handleSubmit}>
33
+ <AForm.Item name="phone" rules={formRules.phone}>
34
+ <AInput placeholder={t('page.login.common.phonePlaceholder')} size="large" />
35
+ </AForm.Item>
36
+ <AForm.Item name="code" rules={formRules.code}>
37
+ <AInput placeholder={t('page.login.common.codePlaceholder')} size="large" />
38
+ </AForm.Item>
39
+ <AForm.Item name="password" rules={formRules.pwd}>
40
+ <AInput.Password
41
+ autoComplete="password"
42
+ placeholder={t('page.login.common.passwordPlaceholder')}
43
+ size="large"
44
+ />
45
+ </AForm.Item>
46
+ <AForm.Item name="confirmPassword" rules={createConfirmPwdRule(form)}>
47
+ <AInput.Password
48
+ autoComplete="confirm-password"
49
+ placeholder={t('page.login.common.confirmPasswordPlaceholder')}
50
+ size="large"
51
+ />
52
+ </AForm.Item>
53
+ <ASpace className="w-full" orientation="vertical" size={18}>
54
+ <AButton block htmlType="submit" shape="round" size="large" type="primary">
55
+ {t('common.confirm')}
56
+ </AButton>
57
+
58
+ <ButtonLink block shape="round" size="large" to="..">
59
+ {t('page.login.common.back')}
60
+ </ButtonLink>
61
+ </ASpace>
62
+ </AForm>
63
+ </>
64
+ );
65
+ };
66
+
67
+ export const Route = createFileRoute('/(auth)/login/reset-pwd')({
68
+ component: ResetPwd,
69
+ staticData: {
70
+ title: 'login_reset-pwd',
71
+ i18nKey: 'route.login_reset-pwd'
72
+ }
73
+ });
@@ -91,6 +91,7 @@ const AntdProvider = (props: AntdProviderProps) => {
91
91
  >
92
92
  <App style={{ height: '100%' }}>
93
93
  <ContextHolder />
94
+
94
95
  <Watermark
95
96
  className="shadow-initial h-full bg-opacity-100 text-opacity-100"
96
97
  content={watermarkContent}
@@ -60,6 +60,8 @@ export function getAntdTheme(colors: Theme.ThemeColor, darkMode: boolean, settin
60
60
  }
61
61
  },
62
62
  token: {
63
+ // 先注入预设色板,再用运行时主题色覆盖语义色
64
+ ...defaultPresetColors,
63
65
  colorBgContainer: containerBgColor,
64
66
  colorInverted: invertedColor,
65
67
  colorError: error,
@@ -70,9 +72,7 @@ export function getAntdTheme(colors: Theme.ThemeColor, darkMode: boolean, settin
70
72
  colorPrimary: primary,
71
73
  borderRadius: themeRadius,
72
74
  colorSuccess: success,
73
- colorWarning: warning,
74
- // 使用预设的颜色
75
- ...defaultPresetColors
75
+ colorWarning: warning
76
76
  }
77
77
  };
78
78
 
@@ -8,8 +8,8 @@ export const DARK_TEXT_BASE = '#FFFFFF';
8
8
  /**
9
9
  * Generate color palettes (dark mode)
10
10
  *
11
- * Uses generateDarkModePalette algorithm to replace antd's @ant-design/colors Reference Ant Design official dark
12
- * algorithm, palette mapping needs special handling.
11
+ * Uses a dark-mode OKLCH palette anchored at 500 so the user-provided theme color remains the semantic main color. The
12
+ * palette mapping follows Ant Design's dark-mode interaction rules.
13
13
  *
14
14
  * 档位号语义与亮色一致——50 最亮、950 最暗。暗色主题需要深色背景,因此 antd 的 1-4 档取的是色板暗端(950-700),而不是靠翻转档位号来实现。
15
15
  *
@@ -17,7 +17,7 @@ export const DARK_TEXT_BASE = '#FFFFFF';
17
17
  * 7→600),增强交互对比 - 8-10: 复用 5-7 (8→400, 9→500, 10→600),用于文字色
18
18
  */
19
19
  export const generateColorPalettes: GenerateColorMap = (baseColor: string): ColorMap => {
20
- const { palettes } = generateDarkModePalette(baseColor);
20
+ const { palettes } = generateDarkModePalette(baseColor, 500);
21
21
  const [p50, p100, p200, p300, p400, p500, p600, p700, p800, p900, p950] = palettes;
22
22
 
23
23
  return {
@@ -12,7 +12,7 @@ export const LIGHT_TEXT_BASE = '#1F1F1F';
12
12
  * antd's 1-10 indexes
13
13
  */
14
14
  export const generateColorPalettes: GenerateColorMap = (baseColor: string): ColorMap => {
15
- const { palettes } = generateOklchPaletteAnchored(baseColor);
15
+ const { palettes } = generateOklchPaletteAnchored(baseColor, 500);
16
16
  const [p50, p100, p200, p300, p400, p500, p600, p700, p800, p900, p950] = palettes;
17
17
 
18
18
  return {
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "themeRadius": 6,
19
19
  "isInfoFollowPrimary": true,
20
- "themeTextSize": 16,
20
+ "themeTextSize": 14,
21
21
  "layout": {
22
22
  "mode": "vertical",
23
23
  "scrollMode": "content"
@@ -4,7 +4,6 @@
4
4
  "@skyroc/color": "^2.6.0",
5
5
  "@skyroc/core-state": "^1.0.0",
6
6
  "@skyroc/hooks": "^1.0.0",
7
- "@skyroc/logger": "^1.0.0",
8
7
  "@skyroc/service": "^1.0.0",
9
8
  "@skyroc/utils": "^4.0.0",
10
9
  "@skyroc/web-admin-devtools": "^0.1.0",
@@ -31,7 +30,7 @@
31
30
  "@iconify/react": "^6.0.2",
32
31
  "@iconify/types": "^2.0.0",
33
32
  "@sa/uno-config": "^1.0.0",
34
- "@skyroc/scripts": "^2.5.0",
33
+ "@skyroc/scripts": "^2.5.1",
35
34
  "@skyroc/web-admin-vite": "^0.1.0",
36
35
  "@svgr/core": "^8.1.0",
37
36
  "@svgr/plugin-jsx": "^8.1.0",