@gadmin2n/schematics 0.0.110 → 0.0.112
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/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +0 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/ITActivityDay.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/taihu.ts +8 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/authProvider.ts +26 -27
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/DevShell.tsx +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/OnboardingModal.tsx +101 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/style.css +129 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +0 -24
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useUserPageAccess.ts +17 -28
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/index.tsx +15 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +10 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +12 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasCell.tsx +103 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasComponentLibrary.tsx +449 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +794 -713
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +3 -239
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasContextMenuRegistry.tsx +3 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasContextMenu.tsx +79 -8
- package/package.json +1 -1
|
@@ -30,18 +30,11 @@ export const Event: ModelConfig = {
|
|
|
30
30
|
fields: [
|
|
31
31
|
"*",
|
|
32
32
|
"!created_at",
|
|
33
|
-
"!businessRequester",
|
|
34
|
-
"!number",
|
|
35
|
-
"!additionalInfo",
|
|
36
|
-
"!snSysId",
|
|
37
|
-
"!oitOwner",
|
|
38
33
|
"!createdAt",
|
|
39
34
|
"!endDate",
|
|
40
35
|
"!creator",
|
|
41
36
|
"!updatedAt",
|
|
42
37
|
"!startDate",
|
|
43
|
-
"!eventType",
|
|
44
|
-
"!country",
|
|
45
38
|
],
|
|
46
39
|
rowSelection: {
|
|
47
40
|
actions: [
|
|
@@ -86,11 +79,6 @@ export const Event: ModelConfig = {
|
|
|
86
79
|
fields: [
|
|
87
80
|
"*",
|
|
88
81
|
"!created_at",
|
|
89
|
-
"!businessRequester",
|
|
90
|
-
"!number",
|
|
91
|
-
"!additionalInfo",
|
|
92
|
-
"!snSysId",
|
|
93
|
-
"!oitOwner",
|
|
94
82
|
"!createdAt",
|
|
95
83
|
"!endDate",
|
|
96
84
|
"!creator",
|
|
@@ -27,7 +27,7 @@ export const ITActivityDay: ModelConfig = {
|
|
|
27
27
|
filter: [],
|
|
28
28
|
sorter: ["id"],
|
|
29
29
|
},
|
|
30
|
-
fields: ["*", "!createdAt", "!updatedAt"
|
|
30
|
+
fields: ["*", "!createdAt", "!updatedAt"],
|
|
31
31
|
rowSelection: {
|
|
32
32
|
actions: [
|
|
33
33
|
{ action: "DELETE", desc: "Delete" },
|
|
@@ -31,6 +31,12 @@ export default class TaiHuId {
|
|
|
31
31
|
static config = {
|
|
32
32
|
clientId: process.env.TAIHU_ODC_CLIENT_ID,
|
|
33
33
|
clientSecret: process.env.TAIHU_ODC_APP_TOKEN || 'gadmin-default-secret',
|
|
34
|
+
// JWT secret 与 portal 共享,独立于太湖 client_secret;
|
|
35
|
+
// 分支只用它 verify portal 签发的 token,不 sign。
|
|
36
|
+
jwtSecret:
|
|
37
|
+
process.env.GADMIN_JWT_SECRET ||
|
|
38
|
+
process.env.TAIHU_ODC_APP_TOKEN ||
|
|
39
|
+
'gadmin-default-secret',
|
|
34
40
|
apiBase: process.env.TAIHU_BASE,
|
|
35
41
|
callback: process.env.TAIHU_CALLBACK,
|
|
36
42
|
logout: process.env.TAIHU_LOGOUT,
|
|
@@ -138,7 +144,7 @@ export default class TaiHuId {
|
|
|
138
144
|
return new Promise<string>((resolve, reject) => {
|
|
139
145
|
jwt.sign(
|
|
140
146
|
payload,
|
|
141
|
-
TaiHuId.config.
|
|
147
|
+
TaiHuId.config.jwtSecret,
|
|
142
148
|
{ expiresIn },
|
|
143
149
|
(err, token) => {
|
|
144
150
|
if (err) {
|
|
@@ -163,7 +169,7 @@ export default class TaiHuId {
|
|
|
163
169
|
return;
|
|
164
170
|
}
|
|
165
171
|
|
|
166
|
-
jwt.verify(token, TaiHuId.config.
|
|
172
|
+
jwt.verify(token, TaiHuId.config.jwtSecret, {}, (err, decoded) => {
|
|
167
173
|
if (err) {
|
|
168
174
|
reject(err);
|
|
169
175
|
return;
|
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
import { AuthBindings } from '@refinedev/core';
|
|
2
2
|
import { getApiUrl } from 'config/http';
|
|
3
|
-
import { requestHeaders, GAdminTokenName
|
|
3
|
+
import { requestHeaders, GAdminTokenName } from 'helpers';
|
|
4
|
+
import { login as taihuLogin, logout as taihuLogout } from './helpers/login';
|
|
4
5
|
import Cookies from 'js-cookie';
|
|
5
6
|
import memoize from 'lodash.memoize';
|
|
6
7
|
|
|
7
|
-
const getUserInfoOnce = memoize(async () => {
|
|
8
|
+
export const getUserInfoOnce = memoize(async () => {
|
|
8
9
|
const headers = requestHeaders();
|
|
9
|
-
|
|
10
|
-
if (import.meta.env.DEV) {
|
|
11
|
-
console.log('[getUserInfoOnce] Fetching /userinfo');
|
|
12
|
-
console.log('[getUserInfoOnce] Request headers:', headers);
|
|
13
|
-
console.log('[getUserInfoOnce] Hostname:', window.location.hostname);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
10
|
const res = await fetch(`${getApiUrl()}/userinfo`, { headers });
|
|
17
11
|
|
|
18
12
|
if (!res.ok) {
|
|
19
|
-
|
|
20
|
-
if (res.status === 401) {
|
|
21
|
-
console.error('[getUserInfoOnce] 401 - Missing or invalid credentials');
|
|
22
|
-
}
|
|
13
|
+
throw new Error(`userinfo ${res.status}`);
|
|
23
14
|
}
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
return user;
|
|
16
|
+
return res.json();
|
|
27
17
|
});
|
|
28
18
|
|
|
29
19
|
export const authProvider: AuthBindings = {
|
|
@@ -107,9 +97,12 @@ export const authProvider: AuthBindings = {
|
|
|
107
97
|
},
|
|
108
98
|
logout: async () => {
|
|
109
99
|
localStorage.removeItem('email');
|
|
100
|
+
// taihuLogout() 通过 window.location.href 跳转后端 /logout,
|
|
101
|
+
// 后端清 cookie 后再 302 到太湖 IDP 退出 SSO,最后回到首页重新触发 OAuth。
|
|
102
|
+
// 因此这里不需要再返回 redirectTo——页面已经在跳走了。
|
|
103
|
+
taihuLogout();
|
|
110
104
|
return {
|
|
111
105
|
success: true,
|
|
112
|
-
redirectTo: '/',
|
|
113
106
|
};
|
|
114
107
|
},
|
|
115
108
|
onError: async (error) => {
|
|
@@ -117,19 +110,25 @@ export const authProvider: AuthBindings = {
|
|
|
117
110
|
return { error };
|
|
118
111
|
},
|
|
119
112
|
check: async () => {
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
try {
|
|
114
|
+
await getUserInfoOnce();
|
|
115
|
+
sessionStorage.removeItem('gadmin-relogin-inflight');
|
|
122
116
|
return { authenticated: true };
|
|
117
|
+
} catch {
|
|
118
|
+
// 清掉本地缓存,让 helpers/login.ts 的 login() 重新走一遍 OAuth。
|
|
119
|
+
// 不要 redirectTo: '/login' —— 前端没有这个路由,会进死路。
|
|
120
|
+
// 防重入:避免 401 拦截器和 check() 同时跳登录。
|
|
121
|
+
if (!sessionStorage.getItem('gadmin-relogin-inflight')) {
|
|
122
|
+
sessionStorage.setItem('gadmin-relogin-inflight', '1');
|
|
123
|
+
getUserInfoOnce.cache.clear?.();
|
|
124
|
+
taihuLogin();
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
authenticated: false,
|
|
128
|
+
logout: true,
|
|
129
|
+
error: { message: 'Check failed', name: 'Not authenticated' },
|
|
130
|
+
};
|
|
123
131
|
}
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
authenticated: false,
|
|
127
|
-
redirectTo: '/login',
|
|
128
|
-
error: {
|
|
129
|
-
message: 'Check failed',
|
|
130
|
-
name: 'Not authenticated',
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
132
|
},
|
|
134
133
|
getPermissions: async () => ['admin'],
|
|
135
134
|
getIdentity: async () => {
|
|
@@ -10,12 +10,15 @@ import AddDataModal from './AddDataModal';
|
|
|
10
10
|
import AddPageModal from './AddPageModal';
|
|
11
11
|
import DeleteDataConfirm from './DeleteDataConfirm';
|
|
12
12
|
import EditDataModal from './EditDataModal';
|
|
13
|
+
import OnboardingModal from './OnboardingModal';
|
|
13
14
|
import { resolvePagePaths } from '../components/agentPanel/pagePathUtils';
|
|
14
15
|
import SkillMenu from './SkillMenu';
|
|
15
16
|
import { agentAllowedPromise } from '../config/agentAllowed';
|
|
16
17
|
import './style.css';
|
|
17
18
|
import UndoConfirm from './UndoConfirm';
|
|
18
19
|
|
|
20
|
+
const ONBOARDING_KEY = 'gadmin-onboarded';
|
|
21
|
+
|
|
19
22
|
declare global {
|
|
20
23
|
interface Window {
|
|
21
24
|
ChatSDK: any;
|
|
@@ -82,6 +85,9 @@ function DevShellInner() {
|
|
|
82
85
|
const [modal, setModal] = useState<ModalType>(null);
|
|
83
86
|
const [isDataMngtPage, setIsDataMngtPage] = useState(false);
|
|
84
87
|
const [pageType, setPageType] = useState<string>('');
|
|
88
|
+
const [showOnboarding, setShowOnboarding] = useState(
|
|
89
|
+
() => !localStorage.getItem(ONBOARDING_KEY),
|
|
90
|
+
);
|
|
85
91
|
const pageContextRef = useRef<PageContext | null>(null);
|
|
86
92
|
const inspectorSendDepthRef = useRef(0);
|
|
87
93
|
|
|
@@ -475,6 +481,19 @@ function DevShellInner() {
|
|
|
475
481
|
/>
|
|
476
482
|
|
|
477
483
|
{/* Modals */}
|
|
484
|
+
{showOnboarding && (
|
|
485
|
+
<OnboardingModal
|
|
486
|
+
onAddData={() => {
|
|
487
|
+
localStorage.setItem(ONBOARDING_KEY, '1');
|
|
488
|
+
setShowOnboarding(false);
|
|
489
|
+
setModal('addData');
|
|
490
|
+
}}
|
|
491
|
+
onDismiss={() => {
|
|
492
|
+
localStorage.setItem(ONBOARDING_KEY, '1');
|
|
493
|
+
setShowOnboarding(false);
|
|
494
|
+
}}
|
|
495
|
+
/>
|
|
496
|
+
)}
|
|
478
497
|
{modal === 'undo' && (
|
|
479
498
|
<UndoConfirm
|
|
480
499
|
onConfirm={(prompt) => {
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/OnboardingModal.tsx
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
onAddData: () => void;
|
|
5
|
+
onDismiss: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const CAPABILITIES = [
|
|
9
|
+
{
|
|
10
|
+
icon: '🗄️',
|
|
11
|
+
title: '管理数据表',
|
|
12
|
+
desc: '用自然语言描述业务数据,AI 自动生成数据库和完整的数据管理页面',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
icon: '✏️',
|
|
16
|
+
title: '所见即所得定制',
|
|
17
|
+
desc: 'Inspector模式下,点击数据管理页面任意元素,告诉 AI 要改什么,立即生效',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
icon: '📊',
|
|
21
|
+
title: '新建 Dashboard',
|
|
22
|
+
desc: '在 Canvas 画布上拖拽搭建数据可视化看板,完成后一键发布到导航菜单',
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export default function OnboardingModal({ onAddData, onDismiss }: Props) {
|
|
27
|
+
const [step, setStep] = useState<1 | 2>(1);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div id="onboarding-overlay">
|
|
31
|
+
<div id="onboarding-card">
|
|
32
|
+
{step === 1 ? (
|
|
33
|
+
<>
|
|
34
|
+
<div className="onboarding-header">
|
|
35
|
+
<div className="onboarding-welcome-icon">👋</div>
|
|
36
|
+
<h2 className="onboarding-title">欢迎使用 AI+ Ops Admin</h2>
|
|
37
|
+
<p className="onboarding-subtitle">
|
|
38
|
+
这里是你的 AI 驱动开发环境,你可以做这些事:
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="onboarding-caps">
|
|
43
|
+
{CAPABILITIES.map((cap) => (
|
|
44
|
+
<div key={cap.title} className="onboarding-cap-card">
|
|
45
|
+
<div className="onboarding-cap-icon">{cap.icon}</div>
|
|
46
|
+
<div className="onboarding-cap-title">{cap.title}</div>
|
|
47
|
+
<div className="onboarding-cap-desc">{cap.desc}</div>
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="onboarding-footer">
|
|
53
|
+
<button className="add-data-btn-cancel" onClick={onDismiss}>
|
|
54
|
+
我已了解,跳过
|
|
55
|
+
</button>
|
|
56
|
+
<button
|
|
57
|
+
className="add-data-btn-confirm"
|
|
58
|
+
onClick={() => setStep(2)}
|
|
59
|
+
>
|
|
60
|
+
下一步:如何开始 →
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</>
|
|
64
|
+
) : (
|
|
65
|
+
<>
|
|
66
|
+
<div className="onboarding-step2-body">
|
|
67
|
+
<div className="onboarding-step2-icon">🚀</div>
|
|
68
|
+
<h3 className="onboarding-step2-title">开始旅程:添加数据表</h3>
|
|
69
|
+
<p className="onboarding-step2-desc">
|
|
70
|
+
数据表是一切的基础。描述你要管理什么业务数据,AI
|
|
71
|
+
会自动设计数据库结构,并同步生成完整的管理页面。
|
|
72
|
+
<br />
|
|
73
|
+
<br />
|
|
74
|
+
有了数据表之后,你就可以用 Inspector 点击页面元素进行定制, 或在
|
|
75
|
+
Canvas 上搭建可视化看板。
|
|
76
|
+
</p>
|
|
77
|
+
<div className="onboarding-step2-hint">
|
|
78
|
+
💡 之后随时可以通过右下角菜单「添加数据表」重新触发
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div className="onboarding-footer">
|
|
83
|
+
<button
|
|
84
|
+
className="add-data-btn-cancel"
|
|
85
|
+
onClick={() => setStep(1)}
|
|
86
|
+
>
|
|
87
|
+
← 返回
|
|
88
|
+
</button>
|
|
89
|
+
<button className="add-data-btn-cancel" onClick={onDismiss}>
|
|
90
|
+
先跳过
|
|
91
|
+
</button>
|
|
92
|
+
<button className="add-data-btn-confirm" onClick={onAddData}>
|
|
93
|
+
立即添加数据表
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -833,3 +833,132 @@ div.chat-header {
|
|
|
833
833
|
opacity: 1;
|
|
834
834
|
transform: translateY(-50%) scale(1);
|
|
835
835
|
}
|
|
836
|
+
|
|
837
|
+
/* ── Onboarding Modal ────────────────────────────────────────────────────── */
|
|
838
|
+
#onboarding-overlay {
|
|
839
|
+
position: fixed;
|
|
840
|
+
inset: 0;
|
|
841
|
+
z-index: 999999;
|
|
842
|
+
background: rgba(0, 0, 0, 0.5);
|
|
843
|
+
display: flex;
|
|
844
|
+
align-items: center;
|
|
845
|
+
justify-content: center;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
#onboarding-card {
|
|
849
|
+
background: #fff;
|
|
850
|
+
border-radius: 16px;
|
|
851
|
+
width: 600px;
|
|
852
|
+
max-width: calc(100vw - 32px);
|
|
853
|
+
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.22);
|
|
854
|
+
display: flex;
|
|
855
|
+
flex-direction: column;
|
|
856
|
+
overflow: hidden;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.onboarding-header {
|
|
860
|
+
padding: 32px 32px 20px;
|
|
861
|
+
text-align: center;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.onboarding-welcome-icon {
|
|
865
|
+
font-size: 36px;
|
|
866
|
+
line-height: 1;
|
|
867
|
+
margin-bottom: 12px;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.onboarding-title {
|
|
871
|
+
font-size: 18px;
|
|
872
|
+
font-weight: 700;
|
|
873
|
+
color: #1a1a1a;
|
|
874
|
+
margin: 0 0 8px;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.onboarding-subtitle {
|
|
878
|
+
font-size: 13px;
|
|
879
|
+
color: #8c8c8c;
|
|
880
|
+
margin: 0;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.onboarding-caps {
|
|
884
|
+
display: flex;
|
|
885
|
+
gap: 12px;
|
|
886
|
+
padding: 0 32px 24px;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.onboarding-cap-card {
|
|
890
|
+
flex: 1;
|
|
891
|
+
display: flex;
|
|
892
|
+
flex-direction: column;
|
|
893
|
+
align-items: center;
|
|
894
|
+
gap: 6px;
|
|
895
|
+
padding: 20px 12px;
|
|
896
|
+
border: 1.5px solid #f0f0f0;
|
|
897
|
+
border-radius: 12px;
|
|
898
|
+
background: #fafafa;
|
|
899
|
+
text-align: center;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.onboarding-cap-icon {
|
|
903
|
+
font-size: 28px;
|
|
904
|
+
line-height: 1;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.onboarding-cap-title {
|
|
908
|
+
font-size: 13px;
|
|
909
|
+
font-weight: 600;
|
|
910
|
+
color: #1a1a1a;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.onboarding-cap-desc {
|
|
914
|
+
font-size: 12px;
|
|
915
|
+
color: #8c8c8c;
|
|
916
|
+
line-height: 1.5;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
.onboarding-footer {
|
|
920
|
+
display: flex;
|
|
921
|
+
gap: 8px;
|
|
922
|
+
justify-content: flex-end;
|
|
923
|
+
padding: 16px 32px 24px;
|
|
924
|
+
border-top: 1px solid #f0f0f0;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/* Step 2 */
|
|
928
|
+
.onboarding-step2-body {
|
|
929
|
+
padding: 32px 40px 24px;
|
|
930
|
+
text-align: center;
|
|
931
|
+
display: flex;
|
|
932
|
+
flex-direction: column;
|
|
933
|
+
align-items: center;
|
|
934
|
+
gap: 12px;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.onboarding-step2-icon {
|
|
938
|
+
font-size: 44px;
|
|
939
|
+
line-height: 1;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.onboarding-step2-title {
|
|
943
|
+
font-size: 17px;
|
|
944
|
+
font-weight: 700;
|
|
945
|
+
color: #1a1a1a;
|
|
946
|
+
margin: 0;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.onboarding-step2-desc {
|
|
950
|
+
font-size: 13px;
|
|
951
|
+
color: #595959;
|
|
952
|
+
line-height: 1.7;
|
|
953
|
+
margin: 0;
|
|
954
|
+
max-width: 440px;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.onboarding-step2-hint {
|
|
958
|
+
font-size: 12px;
|
|
959
|
+
color: #8c8c8c;
|
|
960
|
+
background: #f5f5f5;
|
|
961
|
+
border-radius: 8px;
|
|
962
|
+
padding: 10px 16px;
|
|
963
|
+
margin-top: 4px;
|
|
964
|
+
}
|
|
@@ -42,30 +42,6 @@ export function isWoaDomain(): boolean {
|
|
|
42
42
|
return hostname === 'woa.com' || hostname.endsWith('.woa.com');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* 通过cookie检测是否登录及参数转cookie(for dev)
|
|
47
|
-
*/
|
|
48
|
-
export function checkLogin(): boolean {
|
|
49
|
-
// 优先处理 URL 参数中的 token,确保 token 被写入 cookie(任何域名下都需要)
|
|
50
|
-
const params = getUrlParams();
|
|
51
|
-
const gadminToken = params[GAdminTokenName];
|
|
52
|
-
if (gadminToken) {
|
|
53
|
-
setLoginCookie({ gadminToken });
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (Cookies.get(GAdminTokenName)) {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// woa.com 域名下 SmartGate 保证进入页面时已有登录态,无需跳转 Taihu
|
|
62
|
-
if (isWoaDomain()) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
45
|
export function setLoginCookie({ gadminToken }: Required<LoginCookie>) {
|
|
70
46
|
const expires = 1; // 本地登录态缓存1天
|
|
71
47
|
Cookies.set(GAdminTokenName, gadminToken, { expires });
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useUserPageAccess.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo, useState, useEffect, useRef, useCallback } from 'react';
|
|
2
2
|
import { customRequest } from 'helpers/http';
|
|
3
|
+
import { getUserInfoOnce } from 'authProvider';
|
|
3
4
|
import type { Role } from 'types/role';
|
|
4
5
|
|
|
5
6
|
const RoleCacheKey = 'oiterproles';
|
|
@@ -82,12 +83,12 @@ function writeCache(key: string, value: unknown): void {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export const useUserPageAccess = (): UseUserPageAccessResult => {
|
|
86
|
+
const [isIdentityLoading, setIsIdentityLoading] = useState(true);
|
|
87
|
+
|
|
85
88
|
// Role names — initialise from cache so first render is non-empty
|
|
86
89
|
const [roleNames, setRoleNames] = useState<string[]>(
|
|
87
90
|
() => readCache<string[]>(RoleCacheKey) ?? [],
|
|
88
91
|
);
|
|
89
|
-
// isRoleLoading only stays true while we wait for the live /userinfo response
|
|
90
|
-
const [isRoleLoading, setIsRoleLoading] = useState(true);
|
|
91
92
|
|
|
92
93
|
// Data states — initialise from cache for instant first render
|
|
93
94
|
const [rolesData, setRolesData] = useState<Role[] | null>(() =>
|
|
@@ -114,35 +115,23 @@ export const useUserPageAccess = (): UseUserPageAccessResult => {
|
|
|
114
115
|
}
|
|
115
116
|
}, []);
|
|
116
117
|
|
|
117
|
-
// Step 1:
|
|
118
|
+
// Step 1: 从 getUserInfoOnce 提取角色(复用缓存,不额外发请求)
|
|
118
119
|
useEffect(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
const roles = res?.configPerms?.roles;
|
|
120
|
+
getUserInfoOnce()
|
|
121
|
+
.then((identity) => {
|
|
122
|
+
const roles = identity?.configPerms?.roles;
|
|
123
123
|
if (roles && Array.isArray(roles)) {
|
|
124
124
|
updateRoles(roles as string[]);
|
|
125
|
-
}
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.error(
|
|
128
|
-
'[useUserPageAccess] Failed to fetch roles from backend:',
|
|
129
|
-
error,
|
|
130
|
-
);
|
|
131
|
-
// Fallback to sessionStorage if backend request fails
|
|
132
|
-
try {
|
|
125
|
+
} else {
|
|
133
126
|
const cached = readCache<string[]>(RoleCacheKey);
|
|
134
|
-
if (cached)
|
|
135
|
-
updateRoles(cached);
|
|
136
|
-
}
|
|
137
|
-
} catch (e) {
|
|
138
|
-
console.error('[useUserPageAccess] Failed to parse cached roles:', e);
|
|
127
|
+
if (cached) updateRoles(cached);
|
|
139
128
|
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
129
|
+
})
|
|
130
|
+
.catch(() => {
|
|
131
|
+
const cached = readCache<string[]>(RoleCacheKey);
|
|
132
|
+
if (cached) updateRoles(cached);
|
|
133
|
+
})
|
|
134
|
+
.finally(() => setIsIdentityLoading(false));
|
|
146
135
|
}, [updateRoles]);
|
|
147
136
|
|
|
148
137
|
// Step 2: Fetch all page definitions
|
|
@@ -331,7 +320,7 @@ export const useUserPageAccess = (): UseUserPageAccessResult => {
|
|
|
331
320
|
const isLoading = useMemo(() => {
|
|
332
321
|
// If cache was available on mount, never block rendering
|
|
333
322
|
if (hasCache && pagesData && rolePagesData) return false;
|
|
334
|
-
if (
|
|
323
|
+
if (isIdentityLoading) return true;
|
|
335
324
|
if (!pagesData) return true;
|
|
336
325
|
// 等待 /role/findMany 返回(roleNames 已拿到但 rolesData 还在路上)
|
|
337
326
|
if (roleNames.length > 0 && !rolesData) return true;
|
|
@@ -339,7 +328,7 @@ export const useUserPageAccess = (): UseUserPageAccessResult => {
|
|
|
339
328
|
if (roleIds.length > 0 && !rolePagesData) return true;
|
|
340
329
|
return false;
|
|
341
330
|
}, [
|
|
342
|
-
|
|
331
|
+
isIdentityLoading,
|
|
343
332
|
pagesData,
|
|
344
333
|
roleNames,
|
|
345
334
|
rolesData,
|
|
@@ -4,7 +4,13 @@ import { createRoot } from 'react-dom/client';
|
|
|
4
4
|
import App from './App';
|
|
5
5
|
import './i18n';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
login,
|
|
9
|
+
GAdminTokenName,
|
|
10
|
+
setLoginCookie,
|
|
11
|
+
getUrlParams,
|
|
12
|
+
} from './helpers/login';
|
|
13
|
+
import Cookies from 'js-cookie';
|
|
8
14
|
|
|
9
15
|
// 屏蔽 ECharts 在 RadarChart 多 Y 轴场景下的 alignTicks 噪音警告
|
|
10
16
|
const _warn = console.warn.bind(console);
|
|
@@ -13,7 +19,14 @@ console.warn = (...args: any[]) => {
|
|
|
13
19
|
_warn(...args);
|
|
14
20
|
};
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
// DEV 模式下支持 URL 参数传 token(跨域开发用)
|
|
23
|
+
if (import.meta.env.DEV) {
|
|
24
|
+
const params = getUrlParams();
|
|
25
|
+
const gadminToken = params[GAdminTokenName];
|
|
26
|
+
if (gadminToken) setLoginCookie({ gadminToken });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!Cookies.get(GAdminTokenName)) {
|
|
17
30
|
login();
|
|
18
31
|
} else {
|
|
19
32
|
const container = document.getElementById('root') as HTMLElement;
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
"emptyHint": "Drag components from the library",
|
|
160
160
|
"exitPreview": "Exit Preview",
|
|
161
161
|
"config": "Configure",
|
|
162
|
-
"configCharts": "Configure
|
|
162
|
+
"configCharts": "Configure switchable chart types",
|
|
163
163
|
"editCode": "Edit Code",
|
|
164
164
|
"duplicate": "Duplicate",
|
|
165
165
|
"delete": "Delete",
|
|
@@ -245,14 +245,14 @@
|
|
|
245
245
|
"changePageSize": "Change Page Size",
|
|
246
246
|
"removePagination": "Remove Pagination",
|
|
247
247
|
"addPagination": "Add Pagination",
|
|
248
|
-
"direction": "Direction",
|
|
248
|
+
"direction": "Chart Direction",
|
|
249
249
|
"vertical": "Vertical",
|
|
250
250
|
"horizontal": "Horizontal",
|
|
251
251
|
"hideGrid": "Hide Grid",
|
|
252
252
|
"showGrid": "Show Grid",
|
|
253
253
|
"hideLabel": "Hide Labels",
|
|
254
254
|
"showLabel": "Show Labels",
|
|
255
|
-
"switchVariant": "Switch Style",
|
|
255
|
+
"switchVariant": "Switch Display Style",
|
|
256
256
|
"pie": "Pie",
|
|
257
257
|
"ring": "Ring",
|
|
258
258
|
"topN": "Top N Items",
|
|
@@ -262,22 +262,23 @@
|
|
|
262
262
|
"showDots": "Show Dots",
|
|
263
263
|
"hideDotsLabel": "Hide Dot Values",
|
|
264
264
|
"showDotsLabel": "Show Dot Values",
|
|
265
|
-
"disableStacked": "Disable Stacking",
|
|
266
|
-
"enableStacked": "Enable Stacking",
|
|
267
|
-
"disablePercent": "Disable Percent",
|
|
268
|
-
"enablePercent": "Enable Percent",
|
|
265
|
+
"disableStacked": "Disable Data Stacking",
|
|
266
|
+
"enableStacked": "Enable Data Stacking",
|
|
267
|
+
"disablePercent": "Disable Percent Stacking",
|
|
268
|
+
"enablePercent": "Enable Percent Stacking",
|
|
269
269
|
"disableArea": "Disable Area Fill",
|
|
270
270
|
"enableArea": "Enable Area Fill",
|
|
271
271
|
"disableSmooth": "Disable Smooth",
|
|
272
272
|
"enableSmooth": "Enable Smooth",
|
|
273
|
-
"switchTo": "Switch
|
|
273
|
+
"switchTo": "Switch Chart Type",
|
|
274
274
|
"showDownload": "Show Download",
|
|
275
275
|
"hideDownload": "Hide Download",
|
|
276
276
|
"enableSorting": "Enable Sorting",
|
|
277
277
|
"disableSorting": "Disable Sorting",
|
|
278
278
|
"enableFiltering": "Enable Filtering",
|
|
279
279
|
"disableFiltering": "Disable Filtering",
|
|
280
|
-
"configColumns": "Configure Columns"
|
|
280
|
+
"configColumns": "Configure Columns",
|
|
281
|
+
"visibilityGroup": "Display Elements"
|
|
281
282
|
},
|
|
282
283
|
"modal": {
|
|
283
284
|
"columnConfigTitle": "Column Settings",
|