@gadmin2n/schematics 0.0.65 → 0.0.66
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/prisma/system.prisma +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +4 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/authProvider.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +419 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/business/index.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/http.ts +28 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/http.ts +87 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/index.tsx +6 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -77
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/utils.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +0 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +110 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/{permission-readme → permissionReadme}/index.tsx +4 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +0 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +32 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +1 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +52 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +1 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +41 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/yarn.lock +8321 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/utils.ts +0 -76
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/create.tsx +0 -113
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/edit.tsx +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/show.tsx +0 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/create.tsx +0 -113
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/edit.tsx +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/index.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/list.tsx +0 -243
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/show.tsx +0 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/create.tsx +0 -113
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/edit.tsx +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/show.tsx +0 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/create.tsx +0 -113
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/edit.tsx +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/show.tsx +0 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/create.tsx +0 -113
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/edit.tsx +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/index.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/list.tsx +0 -243
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/show.tsx +0 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/create.tsx +0 -113
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/edit.tsx +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/index.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/list.tsx +0 -243
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/show.tsx +0 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/create.tsx +0 -113
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/edit.tsx +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/show.tsx +0 -61
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/index.ts +0 -2
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/get-name-initials.ts +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/get-random-color.ts +0 -0
|
@@ -6,6 +6,7 @@ model Game {
|
|
|
6
6
|
@@map("t_admin_game")
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/// @IgnoreReactCode
|
|
9
10
|
/// @IgnoreAutoField
|
|
10
11
|
model User {
|
|
11
12
|
id Int @id @default(autoincrement())
|
|
@@ -45,6 +46,7 @@ model Audit {
|
|
|
45
46
|
@@map("t_admin_audit_log")
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/// @IgnoreReactCode
|
|
48
50
|
/// @IgnoreAutoField
|
|
49
51
|
model Resource {
|
|
50
52
|
id Int @id @default(autoincrement())
|
|
@@ -62,6 +64,7 @@ model Resource {
|
|
|
62
64
|
@@map("t_resource")
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
/// @IgnoreReactCode
|
|
65
68
|
/// @IgnoreAutoField
|
|
66
69
|
model Role {
|
|
67
70
|
id Int @id @default(autoincrement())
|
|
@@ -81,6 +84,7 @@ model Role {
|
|
|
81
84
|
@@map("t_role")
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/// @IgnoreReactCode
|
|
84
88
|
/// @IgnoreAutoField
|
|
85
89
|
model Page {
|
|
86
90
|
id Int @id @default(autoincrement())
|
|
@@ -108,6 +112,7 @@ model Page {
|
|
|
108
112
|
@@map("t_page")
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
/// @IgnoreReactCode
|
|
111
116
|
/// @IgnoreAutoField
|
|
112
117
|
model RoleResource {
|
|
113
118
|
id Int @id @default(autoincrement())
|
|
@@ -127,6 +132,7 @@ model RoleResource {
|
|
|
127
132
|
@@map("t_role_resource")
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
/// @IgnoreReactCode
|
|
130
136
|
/// @IgnoreAutoField
|
|
131
137
|
model PageResource {
|
|
132
138
|
id Int @id @default(autoincrement())
|
|
@@ -145,6 +151,7 @@ model PageResource {
|
|
|
145
151
|
@@map("t_page_resource")
|
|
146
152
|
}
|
|
147
153
|
|
|
154
|
+
/// @IgnoreReactCode
|
|
148
155
|
/// @IgnoreAutoField
|
|
149
156
|
model RolePages {
|
|
150
157
|
id Int @id @default(autoincrement())
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@azure/identity": "^4.13.0",
|
|
38
|
-
"@gadmin2n/nest-common": "^0.0.
|
|
38
|
+
"@gadmin2n/nest-common": "^0.0.37",
|
|
39
39
|
"@nestjs/cache-manager": "^3.0.1",
|
|
40
40
|
"@nestjs/common": "^10.4.15",
|
|
41
41
|
"@nestjs/config": "^3.2.0",
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"xlsx": "^0.18.5"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
|
-
"@gadmin2n/prisma-nest-generator": "^0.0.
|
|
88
|
-
"@gadmin2n/prisma-react-generator": "^0.0.
|
|
87
|
+
"@gadmin2n/prisma-nest-generator": "^0.0.31",
|
|
88
|
+
"@gadmin2n/prisma-react-generator": "^0.0.47",
|
|
89
89
|
"@nestjs/testing": "^10.4.15",
|
|
90
90
|
"@types/cookie-parser": "^1.4.3",
|
|
91
91
|
"@types/express": "^4.17.21",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
11
11
|
"@dnd-kit/sortable": "^7.0.2",
|
|
12
12
|
"@dnd-kit/utilities": "^3.2.2",
|
|
13
|
-
"@gadmin2n/react-common": "^0.0.
|
|
13
|
+
"@gadmin2n/react-common": "^0.0.57",
|
|
14
14
|
"@refinedev/antd": "^5.47.0",
|
|
15
15
|
"@refinedev/cli": "^2.16.51",
|
|
16
16
|
"@refinedev/core": "^4.58.0",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
"@types/react-dom": "^18.0.6",
|
|
56
56
|
"@vitejs/plugin-react": "^4.3.4",
|
|
57
57
|
"autoprefixer": "^10.4.24",
|
|
58
|
-
"postcss": "^8.5.6",
|
|
59
|
-
"postcss-nesting": "^14.0.0",
|
|
60
|
-
"typescript": "^5.4.2",
|
|
61
58
|
"husky": "^8.0.0",
|
|
62
59
|
"lint-staged": "^13.0.0",
|
|
60
|
+
"postcss": "^8.5.6",
|
|
61
|
+
"postcss-nesting": "^14.0.0",
|
|
63
62
|
"prettier": "^3.0.0",
|
|
63
|
+
"typescript": "^5.4.2",
|
|
64
64
|
"vite": "^6.2.0",
|
|
65
65
|
"vite-tsconfig-paths": "^5.1.4"
|
|
66
66
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthBindings } from '@refinedev/core';
|
|
2
|
-
import { getApiUrl } from '
|
|
3
|
-
import { requestHeaders } from 'helpers
|
|
2
|
+
import { getApiUrl } from 'config/http';
|
|
3
|
+
import { requestHeaders } from 'helpers';
|
|
4
4
|
import memoize from 'lodash.memoize';
|
|
5
5
|
|
|
6
6
|
const getUserInfoOnce = memoize(async () => {
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Modal, Button, Typography, message, Tooltip } from "antd";
|
|
3
|
+
import { CopyOutlined, CheckOutlined } from "@ant-design/icons";
|
|
4
|
+
|
|
5
|
+
const { Text } = Typography;
|
|
6
|
+
|
|
7
|
+
interface SqlModalProps {
|
|
8
|
+
open: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
title: string;
|
|
11
|
+
sql: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const SqlModal: React.FC<SqlModalProps> = ({ open, onClose, title, sql }) => {
|
|
15
|
+
const [copied, setCopied] = useState(false);
|
|
16
|
+
|
|
17
|
+
const handleCopy = () => {
|
|
18
|
+
navigator.clipboard.writeText(sql).then(() => {
|
|
19
|
+
setCopied(true);
|
|
20
|
+
message.success("SQL copied to clipboard");
|
|
21
|
+
setTimeout(() => setCopied(false), 2000);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Modal
|
|
27
|
+
title={title}
|
|
28
|
+
open={open}
|
|
29
|
+
onCancel={onClose}
|
|
30
|
+
width={720}
|
|
31
|
+
footer={[
|
|
32
|
+
<Tooltip key="copy" title={copied ? "Copied!" : "Copy SQL"}>
|
|
33
|
+
<Button
|
|
34
|
+
icon={copied ? <CheckOutlined /> : <CopyOutlined />}
|
|
35
|
+
type="primary"
|
|
36
|
+
onClick={handleCopy}
|
|
37
|
+
>
|
|
38
|
+
{copied ? "Copied!" : "Copy SQL"}
|
|
39
|
+
</Button>
|
|
40
|
+
</Tooltip>,
|
|
41
|
+
<Button key="close" onClick={onClose}>
|
|
42
|
+
Close
|
|
43
|
+
</Button>,
|
|
44
|
+
]}
|
|
45
|
+
>
|
|
46
|
+
<pre
|
|
47
|
+
style={{
|
|
48
|
+
background: "#1e1e1e",
|
|
49
|
+
color: "#d4d4d4",
|
|
50
|
+
padding: "16px",
|
|
51
|
+
borderRadius: "6px",
|
|
52
|
+
overflowX: "auto",
|
|
53
|
+
overflowY: "auto",
|
|
54
|
+
maxHeight: "500px",
|
|
55
|
+
fontSize: "12px",
|
|
56
|
+
lineHeight: "1.6",
|
|
57
|
+
whiteSpace: "pre-wrap",
|
|
58
|
+
wordBreak: "break-word",
|
|
59
|
+
fontFamily: "'Fira Code', 'Cascadia Code', 'Consolas', 'Monaco', monospace",
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<Text
|
|
63
|
+
style={{
|
|
64
|
+
color: "inherit",
|
|
65
|
+
fontSize: "inherit",
|
|
66
|
+
fontFamily: "inherit",
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{sql}
|
|
70
|
+
</Text>
|
|
71
|
+
</pre>
|
|
72
|
+
</Modal>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ─── SQL Generators ────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
function escapeStr(val: string | null | undefined): string {
|
|
79
|
+
if (val == null) return "NULL";
|
|
80
|
+
return `'${String(val).replace(/'/g, "''")}'`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function jsonArr(val: string | null | undefined): string {
|
|
84
|
+
// roles is stored as a JSON-encoded array string in the DB, e.g. '["GUEST","PRODUCT"]'
|
|
85
|
+
if (val == null) return "'{}'";
|
|
86
|
+
return `'${String(val).replace(/'/g, "''")}'`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generate an INSERT for t_admin_audit_log.
|
|
91
|
+
* authorId / authorName come from the currently logged-in user passed by the caller.
|
|
92
|
+
*/
|
|
93
|
+
function auditInsert(opts: {
|
|
94
|
+
action: string;
|
|
95
|
+
resource: string;
|
|
96
|
+
resourceId: string;
|
|
97
|
+
data: string; // JSON string of the record
|
|
98
|
+
creator?: string | null;
|
|
99
|
+
authorId?: string | null;
|
|
100
|
+
authorName?: string | null;
|
|
101
|
+
}): string {
|
|
102
|
+
const dataJson = escapeStr(opts.data);
|
|
103
|
+
const authorId = opts.authorId ?? "<author_id>";
|
|
104
|
+
const authorName = opts.authorName ?? "<author_name>";
|
|
105
|
+
return `INSERT INTO public.t_admin_audit_log (creator, "authorId", "authorName", action, resource, "resourceId", data, "previousData")
|
|
106
|
+
VALUES (
|
|
107
|
+
${escapeStr(authorId)},
|
|
108
|
+
${escapeStr(authorId)},
|
|
109
|
+
${escapeStr(authorName)},
|
|
110
|
+
${escapeStr(opts.action)},
|
|
111
|
+
${escapeStr(opts.resource)},
|
|
112
|
+
${escapeStr(opts.resourceId)},
|
|
113
|
+
${dataJson},
|
|
114
|
+
NULL
|
|
115
|
+
);`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate INSERT SQL for a User record.
|
|
120
|
+
* Table: t_admin_user_role
|
|
121
|
+
*/
|
|
122
|
+
export function generateUserSql(user: {
|
|
123
|
+
userid: string;
|
|
124
|
+
username?: string | null;
|
|
125
|
+
roles?: string | null;
|
|
126
|
+
isApiKey?: boolean;
|
|
127
|
+
apiKey?: string | null;
|
|
128
|
+
description?: string | null;
|
|
129
|
+
isActive?: boolean;
|
|
130
|
+
creator?: string | null;
|
|
131
|
+
}, operator?: { authorId?: string | null; authorName?: string | null }): string {
|
|
132
|
+
const rolesValue = user.roles ?? "[]";
|
|
133
|
+
const isApiKey = user.isApiKey ? "true" : "false";
|
|
134
|
+
const isActive = user.isActive !== false ? "true" : "false";
|
|
135
|
+
|
|
136
|
+
const userDataJson = JSON.stringify({
|
|
137
|
+
userid: user.userid,
|
|
138
|
+
username: user.username ?? null,
|
|
139
|
+
roles: rolesValue,
|
|
140
|
+
isApiKey: user.isApiKey ?? false,
|
|
141
|
+
description: user.description ?? null,
|
|
142
|
+
isActive: user.isActive !== false,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const mainSql = `-- Insert User: ${user.userid}
|
|
146
|
+
INSERT INTO public.t_admin_user_role (userid, username, roles, "isApiKey", "apiKey", description, "isActive", creator, "createdAt", "updatedAt")
|
|
147
|
+
VALUES (
|
|
148
|
+
${escapeStr(user.userid)},
|
|
149
|
+
${escapeStr(user.username)},
|
|
150
|
+
${jsonArr(rolesValue)},
|
|
151
|
+
${isApiKey},
|
|
152
|
+
${user.apiKey ? escapeStr(user.apiKey) : "NULL"},
|
|
153
|
+
${escapeStr(user.description)},
|
|
154
|
+
${isActive},
|
|
155
|
+
${escapeStr(user.creator)},
|
|
156
|
+
NOW(),
|
|
157
|
+
NOW()
|
|
158
|
+
)
|
|
159
|
+
ON CONFLICT (userid) DO UPDATE SET
|
|
160
|
+
username = EXCLUDED.username,
|
|
161
|
+
roles = EXCLUDED.roles,
|
|
162
|
+
"isApiKey" = EXCLUDED."isApiKey",
|
|
163
|
+
"apiKey" = EXCLUDED."apiKey",
|
|
164
|
+
description = EXCLUDED.description,
|
|
165
|
+
"isActive" = EXCLUDED."isActive",
|
|
166
|
+
"updatedAt" = NOW();`;
|
|
167
|
+
|
|
168
|
+
return [
|
|
169
|
+
mainSql,
|
|
170
|
+
"",
|
|
171
|
+
auditInsert({ action: "create", resource: "user", resourceId: user.userid, data: userDataJson, creator: user.creator, ...operator }),
|
|
172
|
+
].join("\n");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate INSERT SQL for a Resource record.
|
|
177
|
+
* Table: t_resource
|
|
178
|
+
*/
|
|
179
|
+
export function generateResourceSql(resource: {
|
|
180
|
+
code: string;
|
|
181
|
+
name: string;
|
|
182
|
+
description?: string | null;
|
|
183
|
+
isCommonResource?: boolean;
|
|
184
|
+
creator?: string | null;
|
|
185
|
+
}, operator?: { authorId?: string | null; authorName?: string | null }): string {
|
|
186
|
+
const isCommon = resource.isCommonResource ? "true" : "false";
|
|
187
|
+
|
|
188
|
+
const resourceDataJson = JSON.stringify({
|
|
189
|
+
code: resource.code,
|
|
190
|
+
name: resource.name,
|
|
191
|
+
description: resource.description ?? null,
|
|
192
|
+
isCommonResource: resource.isCommonResource ?? false,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const mainSql = `-- Insert Resource: ${resource.code}
|
|
196
|
+
INSERT INTO public.t_resource (code, name, description, is_common_resource, creator, "createdAt", "updatedAt")
|
|
197
|
+
VALUES (
|
|
198
|
+
${escapeStr(resource.code)},
|
|
199
|
+
${escapeStr(resource.name)},
|
|
200
|
+
${escapeStr(resource.description)},
|
|
201
|
+
${isCommon},
|
|
202
|
+
${escapeStr(resource.creator)},
|
|
203
|
+
NOW(),
|
|
204
|
+
NOW()
|
|
205
|
+
)
|
|
206
|
+
ON CONFLICT (code) DO UPDATE SET
|
|
207
|
+
name = EXCLUDED.name,
|
|
208
|
+
description = EXCLUDED.description,
|
|
209
|
+
is_common_resource = EXCLUDED.is_common_resource,
|
|
210
|
+
"updatedAt" = NOW();`;
|
|
211
|
+
|
|
212
|
+
return [
|
|
213
|
+
mainSql,
|
|
214
|
+
"",
|
|
215
|
+
auditInsert({ action: "create", resource: "resource", resourceId: resource.code, data: resourceDataJson, creator: resource.creator, ...operator }),
|
|
216
|
+
].join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate INSERT SQL for a Role record, including its page assignments and
|
|
221
|
+
* the resulting role_resource entries.
|
|
222
|
+
*
|
|
223
|
+
* rolePages: list of { pageId, pageName }
|
|
224
|
+
* roleResources: list of { resourceCode, resourceName, actions: string[] }
|
|
225
|
+
*/
|
|
226
|
+
export function generateRoleSql(
|
|
227
|
+
role: {
|
|
228
|
+
name: string;
|
|
229
|
+
description?: string | null;
|
|
230
|
+
creator?: string | null;
|
|
231
|
+
},
|
|
232
|
+
rolePages: Array<{ pageId: number; pageName: string; pageCode: string }>,
|
|
233
|
+
roleResources: Array<{ resourceCode: string; resourceName: string; actions: string[] }>,
|
|
234
|
+
operator?: { authorId?: string | null; authorName?: string | null },
|
|
235
|
+
): string {
|
|
236
|
+
const lines: string[] = [];
|
|
237
|
+
|
|
238
|
+
// 1. Role
|
|
239
|
+
lines.push(`-- ════════════════════════════════════════════════════════`);
|
|
240
|
+
lines.push(`-- Role: ${role.name}`);
|
|
241
|
+
lines.push(`-- ════════════════════════════════════════════════════════`);
|
|
242
|
+
lines.push(`INSERT INTO public.t_role (name, description, creator, "createdAt", "updatedAt")`);
|
|
243
|
+
lines.push(`VALUES (`);
|
|
244
|
+
lines.push(` ${escapeStr(role.name)},`);
|
|
245
|
+
lines.push(` ${escapeStr(role.description)},`);
|
|
246
|
+
lines.push(` ${escapeStr(role.creator)},`);
|
|
247
|
+
lines.push(` NOW(),`);
|
|
248
|
+
lines.push(` NOW()`);
|
|
249
|
+
lines.push(`)`);
|
|
250
|
+
lines.push(`ON CONFLICT (name) DO UPDATE SET`);
|
|
251
|
+
lines.push(` description = EXCLUDED.description,`);
|
|
252
|
+
lines.push(` "updatedAt" = NOW();`);
|
|
253
|
+
|
|
254
|
+
// 2. Role-page associations
|
|
255
|
+
if (rolePages.length > 0) {
|
|
256
|
+
lines.push(``);
|
|
257
|
+
lines.push(`-- Role-Page Associations for "${role.name}"`);
|
|
258
|
+
lines.push(`-- Pages: ${rolePages.map(p => p.pageName).join(", ")}`);
|
|
259
|
+
|
|
260
|
+
for (const rp of rolePages) {
|
|
261
|
+
lines.push(``);
|
|
262
|
+
lines.push(`INSERT INTO public.t_role_pages (role_id, page_id, "created_at", "updated_at")`);
|
|
263
|
+
lines.push(`SELECT r.id, p.id, NOW(), NOW()`);
|
|
264
|
+
lines.push(`FROM public.t_role r, public.t_page p`);
|
|
265
|
+
lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
|
|
266
|
+
lines.push(` AND p.code = ${escapeStr(rp.pageCode)} -- ${rp.pageName}`);
|
|
267
|
+
lines.push(`ON CONFLICT (role_id, page_id) DO NOTHING;`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 3. Role-resource associations
|
|
272
|
+
if (roleResources.length > 0) {
|
|
273
|
+
lines.push(``);
|
|
274
|
+
lines.push(`-- Role-Resource Permissions for "${role.name}"`);
|
|
275
|
+
|
|
276
|
+
for (const rr of roleResources) {
|
|
277
|
+
const actionsJson = JSON.stringify(rr.actions);
|
|
278
|
+
lines.push(``);
|
|
279
|
+
lines.push(`INSERT INTO public.t_role_resource (role_id, resource_id, actions, "created_at", "updated_at")`);
|
|
280
|
+
lines.push(`SELECT r.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`);
|
|
281
|
+
lines.push(`FROM public.t_role r, public.t_resource res`);
|
|
282
|
+
lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
|
|
283
|
+
lines.push(` AND res.code = ${escapeStr(rr.resourceCode)} -- ${rr.resourceName}`);
|
|
284
|
+
lines.push(`ON CONFLICT (role_id, resource_id) DO UPDATE SET`);
|
|
285
|
+
lines.push(` actions = EXCLUDED.actions,`);
|
|
286
|
+
lines.push(` "updated_at" = NOW();`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 4. Audit log
|
|
291
|
+
const roleDataJson = JSON.stringify({
|
|
292
|
+
name: role.name,
|
|
293
|
+
description: role.description ?? null,
|
|
294
|
+
});
|
|
295
|
+
lines.push(``);
|
|
296
|
+
lines.push(auditInsert({ action: "create", resource: "role", resourceId: role.name, data: roleDataJson, creator: role.creator, ...operator }));
|
|
297
|
+
|
|
298
|
+
return lines.join("\n");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Generate INSERT SQL for a Page and (optionally) its children.
|
|
303
|
+
*
|
|
304
|
+
* When called on a parent page, pass all child pages in `children`.
|
|
305
|
+
* Each page includes its pageResources (resource assignments).
|
|
306
|
+
*
|
|
307
|
+
* pageResources: list of { resourceCode, resourceName, actions }
|
|
308
|
+
*/
|
|
309
|
+
export function generatePageSql(
|
|
310
|
+
page: {
|
|
311
|
+
id: number;
|
|
312
|
+
code: string;
|
|
313
|
+
name: string;
|
|
314
|
+
path?: string | null;
|
|
315
|
+
description?: string | null;
|
|
316
|
+
zhName?: string | null;
|
|
317
|
+
enName?: string | null;
|
|
318
|
+
sortOrder?: number | null;
|
|
319
|
+
parentId?: number | null;
|
|
320
|
+
/** code of the parent page — used to generate a portable subquery instead of a raw numeric ID */
|
|
321
|
+
parentCode?: string | null;
|
|
322
|
+
isVirtual?: boolean;
|
|
323
|
+
creator?: string | null;
|
|
324
|
+
},
|
|
325
|
+
pageResources: Array<{ resourceCode: string; resourceName: string; actions: string[] }>,
|
|
326
|
+
children: Array<{
|
|
327
|
+
page: typeof page;
|
|
328
|
+
pageResources: Array<{ resourceCode: string; resourceName: string; actions: string[] }>;
|
|
329
|
+
}>,
|
|
330
|
+
operator?: { authorId?: string | null; authorName?: string | null },
|
|
331
|
+
): string {
|
|
332
|
+
const lines: string[] = [];
|
|
333
|
+
|
|
334
|
+
const renderPageInsert = (
|
|
335
|
+
p: typeof page,
|
|
336
|
+
res: Array<{ resourceCode: string; resourceName: string; actions: string[] }>,
|
|
337
|
+
parentRef: string | null,
|
|
338
|
+
) => {
|
|
339
|
+
lines.push(`-- ────────────────────────────────────────────────────────`);
|
|
340
|
+
lines.push(`-- Page: ${p.name} (code: ${p.code})`);
|
|
341
|
+
lines.push(`-- ────────────────────────────────────────────────────────`);
|
|
342
|
+
lines.push(`INSERT INTO public.t_page (`);
|
|
343
|
+
lines.push(` code, name, path, description, zh_name, en_name, sort_order, parent_id, "isVirtual", creator, created_at, updated_at`);
|
|
344
|
+
lines.push(`)`);
|
|
345
|
+
lines.push(`VALUES (`);
|
|
346
|
+
lines.push(` ${escapeStr(p.code)},`);
|
|
347
|
+
lines.push(` ${escapeStr(p.name)},`);
|
|
348
|
+
lines.push(` ${escapeStr(p.path)},`);
|
|
349
|
+
lines.push(` ${escapeStr(p.description)},`);
|
|
350
|
+
lines.push(` ${escapeStr(p.zhName)},`);
|
|
351
|
+
lines.push(` ${escapeStr(p.enName)},`);
|
|
352
|
+
lines.push(` ${p.sortOrder != null ? p.sortOrder : "NULL"},`);
|
|
353
|
+
if (parentRef) {
|
|
354
|
+
lines.push(` (${parentRef}), -- parent_id resolved by code`);
|
|
355
|
+
} else {
|
|
356
|
+
lines.push(` NULL,`);
|
|
357
|
+
}
|
|
358
|
+
lines.push(` ${p.isVirtual ? "true" : "false"},`);
|
|
359
|
+
lines.push(` ${escapeStr(p.creator)},`);
|
|
360
|
+
lines.push(` NOW(),`);
|
|
361
|
+
lines.push(` NOW()`);
|
|
362
|
+
lines.push(`)`);
|
|
363
|
+
lines.push(`ON CONFLICT (code) DO UPDATE SET`);
|
|
364
|
+
lines.push(` name = EXCLUDED.name,`);
|
|
365
|
+
lines.push(` path = EXCLUDED.path,`);
|
|
366
|
+
lines.push(` description = EXCLUDED.description,`);
|
|
367
|
+
lines.push(` zh_name = EXCLUDED.zh_name,`);
|
|
368
|
+
lines.push(` en_name = EXCLUDED.en_name,`);
|
|
369
|
+
lines.push(` sort_order = EXCLUDED.sort_order,`);
|
|
370
|
+
lines.push(` parent_id = EXCLUDED.parent_id,`);
|
|
371
|
+
lines.push(` updated_at = NOW();`);
|
|
372
|
+
|
|
373
|
+
if (res.length > 0) {
|
|
374
|
+
lines.push(``);
|
|
375
|
+
lines.push(`-- Page-Resource Associations for "${p.name}"`);
|
|
376
|
+
for (const pr of res) {
|
|
377
|
+
const actionsJson = JSON.stringify(pr.actions);
|
|
378
|
+
lines.push(``);
|
|
379
|
+
lines.push(`INSERT INTO public.t_page_resource (page_id, resource_id, actions, "created_at", "updated_at")`);
|
|
380
|
+
lines.push(`SELECT pg.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`);
|
|
381
|
+
lines.push(`FROM public.t_page pg, public.t_resource res`);
|
|
382
|
+
lines.push(`WHERE pg.code = ${escapeStr(p.code)}`);
|
|
383
|
+
lines.push(` AND res.code = ${escapeStr(pr.resourceCode)} -- ${pr.resourceName}`);
|
|
384
|
+
lines.push(`ON CONFLICT (page_id, resource_id) DO UPDATE SET`);
|
|
385
|
+
lines.push(` actions = EXCLUDED.actions,`);
|
|
386
|
+
lines.push(` "updated_at" = NOW();`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Audit log for this page
|
|
391
|
+
const pageDataJson = JSON.stringify({
|
|
392
|
+
code: p.code,
|
|
393
|
+
name: p.name,
|
|
394
|
+
path: p.path ?? null,
|
|
395
|
+
description: p.description ?? null,
|
|
396
|
+
zhName: p.zhName ?? null,
|
|
397
|
+
enName: p.enName ?? null,
|
|
398
|
+
sortOrder: p.sortOrder ?? null,
|
|
399
|
+
isVirtual: p.isVirtual ?? false,
|
|
400
|
+
});
|
|
401
|
+
lines.push(``);
|
|
402
|
+
lines.push(auditInsert({ action: "create", resource: "page", resourceId: p.code, data: pageDataJson, creator: p.creator, ...operator }));
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// Parent page — if it has a parentCode, resolve parent_id dynamically by code
|
|
406
|
+
const rootParentRef = page.parentCode
|
|
407
|
+
? `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.parentCode)}`
|
|
408
|
+
: null;
|
|
409
|
+
renderPageInsert(page, pageResources, rootParentRef);
|
|
410
|
+
|
|
411
|
+
// Children
|
|
412
|
+
for (const child of children) {
|
|
413
|
+
lines.push(``);
|
|
414
|
+
const parentRef = `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.code)}`;
|
|
415
|
+
renderPageInsert(child.page, child.pageResources, parentRef);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return lines.join("\n");
|
|
419
|
+
}
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import React, { memo } from "react";
|
|
|
3
3
|
import type { AvatarProps } from "antd";
|
|
4
4
|
import { Avatar as AntdAvatar } from "antd";
|
|
5
5
|
|
|
6
|
-
import { getNameInitials, getRandomColorFromString } from "
|
|
6
|
+
import { getNameInitials, getRandomColorFromString } from "helpers";
|
|
7
7
|
|
|
8
8
|
type Props = AvatarProps & {
|
|
9
9
|
name?: string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ─── URL / 部署配置 ───────────────────────────────────────────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* 读取部署子路径(Vite env: VITE_DEPLOY_NAME)
|
|
4
|
+
* 本地开发不设置时返回空字符串
|
|
5
|
+
*/
|
|
6
|
+
export function getUrlBasename(): string {
|
|
7
|
+
if (
|
|
8
|
+
typeof import.meta !== "undefined" &&
|
|
9
|
+
import.meta.env?.VITE_DEPLOY_NAME?.trim()
|
|
10
|
+
) {
|
|
11
|
+
return `/${import.meta.env.VITE_DEPLOY_NAME.trim()}`;
|
|
12
|
+
}
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 读取后端 API 根路径(Vite env: VITE_API_URL 优先)
|
|
18
|
+
* 本地开发时自动将前端 3000 端口映射到后端 8000 端口
|
|
19
|
+
*/
|
|
20
|
+
export function getApiUrl(): string {
|
|
21
|
+
if (
|
|
22
|
+
typeof import.meta !== "undefined" &&
|
|
23
|
+
import.meta.env?.VITE_API_URL?.trim()
|
|
24
|
+
) {
|
|
25
|
+
return import.meta.env.VITE_API_URL.trim();
|
|
26
|
+
}
|
|
27
|
+
return `${window.location.origin.replace("3000", "8000")}${getUrlBasename()}/api`;
|
|
28
|
+
}
|
|
@@ -110,7 +110,7 @@ export const routeRegistry: Record<string, RouteEntry> = {
|
|
|
110
110
|
permission_readme: {
|
|
111
111
|
path: "readme",
|
|
112
112
|
component: lazy(() =>
|
|
113
|
-
import("routes/
|
|
113
|
+
import("routes/permissionReadme").then((m) => ({
|
|
114
114
|
default: m.PermissionReadmePage,
|
|
115
115
|
})),
|
|
116
116
|
),
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import {
|
|
3
|
+
GadminCrud as gadminDataProvider,
|
|
4
|
+
applyErrorInterceptor,
|
|
5
|
+
customRequest as baseCustomRequest,
|
|
6
|
+
} from "@gadmin2n/react-common";
|
|
7
|
+
import { login, requestHeaders } from "./login";
|
|
8
|
+
import { getApiUrl } from "config/http";
|
|
9
|
+
// Re-export Prisma helpers from react-common for backward compatibility
|
|
10
|
+
export { convertPrismaDecimal, isPrismaDecimal } from "@gadmin2n/react-common";
|
|
11
|
+
|
|
12
|
+
const apiUrl = getApiUrl();
|
|
13
|
+
|
|
14
|
+
const axiosInstance = axios.create();
|
|
15
|
+
|
|
16
|
+
// 注入 auth header
|
|
17
|
+
axiosInstance.interceptors.request.use(
|
|
18
|
+
(config) => {
|
|
19
|
+
if (config.headers) {
|
|
20
|
+
Object.assign(config.headers, requestHeaders());
|
|
21
|
+
}
|
|
22
|
+
return config;
|
|
23
|
+
},
|
|
24
|
+
(error) => Promise.reject(error)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// 处理 401 跳转登录、403 跳权限页(app 层特有逻辑,须在 applyErrorInterceptor 之前注册)
|
|
28
|
+
axiosInstance.interceptors.response.use(
|
|
29
|
+
(response) => response,
|
|
30
|
+
(error) => {
|
|
31
|
+
if (error?.response?.status === 401) {
|
|
32
|
+
console.log("login expired");
|
|
33
|
+
login();
|
|
34
|
+
// 返回pending状态的promise,以免后续代码出错
|
|
35
|
+
return new Promise(() => {});
|
|
36
|
+
}
|
|
37
|
+
if (error?.response?.status === 403) {
|
|
38
|
+
// 已经在 /403 页面则不再跳转,防止死循环
|
|
39
|
+
if (!window.location.pathname.endsWith("/403")) {
|
|
40
|
+
window.location.href = "/403";
|
|
41
|
+
}
|
|
42
|
+
return new Promise(() => {});
|
|
43
|
+
}
|
|
44
|
+
return Promise.reject(error);
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// 统一错误格式化(复用公共库逻辑,消除重复代码)
|
|
49
|
+
applyErrorInterceptor(axiosInstance);
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export const gadminCrudProvider = gadminDataProvider(apiUrl, axiosInstance);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 直接用 fetch 发 findMany 请求(绕开 refine DataProvider,适合非 React 上下文)
|
|
57
|
+
*/
|
|
58
|
+
export async function findMany<T>(
|
|
59
|
+
resource: string,
|
|
60
|
+
params: Record<string, any>
|
|
61
|
+
): Promise<T[]> {
|
|
62
|
+
const url = `${apiUrl}/${resource}/findMany`;
|
|
63
|
+
const res = await fetch(url, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
...requestHeaders(),
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify(params),
|
|
70
|
+
});
|
|
71
|
+
const data = await res.json();
|
|
72
|
+
return data.data as T[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 向后端发送原始 HTTP 请求(绕开 refine DataProvider)。
|
|
77
|
+
* 底层复用公共库的 customRequest,自动携带当前 app 的 auth header 及错误拦截。
|
|
78
|
+
*/
|
|
79
|
+
export async function customRequest<T>(
|
|
80
|
+
urlPath: string,
|
|
81
|
+
method: string,
|
|
82
|
+
params?: Record<string, any>
|
|
83
|
+
) {
|
|
84
|
+
return baseCustomRequest<T>(axiosInstance, apiUrl, urlPath, method, params);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|