nsgm-cli 2.1.41 → 2.1.42
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/client/components/ClientProviders.tsx +3 -3
- package/client/components/LanguageSwitcher.tsx +3 -3
- package/client/layout/index.tsx +2 -2
- package/generation/package.json +21 -20
- package/lib/generators/dataloader-generator.d.ts +8 -0
- package/lib/generators/dataloader-generator.js +63 -11
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/pages/_app.tsx +8 -2
- package/pages/_document.tsx +65 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, ReactNode, FC } from "react";
|
|
2
2
|
import { ThemeProvider } from "styled-components";
|
|
3
3
|
import { GlobalStyle } from "@/styled/common";
|
|
4
4
|
|
|
5
5
|
interface ClientProvidersProps {
|
|
6
|
-
children:
|
|
6
|
+
children: ReactNode;
|
|
7
7
|
theme: any;
|
|
8
8
|
whiteColor?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const ClientProviders:
|
|
11
|
+
const ClientProviders: FC<ClientProvidersProps> = ({ children, theme, whiteColor = true }) => {
|
|
12
12
|
const [isClient, setIsClient] = useState(false);
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, CSSProperties, FC } from "react";
|
|
2
2
|
import { Select } from "antd";
|
|
3
3
|
import { useRouter } from "next/router";
|
|
4
4
|
import { GlobalOutlined } from "@ant-design/icons";
|
|
@@ -6,11 +6,11 @@ import { GlobalOutlined } from "@ant-design/icons";
|
|
|
6
6
|
const { Option } = Select;
|
|
7
7
|
|
|
8
8
|
interface LanguageSwitcherProps {
|
|
9
|
-
style?:
|
|
9
|
+
style?: CSSProperties;
|
|
10
10
|
size?: "small" | "middle" | "large";
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const LanguageSwitcher:
|
|
13
|
+
const LanguageSwitcher: FC<LanguageSwitcherProps> = ({ style, size = "middle" }) => {
|
|
14
14
|
const router = useRouter();
|
|
15
15
|
const [mounted, setMounted] = useState(false);
|
|
16
16
|
const [currentLocale, setCurrentLocale] = useState("zh-CN");
|
package/client/layout/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, ReactNode } from "react";
|
|
2
2
|
import { Layout, Menu, Dropdown, Space } from "antd";
|
|
3
3
|
import {
|
|
4
4
|
Container,
|
|
@@ -28,7 +28,7 @@ interface MenuItem {
|
|
|
28
28
|
key: string;
|
|
29
29
|
text: string;
|
|
30
30
|
url: string;
|
|
31
|
-
icon?:
|
|
31
|
+
icon?: ReactNode;
|
|
32
32
|
subMenus?: SubMenuItem[];
|
|
33
33
|
}
|
|
34
34
|
|
package/generation/package.json
CHANGED
|
@@ -27,24 +27,25 @@
|
|
|
27
27
|
"nsgm-cli": "^2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^
|
|
31
|
-
"@types/react": "^18",
|
|
32
|
-
"@types/
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"jest
|
|
36
|
-
"
|
|
37
|
-
"@testing-library/
|
|
38
|
-
"@testing-library/
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"eslint": "^
|
|
43
|
-
"eslint
|
|
44
|
-
"eslint-
|
|
45
|
-
"prettier": "^3",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"i18next": "^
|
|
30
|
+
"@types/node": "^24.1.0",
|
|
31
|
+
"@types/react": "^18.3.23",
|
|
32
|
+
"@types/react-dom": "^18.3.7",
|
|
33
|
+
"@types/lodash": "^4.17.20",
|
|
34
|
+
"typescript": "^5.8.3",
|
|
35
|
+
"jest": "^30.0.5",
|
|
36
|
+
"jest-environment-jsdom": "^30.0.5",
|
|
37
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
38
|
+
"@testing-library/react": "^16.3.0",
|
|
39
|
+
"@testing-library/user-event": "^14.6.1",
|
|
40
|
+
"@types/jest": "^30.0.0",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
42
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
43
|
+
"eslint": "^9.31.0",
|
|
44
|
+
"eslint-config-prettier": "^10.1.8",
|
|
45
|
+
"eslint-plugin-prettier": "^5.5.3",
|
|
46
|
+
"prettier": "^3.6.2",
|
|
47
|
+
"next-i18next": "^15.3.0",
|
|
48
|
+
"react-i18next": "^15.2.0",
|
|
49
|
+
"i18next": "^24.1.0"
|
|
49
50
|
}
|
|
50
|
-
}
|
|
51
|
+
}
|
|
@@ -4,6 +4,14 @@ import { BaseGenerator } from "./base-generator";
|
|
|
4
4
|
* 自动生成对应的 DataLoader JavaScript 文件
|
|
5
5
|
*/
|
|
6
6
|
export declare class DataLoaderGenerator extends BaseGenerator {
|
|
7
|
+
/**
|
|
8
|
+
* 可能的 JSON 字段名(需要自动解析)
|
|
9
|
+
*/
|
|
10
|
+
private jsonFieldNames;
|
|
11
|
+
/**
|
|
12
|
+
* 获取可能是 JSON 的字段
|
|
13
|
+
*/
|
|
14
|
+
private getJsonFields;
|
|
7
15
|
generate(): string;
|
|
8
16
|
/**
|
|
9
17
|
* 生成外键 DataLoader
|
|
@@ -7,13 +7,66 @@ const base_generator_1 = require("./base-generator");
|
|
|
7
7
|
* 自动生成对应的 DataLoader JavaScript 文件
|
|
8
8
|
*/
|
|
9
9
|
class DataLoaderGenerator extends base_generator_1.BaseGenerator {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
/**
|
|
13
|
+
* 可能的 JSON 字段名(需要自动解析)
|
|
14
|
+
*/
|
|
15
|
+
this.jsonFieldNames = [
|
|
16
|
+
"images",
|
|
17
|
+
"photos",
|
|
18
|
+
"gallery",
|
|
19
|
+
"metadata",
|
|
20
|
+
"attributes",
|
|
21
|
+
"specs",
|
|
22
|
+
"options",
|
|
23
|
+
"settings",
|
|
24
|
+
"config",
|
|
25
|
+
"extra",
|
|
26
|
+
"data",
|
|
27
|
+
"json",
|
|
28
|
+
"params",
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 获取可能是 JSON 的字段
|
|
33
|
+
*/
|
|
34
|
+
getJsonFields() {
|
|
35
|
+
return this.fields
|
|
36
|
+
.filter((f) => this.jsonFieldNames.some((jsonName) => f.name.toLowerCase().includes(jsonName)))
|
|
37
|
+
.map((f) => f.name);
|
|
38
|
+
}
|
|
10
39
|
generate() {
|
|
11
40
|
const capitalizedController = this.getCapitalizedController();
|
|
12
41
|
const selectFields = this.fields.map((f) => f.name).join(", ");
|
|
42
|
+
const jsonFields = this.getJsonFields();
|
|
43
|
+
const hasJsonFields = jsonFields.length > 0;
|
|
13
44
|
return `const DataLoader = require('dataloader');
|
|
14
45
|
const { executeQuery } = require('../utils/common');
|
|
15
46
|
|
|
16
|
-
|
|
47
|
+
${hasJsonFields
|
|
48
|
+
? `/**
|
|
49
|
+
* 处理 ${this.controller} 行数据,将 JSON 字符串解析为对象
|
|
50
|
+
*/
|
|
51
|
+
function process${capitalizedController}Row(row) {
|
|
52
|
+
if (!row) return row;
|
|
53
|
+
|
|
54
|
+
${jsonFields
|
|
55
|
+
.map((field) => ` // 处理 ${field} 字段(JSON 字符串转对象)
|
|
56
|
+
if (row.${field} && typeof row.${field} === 'string') {
|
|
57
|
+
try {
|
|
58
|
+
row.${field} = JSON.parse(row.${field});
|
|
59
|
+
} catch (e) {
|
|
60
|
+
row.${field} = ${field === "images" || field === "photos" || field === "gallery" ? "[]" : "{}"};
|
|
61
|
+
}
|
|
62
|
+
}`)
|
|
63
|
+
.join(",\n ")}
|
|
64
|
+
|
|
65
|
+
return row;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
`
|
|
69
|
+
: ""}/**
|
|
17
70
|
* ${capitalizedController} DataLoader
|
|
18
71
|
* 针对 ${this.controller} 表的批量数据加载器,解决 N+1 查询问题
|
|
19
72
|
*/
|
|
@@ -31,9 +84,7 @@ class ${capitalizedController}DataLoader {
|
|
|
31
84
|
const results = await executeQuery(sql, [...ids]);
|
|
32
85
|
|
|
33
86
|
// 确保返回顺序与输入 keys 一致,未找到的返回 null
|
|
34
|
-
return ids.map(id =>
|
|
35
|
-
results.find((row) => row.id === id) || null
|
|
36
|
-
);
|
|
87
|
+
return ids.map(id => ${hasJsonFields ? `process${capitalizedController}Row(results.find((row) => row.id === id) || null)` : `results.find((row) => row.id === id) || null`});
|
|
37
88
|
} catch (error) {
|
|
38
89
|
console.error('DataLoader byId 批量加载失败:', error);
|
|
39
90
|
throw error;
|
|
@@ -58,9 +109,7 @@ class ${capitalizedController}DataLoader {
|
|
|
58
109
|
const results = await executeQuery(sql, [...names]);
|
|
59
110
|
|
|
60
111
|
// 确保返回顺序与输入 keys 一致
|
|
61
|
-
return names.map(name =>
|
|
62
|
-
results.find((row) => row.name === name) || null
|
|
63
|
-
);
|
|
112
|
+
return names.map(name => ${hasJsonFields ? `process${capitalizedController}Row(results.find((row) => row.name === name) || null)` : `results.find((row) => row.name === name) || null`});
|
|
64
113
|
} catch (error) {
|
|
65
114
|
console.error('DataLoader byName 批量加载失败:', error);
|
|
66
115
|
throw error;
|
|
@@ -83,7 +132,8 @@ class ${capitalizedController}DataLoader {
|
|
|
83
132
|
const results = await Promise.all(
|
|
84
133
|
searchTerms.map(async (term) => {
|
|
85
134
|
const sql = 'SELECT ${selectFields} FROM ${this.controller} WHERE name LIKE ?';
|
|
86
|
-
|
|
135
|
+
const rows = await executeQuery(sql, [\`%\${term}%\`]);
|
|
136
|
+
${hasJsonFields ? `return rows.map(process${capitalizedController}Row);` : "return rows;"}
|
|
87
137
|
})
|
|
88
138
|
);
|
|
89
139
|
|
|
@@ -100,7 +150,7 @@ class ${capitalizedController}DataLoader {
|
|
|
100
150
|
}
|
|
101
151
|
);
|
|
102
152
|
|
|
103
|
-
${this.generateForeignKeyLoaders()}
|
|
153
|
+
${this.generateForeignKeyLoaders(hasJsonFields, capitalizedController)}
|
|
104
154
|
}
|
|
105
155
|
|
|
106
156
|
/**
|
|
@@ -170,7 +220,7 @@ module.exports = { ${capitalizedController}DataLoader, create${capitalizedContro
|
|
|
170
220
|
/**
|
|
171
221
|
* 生成外键 DataLoader
|
|
172
222
|
*/
|
|
173
|
-
generateForeignKeyLoaders() {
|
|
223
|
+
generateForeignKeyLoaders(hasJsonFields, capitalizedController) {
|
|
174
224
|
const foreignKeys = this.fields.filter((f) => f.name.endsWith("_id") && f.name !== "id");
|
|
175
225
|
if (foreignKeys.length === 0) {
|
|
176
226
|
return "";
|
|
@@ -193,7 +243,9 @@ module.exports = { ${capitalizedController}DataLoader, create${capitalizedContro
|
|
|
193
243
|
|
|
194
244
|
// 按外键分组
|
|
195
245
|
return ${fk.name}s.map(${fk.name} =>
|
|
196
|
-
results
|
|
246
|
+
results
|
|
247
|
+
.filter((row) => row.${fk.name} === ${fk.name})
|
|
248
|
+
${hasJsonFields ? `.map(process${capitalizedController}Row)` : ""}
|
|
197
249
|
);
|
|
198
250
|
} catch (error) {
|
|
199
251
|
console.error('DataLoader by${capitalizedRelated}Id 批量加载失败:', error);
|