@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.
Files changed (72) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +7 -0
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +3 -3
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +4 -4
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/authProvider.ts +2 -2
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +419 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/business/index.tsx +1 -1
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +1 -1
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/http.ts +28 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/http.ts +87 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/index.tsx +6 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -77
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/utils.tsx +1 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +1 -1
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +3 -3
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +1 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +1 -1
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +1 -1
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +0 -7
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +110 -8
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +1 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/{permission-readme → permissionReadme}/index.tsx +4 -3
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +1 -1
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +0 -7
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +32 -4
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +1 -1
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +1 -1
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +1 -1
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +1 -8
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +52 -3
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +2 -2
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +2 -2
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +1 -8
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +41 -11
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +2 -1
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/yarn.lock +8321 -0
  37. package/package.json +1 -1
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/utils.ts +0 -76
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/create.tsx +0 -113
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/edit.tsx +0 -122
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.tsx +0 -6
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/show.tsx +0 -61
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/create.tsx +0 -113
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/edit.tsx +0 -122
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/index.tsx +0 -6
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/list.tsx +0 -243
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/show.tsx +0 -61
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/create.tsx +0 -113
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/edit.tsx +0 -122
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.tsx +0 -6
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/show.tsx +0 -61
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/create.tsx +0 -113
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/edit.tsx +0 -122
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.tsx +0 -6
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/show.tsx +0 -61
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/create.tsx +0 -113
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/edit.tsx +0 -122
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/index.tsx +0 -6
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/list.tsx +0 -243
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/show.tsx +0 -61
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/create.tsx +0 -113
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/edit.tsx +0 -122
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/index.tsx +0 -6
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/list.tsx +0 -243
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/show.tsx +0 -61
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/create.tsx +0 -113
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/edit.tsx +0 -122
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.tsx +0 -6
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/show.tsx +0 -61
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/index.ts +0 -2
  71. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/{utilities → helpers}/get-name-initials.ts +0 -0
  72. /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
- const axiosInstance = axios.create();
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 headers = {
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
  }
@@ -2,4 +2,4 @@ import React from "react";
2
2
 
3
3
  export const renderLongColumnValue = (value: string) => {
4
4
  return <div style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{value}</div>;
5
- };
5
+ };
@@ -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 "utilities/utils";
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/login";
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<any>("role/findMany", "post"),
46
- customRequest<any>("rolePages/findMany", "post"),
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/zh)
600
+ <code>{"{{locale}}"}</code> → 当前语言 (en/zh_CN)
601
601
  </li>
602
602
  </ul>
603
603
  <p style={{ marginTop: 8 }}>
@@ -1,4 +1,4 @@
1
- import { useFetchData } from "hooks/useFetchData";
1
+ import { useFetchData } from "@/hooks/useFetchData";
2
2
 
3
3
  export const usePageManagement = (
4
4
  defaultPageListQuery: any = {},
@@ -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/login";
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: "#e6f4ff",
121
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
125
+ background: "#bae0ff",
126
+ boxShadow: "0 8px 24px rgba(22, 119, 255, 0.25)",
122
127
  borderLeft: "3px solid #1677ff",
123
- opacity: 0.9,
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={sortableIds} strategy={verticalListSortingStrategy}>
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
  };
@@ -9,6 +9,7 @@ export type PageMenuTreeNode = {
9
9
 
10
10
  export type ResourceDefinition = {
11
11
  id: number;
12
+ code: string;
12
13
  name: string;
13
14
  description?: string;
14
15
  type?: string;
@@ -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>zh</Text>)。例如:
214
+ <Text code>en</Text> 或 <Text code>zh_CN</Text>)。例如:
215
215
  <Text code>
216
- https://example.com/docs?lang={"{{locale}}"}
217
- </Text>
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
- {...(props as any)}
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
- {...(props as any)}
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,6 +1,6 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { Descriptions, Drawer, Table } from "antd";
3
- import type { Role } from "types/role";
3
+ import type { Role } from "@/types/role";
4
4
 
5
5
  type PageInfo = {
6
6
  id: number;
@@ -11,7 +11,7 @@ import {
11
11
  TreeSelect,
12
12
  Typography,
13
13
  } from "antd";
14
- import { Role } from "types/role";
14
+ import { Role } from "@/types/role";
15
15
  import { RoleSelect } from "../queries";
16
16
 
17
17
  type RoleFormFields = Partial<Role> & {
@@ -1,4 +1,4 @@
1
- import { useFetchData } from "hooks/useFetchData";
1
+ import { useFetchData } from "@/hooks/useFetchData";
2
2
 
3
3
  export const useRolePage = (
4
4
  defaultRolePagesQuery: any = {},
@@ -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';