@gadmin2n/schematics 0.0.65 → 0.0.67
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
|
@@ -1,67 +1,22 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import { GadminCrud as gadminDataProvider } from "@gadmin2n/react-common";
|
|
3
|
-
import { HttpError } from "@refinedev/core";
|
|
4
|
-
import { getApiUrl, getUrlParams } from "./utils";
|
|
5
1
|
import Cookies from "js-cookie";
|
|
2
|
+
import { getApiUrl } from "config/http";
|
|
6
3
|
|
|
7
4
|
export const GAdminTokenName = "gadmin-token";
|
|
8
5
|
|
|
6
|
+
export function getUrlParams(search = window.location.search) {
|
|
7
|
+
const hashes = search.slice(search.indexOf("?") + 1).split("&");
|
|
8
|
+
return hashes.reduce((acc, hash) => {
|
|
9
|
+
const [key, val] = hash.split("=");
|
|
10
|
+
if (val) acc[key] = decodeURIComponent(val);
|
|
11
|
+
return acc;
|
|
12
|
+
}, {} as Record<string, string>);
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
type LoginCookie = {
|
|
10
16
|
gadminToken?: string;
|
|
11
17
|
};
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
axiosInstance.interceptors.request.use(
|
|
16
|
-
(config) => {
|
|
17
|
-
if (config.headers) {
|
|
18
|
-
const headers = requestHeaders();
|
|
19
|
-
Object.assign(config.headers, headers);
|
|
20
|
-
}
|
|
21
|
-
return config;
|
|
22
|
-
},
|
|
23
|
-
(error) => {
|
|
24
|
-
return Promise.reject(error);
|
|
25
|
-
}
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
axiosInstance.interceptors.response.use(
|
|
29
|
-
(response) => {
|
|
30
|
-
return response;
|
|
31
|
-
},
|
|
32
|
-
(error) => {
|
|
33
|
-
if (error?.response?.status === 401) {
|
|
34
|
-
console.log("login expired");
|
|
35
|
-
login();
|
|
36
|
-
// 返回pending状态的promise,以免后续代码出错
|
|
37
|
-
return new Promise(() => { });
|
|
38
|
-
}
|
|
39
|
-
let message = error.response?.data?.message;
|
|
40
|
-
if (message) {
|
|
41
|
-
if (Array.isArray(message)) {
|
|
42
|
-
message = message.join(" AND ");
|
|
43
|
-
} else if (typeof message === "string") {
|
|
44
|
-
if (message.includes("Unique constraint")) {
|
|
45
|
-
message = `Unique constraint ${message.split("Unique constraint")[1]
|
|
46
|
-
}`;
|
|
47
|
-
} else {
|
|
48
|
-
let strings = message.split(")");
|
|
49
|
-
message = strings[strings.length - 1];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const customError: HttpError = {
|
|
54
|
-
...error,
|
|
55
|
-
message: `${error?.response?.statusText || ""}: ${message}`,
|
|
56
|
-
statusCode: error?.response?.status || 500,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return Promise.reject(customError);
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const apiUrl = getApiUrl();
|
|
64
|
-
export const gadminCrudProvider = gadminDataProvider(apiUrl, axiosInstance);
|
|
19
|
+
// ─── 认证 / 会话 ──────────────────────────────────────────────────────────────
|
|
65
20
|
|
|
66
21
|
export function login() {
|
|
67
22
|
// woa域名直接刷新页面
|
|
@@ -70,6 +25,7 @@ export function login() {
|
|
|
70
25
|
return;
|
|
71
26
|
}
|
|
72
27
|
clearLoginCookie();
|
|
28
|
+
const apiUrl = getApiUrl();
|
|
73
29
|
window.location.href = `${apiUrl}/login?redirect_url=${encodeURIComponent(
|
|
74
30
|
window.location.href
|
|
75
31
|
)}`;
|
|
@@ -77,6 +33,7 @@ export function login() {
|
|
|
77
33
|
|
|
78
34
|
export function logout() {
|
|
79
35
|
clearLoginCookie();
|
|
36
|
+
const apiUrl = getApiUrl();
|
|
80
37
|
window.location.href = `${apiUrl}/logout?redirect_url=${encodeURIComponent(
|
|
81
38
|
window.location.origin
|
|
82
39
|
)}`;
|
|
@@ -115,28 +72,16 @@ export function clearLoginCookie() {
|
|
|
115
72
|
|
|
116
73
|
export function requestHeaders() {
|
|
117
74
|
const gadminToken = Cookies.get(GAdminTokenName);
|
|
118
|
-
const
|
|
75
|
+
const business = JSON.parse(Cookies.get("Business") || "{}");
|
|
76
|
+
return {
|
|
119
77
|
"X-Requested-With": "XMLHttpRequest",
|
|
78
|
+
// 防止 business.name 含中文等非 ASCII 字符导致 HTTP header 500
|
|
79
|
+
...(Object.keys(business).length > 0 && {
|
|
80
|
+
"X-Gadmin-Business": JSON.stringify({
|
|
81
|
+
...business,
|
|
82
|
+
name: encodeURIComponent(business.name),
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
120
85
|
...(gadminToken && { [GAdminTokenName]: gadminToken }),
|
|
121
86
|
};
|
|
122
|
-
return headers;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
type LowercaseMethod = 'get' | 'delete' | 'head' | 'options' | 'post' | 'put' | 'patch';
|
|
126
|
-
|
|
127
|
-
export async function customRequest<T>(
|
|
128
|
-
urlPath: string,
|
|
129
|
-
method: string,
|
|
130
|
-
params?: Record<string, any>
|
|
131
|
-
) {
|
|
132
|
-
const url = `${apiUrl}/${urlPath}`;
|
|
133
|
-
const headers = {
|
|
134
|
-
"Content-Type": "application/json",
|
|
135
|
-
...requestHeaders(),
|
|
136
|
-
};
|
|
137
|
-
const httpMethod = method.toLowerCase() as LowercaseMethod;
|
|
138
|
-
const axiosOptions = { headers };
|
|
139
|
-
const res = await axiosInstance[httpMethod](url, params, axiosOptions);
|
|
140
|
-
const { data } = res;
|
|
141
|
-
return data as T;
|
|
142
87
|
}
|
|
@@ -7,7 +7,7 @@ import type { ColumnsType } from "antd/es/table";
|
|
|
7
7
|
import { Text } from "components";
|
|
8
8
|
import { AuditLogActionEnum } from "enums";
|
|
9
9
|
import { AuditLog } from "types";
|
|
10
|
-
import { renderLongColumnValue } from "
|
|
10
|
+
import { renderLongColumnValue } from "helpers";
|
|
11
11
|
|
|
12
12
|
import "./action-cell.css";
|
|
13
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { Checkbox, Modal, Spin, message } from "antd";
|
|
3
|
-
import { customRequest } from "helpers/
|
|
3
|
+
import { customRequest } from "@/helpers/http";
|
|
4
4
|
|
|
5
5
|
interface Role {
|
|
6
6
|
id: number;
|
|
@@ -42,8 +42,8 @@ export const AssignRolesModal: React.FC<AssignRolesModalProps> = ({
|
|
|
42
42
|
setLoading(true);
|
|
43
43
|
try {
|
|
44
44
|
const [rolesRes, rolePagesRes] = await Promise.all([
|
|
45
|
-
customRequest<
|
|
46
|
-
customRequest<
|
|
45
|
+
customRequest<{ data: Role[] }>("role/findMany", "post"),
|
|
46
|
+
customRequest<{ data: RolePage[] }>("rolePages/findMany", "post"),
|
|
47
47
|
]);
|
|
48
48
|
const rolesData: Role[] = Array.isArray(rolesRes?.data) ? rolesRes.data : [];
|
|
49
49
|
const rolePagesData: RolePage[] = Array.isArray(rolePagesRes?.data)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useMemo } from "react";
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
3
|
import { Descriptions, Drawer, Space, Table, Tag, type TableColumnsType } from "antd";
|
|
4
|
-
import { useFetchData } from "hooks/useFetchData";
|
|
4
|
+
import { useFetchData } from "@/hooks/useFetchData";
|
|
5
5
|
|
|
6
6
|
interface PageDetailDrawerProps {
|
|
7
7
|
page: { id: number; name: string; code?: string; path?: string; zhName?: string; enName?: string };
|
|
@@ -597,7 +597,7 @@ export const PageFormModal: React.FC<PageFormModalProps> = ({
|
|
|
597
597
|
</p>
|
|
598
598
|
<ul style={{ margin: 0, paddingLeft: 16 }}>
|
|
599
599
|
<li>
|
|
600
|
-
<code>{"{{locale}}"}</code> → 当前语言 (en/
|
|
600
|
+
<code>{"{{locale}}"}</code> → 当前语言 (en/zh_CN)
|
|
601
601
|
</li>
|
|
602
602
|
</ul>
|
|
603
603
|
<p style={{ marginTop: 8 }}>
|
|
@@ -1,8 +1 @@
|
|
|
1
1
|
export * from "./list";
|
|
2
|
-
|
|
3
|
-
// Stub exports for generated/resources.tsx compatibility
|
|
4
|
-
const EmptyComponent = () => null;
|
|
5
|
-
export const PageCreate = EmptyComponent;
|
|
6
|
-
export const PageEdit = EmptyComponent;
|
|
7
|
-
export const PageList = EmptyComponent;
|
|
8
|
-
export const PageShow = EmptyComponent;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { DeleteButton, EditButton, useTable } from "@refinedev/antd";
|
|
3
|
+
import { useGetIdentity } from "@refinedev/core";
|
|
3
4
|
import { Table, Button, Space, Tooltip, Tag, Alert, Modal, message, Tabs } from "antd";
|
|
4
5
|
import {
|
|
5
6
|
SaveOutlined,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
PlusOutlined,
|
|
11
12
|
EyeOutlined,
|
|
12
13
|
TeamOutlined,
|
|
14
|
+
CodeOutlined,
|
|
13
15
|
} from "@ant-design/icons";
|
|
14
16
|
import {
|
|
15
17
|
DndContext,
|
|
@@ -19,9 +21,11 @@ import {
|
|
|
19
21
|
useSensor,
|
|
20
22
|
useSensors,
|
|
21
23
|
DragEndEvent,
|
|
24
|
+
DragOverEvent,
|
|
22
25
|
} from "@dnd-kit/core";
|
|
23
26
|
import {
|
|
24
27
|
SortableContext,
|
|
28
|
+
arrayMove,
|
|
25
29
|
sortableKeyboardCoordinates,
|
|
26
30
|
useSortable,
|
|
27
31
|
verticalListSortingStrategy,
|
|
@@ -29,7 +33,7 @@ import {
|
|
|
29
33
|
import { CSS } from "@dnd-kit/utilities";
|
|
30
34
|
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
31
35
|
|
|
32
|
-
import { customRequest } from "helpers/
|
|
36
|
+
import { customRequest } from "@/helpers/http";
|
|
33
37
|
|
|
34
38
|
import { AssignRolesModal } from "./Components/AssignRolesModal";
|
|
35
39
|
import { EditPageModal } from "./Components/EditPageModal";
|
|
@@ -43,6 +47,7 @@ import type {
|
|
|
43
47
|
} from "./types";
|
|
44
48
|
import { usePageManagement } from "./hooks/usePageManagement";
|
|
45
49
|
import { useTranslation } from "react-i18next";
|
|
50
|
+
import { SqlModal, generatePageSql } from "../../components/SqlModal";
|
|
46
51
|
|
|
47
52
|
const RESOURCE_NAME_DISPLAY_TRUNCATION_LIMIT = 40;
|
|
48
53
|
|
|
@@ -112,15 +117,16 @@ const DraggableRow: React.FC<DraggableRowProps> = ({ children, ...props }) => {
|
|
|
112
117
|
const style: React.CSSProperties = {
|
|
113
118
|
...props.style,
|
|
114
119
|
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
|
|
115
|
-
transition,
|
|
120
|
+
transition: transition ?? "transform 200ms ease",
|
|
116
121
|
...(isDragging
|
|
117
122
|
? {
|
|
118
123
|
position: "relative",
|
|
119
124
|
zIndex: 9999,
|
|
120
|
-
background: "#
|
|
121
|
-
boxShadow: "0
|
|
125
|
+
background: "#bae0ff",
|
|
126
|
+
boxShadow: "0 8px 24px rgba(22, 119, 255, 0.25)",
|
|
122
127
|
borderLeft: "3px solid #1677ff",
|
|
123
|
-
opacity: 0.
|
|
128
|
+
opacity: 0.95,
|
|
129
|
+
outline: "1px solid #1677ff",
|
|
124
130
|
}
|
|
125
131
|
: {}),
|
|
126
132
|
};
|
|
@@ -146,6 +152,7 @@ const DraggableRow: React.FC<DraggableRowProps> = ({ children, ...props }) => {
|
|
|
146
152
|
|
|
147
153
|
export const PageManagementListPage: React.FC = () => {
|
|
148
154
|
const { i18n } = useTranslation();
|
|
155
|
+
const { data: identity } = useGetIdentity<any>();
|
|
149
156
|
const { tableProps } = useTable({
|
|
150
157
|
resource: "page",
|
|
151
158
|
pagination: {
|
|
@@ -176,6 +183,7 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
176
183
|
if (resourceId !== null) {
|
|
177
184
|
lookupMap[resourceId] = {
|
|
178
185
|
id: resourceId,
|
|
186
|
+
code: resource?.code ?? String(resourceId),
|
|
179
187
|
name: resource?.name || resource?.code || `Resource ${resourceId}`,
|
|
180
188
|
description: resource?.description,
|
|
181
189
|
type: resource?.type,
|
|
@@ -377,6 +385,64 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
377
385
|
// State for active tab
|
|
378
386
|
const [activeTab, setActiveTab] = useState<string>("regular");
|
|
379
387
|
|
|
388
|
+
// State for SQL modal
|
|
389
|
+
const [sqlModal, setSqlModal] = useState<{
|
|
390
|
+
open: boolean;
|
|
391
|
+
title: string;
|
|
392
|
+
sql: string;
|
|
393
|
+
}>({ open: false, title: "", sql: "" });
|
|
394
|
+
|
|
395
|
+
const handleShowSql = useCallback((record: any, hasChildren: boolean) => {
|
|
396
|
+
const pagesArray: any[] = Array.isArray(pageListData?.data) ? pageListData.data : [];
|
|
397
|
+
|
|
398
|
+
// Build pageResources for the given page
|
|
399
|
+
const buildPageResources = (pageId: number) => {
|
|
400
|
+
const associationsArray: any[] = Array.isArray(pageResourceAssociationsData?.data)
|
|
401
|
+
? pageResourceAssociationsData.data
|
|
402
|
+
: [];
|
|
403
|
+
return associationsArray
|
|
404
|
+
.filter((assoc: any) => safelyConvertToNumber(assoc?.pageId) === pageId)
|
|
405
|
+
.map((assoc: any) => {
|
|
406
|
+
const resourceId = safelyConvertToNumber(assoc?.resourceId);
|
|
407
|
+
// Prefer nested resource object, then fall back to the id-keyed lookup map (which includes code).
|
|
408
|
+
// The final fallback `String(resourceId)` should never be reached if availableResourcesData is loaded.
|
|
409
|
+
const resourceRecord = assoc?.resource ?? (resourceId !== null ? resourceLookupByIdMap[resourceId] : null);
|
|
410
|
+
const resourceCode = resourceRecord?.code ?? (resourceId !== null ? String(resourceId) : "unknown");
|
|
411
|
+
const resourceName = resourceRecord?.name || resourceRecord?.code || `Resource ${resourceId}`;
|
|
412
|
+
const actions = extractPermissionActionsFromAssociation(assoc, resourceRecord);
|
|
413
|
+
return { resourceCode, resourceName, actions };
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const pageResources = buildPageResources(record.id);
|
|
418
|
+
|
|
419
|
+
// Collect child pages if this is a parent
|
|
420
|
+
const children: Array<{ page: any; pageResources: Array<{ resourceCode: string; resourceName: string; actions: string[] }> }> = [];
|
|
421
|
+
if (hasChildren) {
|
|
422
|
+
const childPages = pagesArray.filter(
|
|
423
|
+
(p: any) => safelyConvertToNumber(p?.parentId ?? p?.parent_id) === record.id,
|
|
424
|
+
);
|
|
425
|
+
for (const child of childPages) {
|
|
426
|
+
const childId = safelyConvertToNumber(child?.id);
|
|
427
|
+
if (childId === null) continue;
|
|
428
|
+
children.push({
|
|
429
|
+
page: child,
|
|
430
|
+
pageResources: buildPageResources(childId),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Resolve parent's code from the in-memory page list so the SQL uses a
|
|
436
|
+
// portable subquery (SELECT id … WHERE code = '…') instead of a raw
|
|
437
|
+
// numeric parentId that differs between environments.
|
|
438
|
+
const parentId = safelyConvertToNumber(record?.parentId ?? record?.parent_id);
|
|
439
|
+
const parentPage = parentId !== null ? pagesArray.find((p: any) => safelyConvertToNumber(p?.id) === parentId) : null;
|
|
440
|
+
const recordWithParentCode = { ...record, parentCode: parentPage?.code ?? null };
|
|
441
|
+
|
|
442
|
+
const sql = generatePageSql(recordWithParentCode, pageResources, children, { authorId: identity?.userid, authorName: identity?.username });
|
|
443
|
+
setSqlModal({ open: true, title: `SQL — Page: ${record.name}`, sql });
|
|
444
|
+
}, [pageListData, pageResourceAssociationsData, resourceLookupByIdMap]);
|
|
445
|
+
|
|
380
446
|
// Ref for expand button click/double-click disambiguation
|
|
381
447
|
const expandClickTimer = useRef<number | null>(null);
|
|
382
448
|
|
|
@@ -519,7 +585,7 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
519
585
|
|
|
520
586
|
// Sort children by sortOrder
|
|
521
587
|
const sortNodes = (nodes: any[]) => {
|
|
522
|
-
nodes.sort((a, b) => (a.sortOrder ?? Infinity) - (b.sortOrder ?? Infinity));
|
|
588
|
+
nodes.sort((a, b) => (a.sortOrder ?? Infinity) - (b.sortOrder ?? Infinity));
|
|
523
589
|
nodes.forEach(node => {
|
|
524
590
|
if (node.children?.length > 0) {
|
|
525
591
|
sortNodes(node.children);
|
|
@@ -562,6 +628,14 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
562
628
|
return flattenedTableData.map(item => String(item.id));
|
|
563
629
|
}, [flattenedTableData]);
|
|
564
630
|
|
|
631
|
+
// Live-reordered IDs during drag (for visual feedback)
|
|
632
|
+
const [activeSortableIds, setActiveSortableIds] = useState<string[]>([]);
|
|
633
|
+
|
|
634
|
+
// Sync activeSortableIds when base list changes (e.g. after save/refresh)
|
|
635
|
+
useEffect(() => {
|
|
636
|
+
setActiveSortableIds(sortableIds);
|
|
637
|
+
}, [sortableIds]);
|
|
638
|
+
|
|
565
639
|
// dnd-kit sensors
|
|
566
640
|
const sensors = useSensors(
|
|
567
641
|
useSensor(PointerSensor, {
|
|
@@ -574,6 +648,18 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
574
648
|
}),
|
|
575
649
|
);
|
|
576
650
|
|
|
651
|
+
// Reorder the visual list live during drag for smooth feedback
|
|
652
|
+
const handleDragOver = useCallback((event: DragOverEvent) => {
|
|
653
|
+
const { active, over } = event;
|
|
654
|
+
if (!over || active.id === over.id) return;
|
|
655
|
+
setActiveSortableIds(prev => {
|
|
656
|
+
const oldIndex = prev.indexOf(String(active.id));
|
|
657
|
+
const newIndex = prev.indexOf(String(over.id));
|
|
658
|
+
if (oldIndex === -1 || newIndex === -1) return prev;
|
|
659
|
+
return arrayMove(prev, oldIndex, newIndex);
|
|
660
|
+
});
|
|
661
|
+
}, []);
|
|
662
|
+
|
|
577
663
|
// Handle drag end for dnd-kit
|
|
578
664
|
const handleDragEnd = useCallback(
|
|
579
665
|
(event: DragEndEvent) => {
|
|
@@ -855,6 +941,14 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
855
941
|
recordItemId={record.id}
|
|
856
942
|
onClick={() => setActiveModalState({ modalType: "edit", targetPageId: record.id })}
|
|
857
943
|
/>
|
|
944
|
+
<Tooltip title="Generate SQL">
|
|
945
|
+
<Button
|
|
946
|
+
type="default"
|
|
947
|
+
size="small"
|
|
948
|
+
icon={<CodeOutlined />}
|
|
949
|
+
onClick={() => handleShowSql(record, hasChildren)}
|
|
950
|
+
/>
|
|
951
|
+
</Tooltip>
|
|
858
952
|
{/* Only show delete button for leaf nodes (pages without children) */}
|
|
859
953
|
{!hasChildren && (
|
|
860
954
|
<>
|
|
@@ -935,9 +1029,10 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
935
1029
|
sensors={sensors}
|
|
936
1030
|
collisionDetection={closestCenter}
|
|
937
1031
|
modifiers={[restrictToVerticalAxis]}
|
|
1032
|
+
onDragOver={handleDragOver}
|
|
938
1033
|
onDragEnd={handleDragEnd}
|
|
939
1034
|
>
|
|
940
|
-
<SortableContext items={
|
|
1035
|
+
<SortableContext items={activeSortableIds} strategy={verticalListSortingStrategy}>
|
|
941
1036
|
<Table
|
|
942
1037
|
{...tableProps}
|
|
943
1038
|
rowKey="id"
|
|
@@ -1108,6 +1203,13 @@ export const PageManagementListPage: React.FC = () => {
|
|
|
1108
1203
|
defaultPathPrefix={activeModalState.defaultPathPrefix}
|
|
1109
1204
|
/>
|
|
1110
1205
|
)}
|
|
1206
|
+
|
|
1207
|
+
<SqlModal
|
|
1208
|
+
open={sqlModal.open}
|
|
1209
|
+
onClose={() => setSqlModal({ open: false, title: "", sql: "" })}
|
|
1210
|
+
title={sqlModal.title}
|
|
1211
|
+
sql={sqlModal.sql}
|
|
1212
|
+
/>
|
|
1111
1213
|
</div>
|
|
1112
1214
|
);
|
|
1113
1215
|
};
|
|
@@ -211,10 +211,11 @@ const faqItems = [
|
|
|
211
211
|
在 <Text code>iframeUrl</Text> 或{" "}
|
|
212
212
|
<Text code>externalRedirectUrl</Text> 中使用{" "}
|
|
213
213
|
<Text code>{"{{locale}}"}</Text>,系统会在运行时自动替换为当前语言(
|
|
214
|
-
<Text code>en</Text> 或 <Text code>
|
|
214
|
+
<Text code>en</Text> 或 <Text code>zh_CN</Text>)。例如:
|
|
215
215
|
<Text code>
|
|
216
|
-
https://example.com/docs?lang=
|
|
217
|
-
|
|
216
|
+
https://example.com/docs?lang=zh_CN
|
|
217
|
+
https://example.com/docs?lang=en
|
|
218
|
+
</Text>
|
|
218
219
|
</Paragraph>
|
|
219
220
|
),
|
|
220
221
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useMemo } from "react";
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
3
|
import { Descriptions, Drawer, Space, Table, Tabs, Tag } from "antd";
|
|
4
|
-
import { useFetchData } from "hooks/useFetchData";
|
|
4
|
+
import { useFetchData } from "@/hooks/useFetchData";
|
|
5
5
|
import type { Resource } from "../types";
|
|
6
6
|
|
|
7
7
|
interface ResourceDetailDrawerProps {
|
|
@@ -1,9 +1,2 @@
|
|
|
1
1
|
export { ResourceManagementListPage } from "./list";
|
|
2
2
|
export type { Resource } from "./types";
|
|
3
|
-
|
|
4
|
-
// Stub exports for generated/resources.tsx compatibility
|
|
5
|
-
const EmptyComponent = () => null;
|
|
6
|
-
export const ResourceCreate = EmptyComponent;
|
|
7
|
-
export const ResourceEdit = EmptyComponent;
|
|
8
|
-
export const ResourceList = EmptyComponent;
|
|
9
|
-
export const ResourceShow = EmptyComponent;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import React, { useState, useMemo } from "react";
|
|
2
2
|
import { useTable, FilterDropdown } from "@refinedev/antd";
|
|
3
3
|
import { Table, Button, Space, Tooltip, Input } from "antd";
|
|
4
|
-
import { EditOutlined, EyeOutlined, SearchOutlined } from "@ant-design/icons";
|
|
5
|
-
import { getDefaultFilter, type HttpError } from "@refinedev/core";
|
|
4
|
+
import { EditOutlined, EyeOutlined, SearchOutlined, CodeOutlined } from "@ant-design/icons";
|
|
5
|
+
import { getDefaultFilter, type HttpError, useGetIdentity } from "@refinedev/core";
|
|
6
6
|
import type { Resource } from "./types";
|
|
7
7
|
import { EditModal } from "./Components/EditModal";
|
|
8
8
|
import { CreateModal } from "./Components/CreateModal";
|
|
9
9
|
import { ResourceDetailDrawer } from "./Components/ResourceDetailDrawer";
|
|
10
|
+
import { SqlModal, generateResourceSql } from "../../components/SqlModal";
|
|
10
11
|
|
|
11
12
|
// System resources that cannot be modified (use Set for O(1) lookup performance)
|
|
12
13
|
// System resources that cannot be modified
|
|
13
14
|
export const PROTECTED_RESOURCES = ["pageresource", "resource", "page", "rolepages", "role", "roleresource"];
|
|
14
15
|
|
|
15
16
|
export const ResourceManagementListPage: React.FC = () => {
|
|
17
|
+
const { data: identity } = useGetIdentity<any>();
|
|
16
18
|
const { tableProps, filters, tableQueryResult } = useTable<
|
|
17
19
|
Resource,
|
|
18
20
|
HttpError,
|
|
@@ -53,6 +55,18 @@ export const ResourceManagementListPage: React.FC = () => {
|
|
|
53
55
|
|
|
54
56
|
const [drawerResource, setDrawerResource] = useState<Resource | null>(null);
|
|
55
57
|
|
|
58
|
+
const [sqlModal, setSqlModal] = useState<{
|
|
59
|
+
open: boolean;
|
|
60
|
+
title: string;
|
|
61
|
+
sql: string;
|
|
62
|
+
}>({ open: false, title: "", sql: "" });
|
|
63
|
+
|
|
64
|
+
const handleShowSql = (record: Resource) => {
|
|
65
|
+
const operator = { authorId: identity?.userid, authorName: identity?.username };
|
|
66
|
+
const sql = generateResourceSql(record, operator);
|
|
67
|
+
setSqlModal({ open: true, title: `SQL — Resource: ${record.code}`, sql });
|
|
68
|
+
};
|
|
69
|
+
|
|
56
70
|
// fix for ant design filter dropdown contains operator
|
|
57
71
|
filters.map(filter => {
|
|
58
72
|
if (filter.operator === "contains" && Array.isArray(filter.value)) {
|
|
@@ -103,7 +117,7 @@ export const ResourceManagementListPage: React.FC = () => {
|
|
|
103
117
|
defaultFilteredValue={getDefaultFilter("code", filters, "contains")}
|
|
104
118
|
filterDropdown={props => (
|
|
105
119
|
<FilterDropdown
|
|
106
|
-
{...
|
|
120
|
+
{...props}
|
|
107
121
|
clearFilters={() => {
|
|
108
122
|
props.setSelectedKeys([]);
|
|
109
123
|
props.confirm?.();
|
|
@@ -121,7 +135,7 @@ export const ResourceManagementListPage: React.FC = () => {
|
|
|
121
135
|
defaultFilteredValue={getDefaultFilter("name", filters, "contains")}
|
|
122
136
|
filterDropdown={props => (
|
|
123
137
|
<FilterDropdown
|
|
124
|
-
{...
|
|
138
|
+
{...props}
|
|
125
139
|
clearFilters={() => {
|
|
126
140
|
props.setSelectedKeys([]);
|
|
127
141
|
props.confirm?.();
|
|
@@ -161,6 +175,14 @@ export const ResourceManagementListPage: React.FC = () => {
|
|
|
161
175
|
onClick={() => setShowModal({ type: "edit", id: record.id })}
|
|
162
176
|
/>
|
|
163
177
|
</Tooltip>
|
|
178
|
+
<Tooltip title="Generate SQL">
|
|
179
|
+
<Button
|
|
180
|
+
type="default"
|
|
181
|
+
size="small"
|
|
182
|
+
icon={<CodeOutlined />}
|
|
183
|
+
onClick={() => handleShowSql(record)}
|
|
184
|
+
/>
|
|
185
|
+
</Tooltip>
|
|
164
186
|
</Space>
|
|
165
187
|
);
|
|
166
188
|
}}
|
|
@@ -179,6 +201,12 @@ export const ResourceManagementListPage: React.FC = () => {
|
|
|
179
201
|
onClose={() => setDrawerResource(null)}
|
|
180
202
|
/>
|
|
181
203
|
)}
|
|
204
|
+
<SqlModal
|
|
205
|
+
open={sqlModal.open}
|
|
206
|
+
onClose={() => setSqlModal({ open: false, title: "", sql: "" })}
|
|
207
|
+
title={sqlModal.title}
|
|
208
|
+
sql={sqlModal.sql}
|
|
209
|
+
/>
|
|
182
210
|
</div>
|
|
183
211
|
);
|
|
184
212
|
};
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
export * from './list';
|
|
2
|
-
|
|
3
|
-
// Stub exports for generated/resources.tsx compatibility
|
|
4
|
-
const EmptyComponent = () => null;
|
|
5
|
-
export const RoleCreate = EmptyComponent;
|
|
6
|
-
export const RoleEdit = EmptyComponent;
|
|
7
|
-
export const RoleList = EmptyComponent;
|
|
8
|
-
export const RoleShow = EmptyComponent;
|
|
1
|
+
export * from './list';
|