yingzios-platform-frontend-devtools 1.0.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/README.md +77 -0
- package/dist/base-path.d.ts +2 -0
- package/dist/base-path.d.ts.map +1 -0
- package/dist/base-path.js +2 -0
- package/dist/components/DevUserMenu/JwtGeneratorModal.d.ts +13 -0
- package/dist/components/DevUserMenu/JwtGeneratorModal.d.ts.map +1 -0
- package/dist/components/DevUserMenu/JwtGeneratorModal.js +137 -0
- package/dist/components/DevUserMenu/index.d.ts +31 -0
- package/dist/components/DevUserMenu/index.d.ts.map +1 -0
- package/dist/components/DevUserMenu/index.js +219 -0
- package/dist/components/DevUserMenu/jwt-utils.d.ts +34 -0
- package/dist/components/DevUserMenu/jwt-utils.d.ts.map +1 -0
- package/dist/components/DevUserMenu/jwt-utils.js +85 -0
- package/dist/components/DevUserMenu/styles.d.ts +5 -0
- package/dist/components/DevUserMenu/styles.d.ts.map +1 -0
- package/dist/components/DevUserMenu/styles.js +225 -0
- package/dist/components/StandaloneAppLayout/index.d.ts +63 -0
- package/dist/components/StandaloneAppLayout/index.d.ts.map +1 -0
- package/dist/components/StandaloneAppLayout/index.js +161 -0
- package/dist/components/StandaloneAppLayout/styles.d.ts +5 -0
- package/dist/components/StandaloneAppLayout/styles.d.ts.map +1 -0
- package/dist/components/StandaloneAppLayout/styles.js +230 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/manifest-loader.d.ts +50 -0
- package/dist/manifest-loader.d.ts.map +1 -0
- package/dist/manifest-loader.js +90 -0
- package/package.json +50 -0
- package/src/base-path.ts +2 -0
- package/src/components/DevUserMenu/JwtGeneratorModal.tsx +219 -0
- package/src/components/DevUserMenu/index.tsx +372 -0
- package/src/components/DevUserMenu/jwt-utils.ts +121 -0
- package/src/components/DevUserMenu/styles.ts +229 -0
- package/src/components/StandaloneAppLayout/index.tsx +332 -0
- package/src/components/StandaloneAppLayout/styles.ts +234 -0
- package/src/index.ts +4 -0
- package/src/manifest-loader.ts +146 -0
- package/src/types/scss.d.ts +19 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { FrontendAppManifest, FrontendAppSectionDefinition, FrontendNavIconKey } from 'yz-frontend-core';
|
|
2
|
+
/**
|
|
3
|
+
* 加载 app-manifest.json 文件
|
|
4
|
+
* @param url manifest 文件路径,默认 'app-manifest.json'(相对路径,受 <base> 标签影响)
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadAppManifest(url?: string): Promise<FrontendAppManifest>;
|
|
7
|
+
/**
|
|
8
|
+
* 从 manifest 的 menus 构建 sections(用于导航菜单)
|
|
9
|
+
*
|
|
10
|
+
* 自动推导规则:
|
|
11
|
+
* - path: 默认 `${routeBase}/${key}`(useRelativePaths=true 时为 `/${key}`)
|
|
12
|
+
* - matchPaths: 默认 `[path]`
|
|
13
|
+
* - group: 仅在 manifest 中显式声明时保留;未声明由导航层使用 displayName 作为默认组
|
|
14
|
+
* - order: 默认按数组顺序
|
|
15
|
+
*
|
|
16
|
+
* @param manifest 应用 manifest
|
|
17
|
+
* @param useRelativePaths 是否使用相对路径(Standalone 模式下建议为 true)
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildSectionsFromManifest(manifest: FrontendAppManifest, useRelativePaths?: boolean): FrontendAppSectionDefinition[];
|
|
20
|
+
export interface StandaloneMenuItem {
|
|
21
|
+
key: string;
|
|
22
|
+
label: string;
|
|
23
|
+
path: string;
|
|
24
|
+
iconKey?: FrontendNavIconKey;
|
|
25
|
+
matchPaths: string[];
|
|
26
|
+
end?: boolean;
|
|
27
|
+
order: number;
|
|
28
|
+
}
|
|
29
|
+
export interface StandaloneMenuGroup {
|
|
30
|
+
groupLabel: string;
|
|
31
|
+
items: StandaloneMenuItem[];
|
|
32
|
+
}
|
|
33
|
+
export interface BuildMenuGroupsOptions {
|
|
34
|
+
/**
|
|
35
|
+
* 是否使用相对路径(Standalone 本地开发模式下建议为 true)
|
|
36
|
+
* - true: 菜单路径为 `/${key}`,如 `/profile`
|
|
37
|
+
* - false: 菜单路径为 `${routeBase}/${key}`,如 `/apps/personal/profile`
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
useRelativePaths?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 从 manifest 直接构建菜单组。
|
|
44
|
+
* 按 group 分组;未声明 group 的菜单统一归入 manifest.displayName 默认组。
|
|
45
|
+
*
|
|
46
|
+
* @param manifest 应用 manifest
|
|
47
|
+
* @param options 构建选项
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildMenuGroupsFromManifest(manifest: FrontendAppManifest, options?: BuildMenuGroupsOptions): StandaloneMenuGroup[];
|
|
50
|
+
//# sourceMappingURL=manifest-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,4BAA4B,EAC5B,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAE1B;;;GAGG;AACH,wBAAsB,eAAe,CAAC,GAAG,SAAsB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAQ7F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,mBAAmB,EAC7B,gBAAgB,UAAQ,GACvB,4BAA4B,EAAE,CAuBhC;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAOD,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,GAAE,sBAA2B,GACnC,mBAAmB,EAAE,CA6CvB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 加载 app-manifest.json 文件
|
|
3
|
+
* @param url manifest 文件路径,默认 'app-manifest.json'(相对路径,受 <base> 标签影响)
|
|
4
|
+
*/
|
|
5
|
+
export async function loadAppManifest(url = 'app-manifest.json') {
|
|
6
|
+
const response = await fetch(url);
|
|
7
|
+
if (!response.ok) {
|
|
8
|
+
throw new Error(`Failed to load manifest: ${response.status} ${response.statusText}`);
|
|
9
|
+
}
|
|
10
|
+
return response.json();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 从 manifest 的 menus 构建 sections(用于导航菜单)
|
|
14
|
+
*
|
|
15
|
+
* 自动推导规则:
|
|
16
|
+
* - path: 默认 `${routeBase}/${key}`(useRelativePaths=true 时为 `/${key}`)
|
|
17
|
+
* - matchPaths: 默认 `[path]`
|
|
18
|
+
* - group: 仅在 manifest 中显式声明时保留;未声明由导航层使用 displayName 作为默认组
|
|
19
|
+
* - order: 默认按数组顺序
|
|
20
|
+
*
|
|
21
|
+
* @param manifest 应用 manifest
|
|
22
|
+
* @param useRelativePaths 是否使用相对路径(Standalone 模式下建议为 true)
|
|
23
|
+
*/
|
|
24
|
+
export function buildSectionsFromManifest(manifest, useRelativePaths = false) {
|
|
25
|
+
const { routeBase, menus } = manifest;
|
|
26
|
+
if (!menus || menus.length === 0) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return menus.map((menu, index) => {
|
|
30
|
+
// useRelativePaths 为 true 时,使用相对路径 /${key},否则用完整路径 ${routeBase}/${key}
|
|
31
|
+
const path = menu.path ?? (useRelativePaths ? `/${menu.key}` : `${routeBase}/${menu.key}`);
|
|
32
|
+
return {
|
|
33
|
+
key: menu.key,
|
|
34
|
+
label: menu.label,
|
|
35
|
+
path,
|
|
36
|
+
group: menu.group,
|
|
37
|
+
iconKey: menu.iconKey,
|
|
38
|
+
description: menu.description,
|
|
39
|
+
order: menu.order ?? index,
|
|
40
|
+
end: menu.end,
|
|
41
|
+
matchPaths: menu.matchPaths ?? [path],
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 从 manifest 直接构建菜单组。
|
|
47
|
+
* 按 group 分组;未声明 group 的菜单统一归入 manifest.displayName 默认组。
|
|
48
|
+
*
|
|
49
|
+
* @param manifest 应用 manifest
|
|
50
|
+
* @param options 构建选项
|
|
51
|
+
*/
|
|
52
|
+
export function buildMenuGroupsFromManifest(manifest, options = {}) {
|
|
53
|
+
const { useRelativePaths = true } = options;
|
|
54
|
+
const sections = buildSectionsFromManifest(manifest, useRelativePaths);
|
|
55
|
+
if (sections.length === 0) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const defaultGroupName = manifest.displayName;
|
|
59
|
+
const groupedItems = new Map();
|
|
60
|
+
sections.forEach((section, index) => {
|
|
61
|
+
const groupName = section.group || defaultGroupName;
|
|
62
|
+
const existing = groupedItems.get(groupName) || {
|
|
63
|
+
items: [],
|
|
64
|
+
firstIndex: index,
|
|
65
|
+
};
|
|
66
|
+
existing.items.push({
|
|
67
|
+
key: section.key,
|
|
68
|
+
label: section.label,
|
|
69
|
+
path: section.path,
|
|
70
|
+
iconKey: section.iconKey,
|
|
71
|
+
matchPaths: section.matchPaths ?? [section.path],
|
|
72
|
+
end: section.end,
|
|
73
|
+
order: section.order ?? 0,
|
|
74
|
+
});
|
|
75
|
+
groupedItems.set(groupName, existing);
|
|
76
|
+
});
|
|
77
|
+
return Array.from(groupedItems.entries())
|
|
78
|
+
.map(([groupLabel, { items, firstIndex }]) => ({
|
|
79
|
+
firstIndex,
|
|
80
|
+
groupLabel,
|
|
81
|
+
items: items.sort((a, b) => {
|
|
82
|
+
if (a.order !== b.order) {
|
|
83
|
+
return a.order - b.order;
|
|
84
|
+
}
|
|
85
|
+
return a.label.localeCompare(b.label, 'zh-CN');
|
|
86
|
+
}),
|
|
87
|
+
}))
|
|
88
|
+
.sort((a, b) => a.firstIndex - b.firstIndex)
|
|
89
|
+
.map(({ groupLabel, items }) => ({ groupLabel, items }));
|
|
90
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yingzios-platform-frontend-devtools",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "YingziOS 前端开发工具库 - 提供微应用本地独立运行所需的组件和工具",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/your-org/yingzios-platform-portal.git",
|
|
26
|
+
"directory": "libs/frontend-devtools"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"react",
|
|
30
|
+
"micro-frontend",
|
|
31
|
+
"devtools",
|
|
32
|
+
"development",
|
|
33
|
+
"manifest-loader"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"yingzios-platform-frontend-core": "1.0.1"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"antd": "^5.0.0 || ^6.0.0",
|
|
41
|
+
"react": "^18.0.0",
|
|
42
|
+
"react-dom": "^18.0.0",
|
|
43
|
+
"react-router-dom": "^6.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/react": "^18.0.0",
|
|
47
|
+
"@types/react-dom": "^18.0.0",
|
|
48
|
+
"antd": "^6.3.5"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/base-path.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Form, Input, message } from 'antd';
|
|
3
|
+
import { modalStyles } from './styles';
|
|
4
|
+
import { signJwtHS256, getDefaultJwtPayload } from './jwt-utils';
|
|
5
|
+
|
|
6
|
+
interface JwtGeneratorModalProps {
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
onSuccess: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface FormValues {
|
|
12
|
+
payloadText: string;
|
|
13
|
+
secret: string;
|
|
14
|
+
expiresIn: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const STORAGE_KEY = 'devtools:jwtFormData';
|
|
18
|
+
|
|
19
|
+
function getInitialValues(): FormValues {
|
|
20
|
+
if (typeof window === 'undefined') {
|
|
21
|
+
return {
|
|
22
|
+
payloadText: JSON.stringify(getDefaultJwtPayload(), null, 2),
|
|
23
|
+
secret: 'this-is-a-secret-key-for-jwt',
|
|
24
|
+
expiresIn: 86400,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
30
|
+
if (saved) {
|
|
31
|
+
const parsed = JSON.parse(saved) as FormValues;
|
|
32
|
+
// 验证 payload 是否为有效 JSON
|
|
33
|
+
JSON.parse(parsed.payloadText);
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// 解析失败,使用默认值
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
payloadText: JSON.stringify(getDefaultJwtPayload(), null, 2),
|
|
42
|
+
secret: 'this-is-a-secret-key-for-jwt',
|
|
43
|
+
expiresIn: 86400,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* JWT 生成器对话框
|
|
49
|
+
*
|
|
50
|
+
* 用于本地开发时手动签发 JWT Token,无需依赖 portal 网关。
|
|
51
|
+
*/
|
|
52
|
+
export function JwtGeneratorModal({ onClose, onSuccess }: JwtGeneratorModalProps) {
|
|
53
|
+
const [form] = Form.useForm<FormValues>();
|
|
54
|
+
const [loading, setLoading] = useState(false);
|
|
55
|
+
|
|
56
|
+
const handleGenerate = async (values: FormValues) => {
|
|
57
|
+
const { payloadText, secret, expiresIn } = values;
|
|
58
|
+
|
|
59
|
+
setLoading(true);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// 1. 解析 payload
|
|
63
|
+
const payload = JSON.parse(payloadText);
|
|
64
|
+
|
|
65
|
+
// 2. 生成 JWT
|
|
66
|
+
const token = await signJwtHS256(payload, secret, expiresIn);
|
|
67
|
+
|
|
68
|
+
// 3. 保存到 localStorage
|
|
69
|
+
localStorage.setItem('accessToken', token);
|
|
70
|
+
|
|
71
|
+
// 4. 保存用户信息到 localStorage(用于 DevUserMenu 显示)
|
|
72
|
+
const userInfo = {
|
|
73
|
+
userId: payload.sub || payload.id,
|
|
74
|
+
nickname: payload.nickname,
|
|
75
|
+
avatar: payload.avatar || '',
|
|
76
|
+
phone: payload.phone,
|
|
77
|
+
};
|
|
78
|
+
localStorage.setItem('devtools:userInfo', JSON.stringify(userInfo));
|
|
79
|
+
|
|
80
|
+
// 5. 保存表单数据到 localStorage
|
|
81
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(values));
|
|
82
|
+
|
|
83
|
+
// 6. 显示成功提示并通知父组件
|
|
84
|
+
message.success('JWT Token 生成成功!2 秒后自动刷新页面...');
|
|
85
|
+
onSuccess();
|
|
86
|
+
|
|
87
|
+
// 7. 2 秒后自动刷新页面
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
window.location.reload();
|
|
90
|
+
}, 2000);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error('[JwtGeneratorModal] 生成 JWT 失败:', err);
|
|
93
|
+
message.error(err instanceof Error ? err.message : '生成 JWT 失败,请检查输入');
|
|
94
|
+
} finally {
|
|
95
|
+
setLoading(false);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div style={modalStyles.overlay as React.CSSProperties} onClick={onClose}>
|
|
101
|
+
<div
|
|
102
|
+
style={{ ...modalStyles.modal, width: '750px', maxWidth: '90vw' }}
|
|
103
|
+
onClick={(e) => e.stopPropagation()}
|
|
104
|
+
>
|
|
105
|
+
<div style={modalStyles.header}>
|
|
106
|
+
<h3 style={modalStyles.title}>本地签发 JWT Token</h3>
|
|
107
|
+
<button style={modalStyles.closeButton} onClick={onClose}>
|
|
108
|
+
×
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<Form
|
|
113
|
+
form={form}
|
|
114
|
+
layout="vertical"
|
|
115
|
+
initialValues={getInitialValues()}
|
|
116
|
+
onFinish={handleGenerate}
|
|
117
|
+
style={modalStyles.form}
|
|
118
|
+
>
|
|
119
|
+
{/* Payload 输入框 */}
|
|
120
|
+
<Form.Item
|
|
121
|
+
label={
|
|
122
|
+
<span>
|
|
123
|
+
Payload <span style={{ color: '#ff4d4f' }}>*</span>
|
|
124
|
+
</span>
|
|
125
|
+
}
|
|
126
|
+
name="payloadText"
|
|
127
|
+
rules={[
|
|
128
|
+
{ required: true, message: '请输入 Payload' },
|
|
129
|
+
{
|
|
130
|
+
validator: async (_, value) => {
|
|
131
|
+
if (!value) return;
|
|
132
|
+
try {
|
|
133
|
+
const payload = JSON.parse(value);
|
|
134
|
+
|
|
135
|
+
// 验证必填字段
|
|
136
|
+
if (!payload.id && !payload.sub) {
|
|
137
|
+
throw new Error('缺少必填字段: id 或 sub(用户ID)');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const requiredFields = ['aud', 'nickname', 'phone'];
|
|
141
|
+
for (const field of requiredFields) {
|
|
142
|
+
if (!payload[field]) {
|
|
143
|
+
throw new Error(`缺少必填字段: ${field}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (err instanceof SyntaxError) {
|
|
148
|
+
throw new Error('Payload 格式错误,请输入有效的 JSON');
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
]}
|
|
155
|
+
extra="JSON 格式,必填字段:id/sub(用户ID)、aud(受众)、nickname(昵称)、phone(手机号)、orgId(组织ID,可选)"
|
|
156
|
+
>
|
|
157
|
+
<Input.TextArea
|
|
158
|
+
rows={8}
|
|
159
|
+
placeholder='{"id": "1000000001", "sub": "1000000001", "aud": "internal", ...}'
|
|
160
|
+
style={{
|
|
161
|
+
fontFamily: 'monospace',
|
|
162
|
+
fontSize: '13px',
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
</Form.Item>
|
|
166
|
+
|
|
167
|
+
{/* JWT 密钥输入框 */}
|
|
168
|
+
<Form.Item
|
|
169
|
+
label={
|
|
170
|
+
<span>
|
|
171
|
+
JWT 密钥(Secret)<span style={{ color: '#ff4d4f' }}>*</span>
|
|
172
|
+
</span>
|
|
173
|
+
}
|
|
174
|
+
name="secret"
|
|
175
|
+
rules={[
|
|
176
|
+
{ required: true, message: '请输入 JWT 密钥' },
|
|
177
|
+
{ whitespace: true, message: 'JWT 密钥不能为空' },
|
|
178
|
+
]}
|
|
179
|
+
extra="签名算法:HS256(HMAC SHA256)"
|
|
180
|
+
>
|
|
181
|
+
<Input placeholder="your-secret-key" />
|
|
182
|
+
</Form.Item>
|
|
183
|
+
|
|
184
|
+
{/* 有效期输入框 */}
|
|
185
|
+
<Form.Item
|
|
186
|
+
label={
|
|
187
|
+
<span>
|
|
188
|
+
有效期(秒)<span style={{ color: '#ff4d4f' }}>*</span>
|
|
189
|
+
</span>
|
|
190
|
+
}
|
|
191
|
+
name="expiresIn"
|
|
192
|
+
rules={[
|
|
193
|
+
{ required: true, message: '请输入有效期' },
|
|
194
|
+
{ type: 'number', min: 1, message: '有效期必须是正整数' },
|
|
195
|
+
]}
|
|
196
|
+
extra="默认 86400 秒(1 天)"
|
|
197
|
+
>
|
|
198
|
+
<Input type="number" placeholder="86400" min={1} />
|
|
199
|
+
</Form.Item>
|
|
200
|
+
|
|
201
|
+
<div style={modalStyles.tip}>
|
|
202
|
+
💡 生成的 Token 将保存到 <code style={modalStyles.tipCode}>localStorage.accessToken</code>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<button
|
|
206
|
+
type="submit"
|
|
207
|
+
style={{
|
|
208
|
+
...modalStyles.submitButton,
|
|
209
|
+
...(loading ? modalStyles.submitButtonDisabled : {}),
|
|
210
|
+
}}
|
|
211
|
+
disabled={loading}
|
|
212
|
+
>
|
|
213
|
+
{loading ? '生成中...' : '生成并保存 Token'}
|
|
214
|
+
</button>
|
|
215
|
+
</Form>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|