nsgm-cli 2.1.21 → 2.1.23
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/README.md +40 -0
- package/client/components/Button.tsx +0 -2
- package/client/layout/index.tsx +2 -4
- package/client/utils/common.ts +12 -10
- package/client/utils/fetch.ts +1 -1
- package/client/utils/menu.tsx +0 -1
- package/client/utils/sso.ts +13 -3
- package/generation/client/utils/menu.tsx +0 -1
- package/jest.config.js +4 -4
- package/lib/generate_create.js +9 -0
- package/lib/generators/dataloader-generator.d.ts +12 -0
- package/lib/generators/dataloader-generator.js +221 -0
- package/lib/generators/resolver-generator.d.ts +2 -1
- package/lib/generators/resolver-generator.js +117 -24
- package/lib/generators/schema-generator.js +1 -0
- package/lib/index.js +11 -8
- package/lib/server/dataloaders/index.d.ts +38 -0
- package/lib/server/dataloaders/index.js +33 -0
- package/lib/server/dataloaders/template-dataloader.d.ts +48 -0
- package/lib/server/dataloaders/template-dataloader.js +131 -0
- package/lib/server/debug/dataloader-debug.d.ts +63 -0
- package/lib/server/debug/dataloader-debug.js +192 -0
- package/lib/server/graphql.js +9 -0
- package/lib/server/utils/dataloader-monitor.d.ts +87 -0
- package/lib/server/utils/dataloader-monitor.js +199 -0
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils.js +1 -1
- package/next-env.d.ts +1 -0
- package/next-i18next.config.js +7 -5
- package/next.config.js +34 -112
- package/package.json +6 -3
- package/pages/_app.tsx +7 -7
- package/pages/_document.tsx +0 -1
- package/pages/_error.tsx +0 -1
- package/pages/api/sso/ticketCheck.ts +117 -0
- package/pages/index.tsx +10 -3
- package/pages/login.tsx +41 -11
- package/pages/template/manage.tsx +16 -2
- package/server/apis/sso.js +22 -4
- package/server/modules/template/resolver.js +101 -21
- package/server/modules/template/schema.js +1 -0
package/lib/utils.js
CHANGED
package/next-env.d.ts
CHANGED
package/next-i18next.config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @type {import('next-i18next').UserConfig}
|
|
3
5
|
*/
|
|
@@ -5,14 +7,14 @@ module.exports = {
|
|
|
5
7
|
i18n: {
|
|
6
8
|
defaultLocale: 'zh-CN',
|
|
7
9
|
locales: ['zh-CN', 'en-US', 'ja-JP'],
|
|
8
|
-
localeDetection: false,
|
|
10
|
+
localeDetection: false,
|
|
9
11
|
},
|
|
10
|
-
localePath: '
|
|
11
|
-
/** To avoid issues when deploying to some platforms, we can configure the cache */
|
|
12
|
+
localePath: path.resolve(process.cwd(), 'public/locales'),
|
|
12
13
|
saveMissing: false,
|
|
13
|
-
strictMode:
|
|
14
|
-
serializeConfig:
|
|
14
|
+
strictMode: false,
|
|
15
|
+
serializeConfig: true,
|
|
15
16
|
react: {
|
|
16
17
|
useSuspense: false,
|
|
17
18
|
},
|
|
19
|
+
reloadOnPrerender: process.env.NODE_ENV === 'development',
|
|
18
20
|
}
|
package/next.config.js
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
const { PHASE_DEVELOPMENT_SERVER, PHASE_EXPORT } = require('next/constants')
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const path = require('path')
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// 直接定义 i18n 配置,避免在 Vercel 上找不到配置文件
|
|
9
|
+
const i18n = {
|
|
10
|
+
defaultLocale: 'zh-CN',
|
|
11
|
+
locales: ['zh-CN', 'en-US', 'ja-JP'],
|
|
12
|
+
localeDetection: false,
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
module.exports = (phase, defaultConfig, options) => {
|
|
10
16
|
let projectConfig = null
|
|
@@ -16,8 +22,18 @@ module.exports = (phase, defaultConfig, options) => {
|
|
|
16
22
|
|
|
17
23
|
if (fs.existsSync(curProjectConfigPath)) {
|
|
18
24
|
projectConfig = require(curProjectConfigPath)
|
|
19
|
-
} else {
|
|
25
|
+
} else if (fs.existsSync('./project.config.js')) {
|
|
20
26
|
projectConfig = require('./project.config.js')
|
|
27
|
+
} else {
|
|
28
|
+
// 默认配置(用于 Vercel 等环境)
|
|
29
|
+
projectConfig = {
|
|
30
|
+
env: 'production',
|
|
31
|
+
version: '1.0.0',
|
|
32
|
+
prefix: '',
|
|
33
|
+
protocol: 'https',
|
|
34
|
+
host: 'localhost',
|
|
35
|
+
port: '443'
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
if (fs.existsSync(curPkgPath)) {
|
|
@@ -40,21 +56,19 @@ module.exports = (phase, defaultConfig, options) => {
|
|
|
40
56
|
prefix = ''
|
|
41
57
|
}
|
|
42
58
|
|
|
59
|
+
// 设置环境变量用于客户端访问
|
|
60
|
+
process.env.NEXT_PUBLIC_VERSION = version
|
|
61
|
+
process.env.NEXT_PUBLIC_PREFIX = prefix
|
|
62
|
+
process.env.NEXT_PUBLIC_PROTOCOL = protocol
|
|
63
|
+
process.env.NEXT_PUBLIC_HOST = host
|
|
64
|
+
process.env.NEXT_PUBLIC_PORT = port
|
|
65
|
+
process.env.NEXT_PUBLIC_ENV = env
|
|
66
|
+
process.env.NEXT_PUBLIC_IS_EXPORT = phase === PHASE_EXPORT ? 'true' : 'false'
|
|
67
|
+
|
|
43
68
|
let configObj = {
|
|
44
69
|
// target: 'serverless',
|
|
45
70
|
// crossOrign: 'anonymous',
|
|
46
71
|
i18n,
|
|
47
|
-
serverRuntimeConfig: {},
|
|
48
|
-
publicRuntimeConfig: {
|
|
49
|
-
version,
|
|
50
|
-
prefix,
|
|
51
|
-
protocol,
|
|
52
|
-
host,
|
|
53
|
-
port,
|
|
54
|
-
env,
|
|
55
|
-
phase,
|
|
56
|
-
isExport: phase === PHASE_EXPORT,
|
|
57
|
-
},
|
|
58
72
|
transpilePackages: [
|
|
59
73
|
'antd',
|
|
60
74
|
'@ant-design',
|
|
@@ -72,7 +86,7 @@ module.exports = (phase, defaultConfig, options) => {
|
|
|
72
86
|
esmExternals: true, // 支持 ESM 外部依赖
|
|
73
87
|
},
|
|
74
88
|
compiler: {
|
|
75
|
-
removeConsole: phase !== PHASE_DEVELOPMENT_SERVER,
|
|
89
|
+
removeConsole: phase !== PHASE_DEVELOPMENT_SERVER ? { exclude: ['warn', 'error'] } : false,
|
|
76
90
|
styledComponents: true,
|
|
77
91
|
},
|
|
78
92
|
...(phase === PHASE_DEVELOPMENT_SERVER && {
|
|
@@ -90,114 +104,22 @@ module.exports = (phase, defaultConfig, options) => {
|
|
|
90
104
|
'127.0.0.1',
|
|
91
105
|
'localhost',
|
|
92
106
|
],
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (dev && isServer) {
|
|
96
|
-
const originalWarn = console.warn
|
|
97
|
-
const originalError = console.error
|
|
98
|
-
|
|
99
|
-
console.warn = (...args) => {
|
|
100
|
-
const warnMessage = args[0]
|
|
101
|
-
if (
|
|
102
|
-
typeof warnMessage === 'string' &&
|
|
103
|
-
(warnMessage.includes('useLayoutEffect does nothing on the server') ||
|
|
104
|
-
warnMessage.includes('Warning: useLayoutEffect does nothing on the server'))
|
|
105
|
-
) {
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
originalWarn.apply(console, args)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
console.error = (...args) => {
|
|
112
|
-
const errorMessage = args[0]
|
|
113
|
-
if (
|
|
114
|
-
typeof errorMessage === 'string' &&
|
|
115
|
-
(errorMessage.includes('useLayoutEffect does nothing on the server') ||
|
|
116
|
-
errorMessage.includes('Warning: useLayoutEffect does nothing on the server'))
|
|
117
|
-
) {
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
originalError.apply(console, args)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// 启用压缩
|
|
125
|
-
if (!dev && !isServer) {
|
|
126
|
-
config.optimization = {
|
|
127
|
-
...config.optimization,
|
|
128
|
-
splitChunks: {
|
|
129
|
-
chunks: 'all',
|
|
130
|
-
cacheGroups: {
|
|
131
|
-
vendor: {
|
|
132
|
-
test: /[\\/]node_modules[\\/]/,
|
|
133
|
-
name: 'vendors',
|
|
134
|
-
chunks: 'all',
|
|
135
|
-
enforce: true,
|
|
136
|
-
},
|
|
137
|
-
antd: {
|
|
138
|
-
test: /[\\/]node_modules[\\/](antd|@ant-design)[\\/]/,
|
|
139
|
-
name: 'antd',
|
|
140
|
-
chunks: 'all',
|
|
141
|
-
priority: 10,
|
|
142
|
-
},
|
|
143
|
-
react: {
|
|
144
|
-
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
|
|
145
|
-
name: 'react',
|
|
146
|
-
chunks: 'all',
|
|
147
|
-
priority: 10,
|
|
148
|
-
},
|
|
149
|
-
common: {
|
|
150
|
-
name: 'common',
|
|
151
|
-
minChunks: 2,
|
|
152
|
-
chunks: 'all',
|
|
153
|
-
priority: 5,
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
minimize: true,
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 添加分析工具
|
|
161
|
-
if (process.env.ANALYZE === 'true') {
|
|
162
|
-
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
|
|
163
|
-
config.plugins.push(
|
|
164
|
-
new BundleAnalyzerPlugin({
|
|
165
|
-
analyzerMode: 'static',
|
|
166
|
-
openAnalyzer: false,
|
|
167
|
-
})
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 优化模块解析
|
|
173
|
-
config.resolve.alias = {
|
|
174
|
-
...config.resolve.alias,
|
|
175
|
-
'@': path.resolve(__dirname, 'client'),
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 支持 TypeScript 路径映射
|
|
179
|
-
config.resolve.modules = [path.resolve(__dirname, 'client'), 'node_modules']
|
|
180
|
-
|
|
181
|
-
return config
|
|
182
|
-
},
|
|
107
|
+
// 使用 Turbopack(Next.js 16 默认)
|
|
108
|
+
turbopack: {},
|
|
183
109
|
generateBuildId: async () => {
|
|
184
110
|
return 'nsgm-cli-' + version
|
|
185
111
|
},
|
|
186
|
-
exportPathMap: async function (defaultPathMap, { dev, dir, outDir }) {
|
|
187
|
-
if (dev) {
|
|
188
|
-
return defaultPathMap
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return defaultPathMap
|
|
192
|
-
},
|
|
193
112
|
generateEtags: false,
|
|
194
113
|
useFileSystemPublicRoutes: true,
|
|
195
114
|
}
|
|
196
115
|
|
|
197
116
|
if (phase !== PHASE_DEVELOPMENT_SERVER) {
|
|
117
|
+
// 在 Vercel 环境中使用默认的 .next 目录,否则使用 build 目录
|
|
118
|
+
const distDir = process.env.VERCEL ? '.next' : 'build'
|
|
119
|
+
|
|
198
120
|
configObj = {
|
|
199
121
|
...configObj,
|
|
200
|
-
distDir
|
|
122
|
+
distDir,
|
|
201
123
|
assetPrefix: prefix,
|
|
202
124
|
async rewrites() {
|
|
203
125
|
return [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nsgm-cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.23",
|
|
4
4
|
"description": "A CLI tool to run Next/Style-components and Graphql/Mysql fullstack project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
"test:coverage": "jest --coverage",
|
|
34
34
|
"test:watch": "jest --watch",
|
|
35
35
|
"performance": "./scripts/performance-check.sh",
|
|
36
|
-
"generate-password": "node scripts/generate-password-hash.js"
|
|
36
|
+
"generate-password": "node scripts/generate-password-hash.js",
|
|
37
|
+
"vercel-build": "npm run tsbuild && npm run build",
|
|
38
|
+
"vercel-dev": "npm run tsbuild && npm run dev"
|
|
37
39
|
},
|
|
38
40
|
"repository": {
|
|
39
41
|
"type": "git",
|
|
@@ -88,6 +90,7 @@
|
|
|
88
90
|
"commander": "14.0.0",
|
|
89
91
|
"cors": "2.8.5",
|
|
90
92
|
"critters": "0.0.25",
|
|
93
|
+
"dataloader": "2.2.3",
|
|
91
94
|
"dayjs": "1.11.13",
|
|
92
95
|
"dotenv": "17.2.1",
|
|
93
96
|
"exceljs": "4.4.0",
|
|
@@ -105,7 +108,7 @@
|
|
|
105
108
|
"lusca": "1.7.0",
|
|
106
109
|
"markdown-it": "14.1.0",
|
|
107
110
|
"mysql2": "3.14.2",
|
|
108
|
-
"next": "
|
|
111
|
+
"next": "16.1.0",
|
|
109
112
|
"next-i18next": "15.3.0",
|
|
110
113
|
"ora": "8.2.0",
|
|
111
114
|
"rc-util": "5.44.4",
|
package/pages/_app.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 必须在所有其他导入之前执行
|
|
2
2
|
import "@/utils/suppressWarnings";
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
5
|
import { Provider } from "react-redux";
|
|
6
6
|
import { Spin } from "antd";
|
|
7
7
|
import { appWithTranslation } from "next-i18next";
|
|
@@ -15,7 +15,6 @@ import SSRSafeAntdProvider from "@/components/SSRSafeAntdProvider";
|
|
|
15
15
|
import { login } from "@/utils/sso";
|
|
16
16
|
import { getAntdLocale } from "@/utils/i18n";
|
|
17
17
|
import { navigateToLogin } from "@/utils/navigation";
|
|
18
|
-
import nextI18NextConfig from "../next-i18next.config.js";
|
|
19
18
|
import "antd/dist/reset.css";
|
|
20
19
|
|
|
21
20
|
const theme = {
|
|
@@ -46,7 +45,10 @@ const App = ({ Component, pageProps }) => {
|
|
|
46
45
|
|
|
47
46
|
useEffect(() => {
|
|
48
47
|
setMounted(true);
|
|
49
|
-
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
// 同步当前语言
|
|
50
52
|
if (typeof window !== "undefined") {
|
|
51
53
|
const locale = router.locale || "zh-CN";
|
|
52
54
|
setCurrentLocale(locale);
|
|
@@ -133,7 +135,5 @@ const App = ({ Component, pageProps }) => {
|
|
|
133
135
|
);
|
|
134
136
|
};
|
|
135
137
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
export default appWithTranslation(App, nextI18NextConfig);
|
|
138
|
+
// appWithTranslation 会自动从 next.config.js 读取 i18n 配置
|
|
139
|
+
export default appWithTranslation(App);
|
package/pages/_document.tsx
CHANGED
package/pages/_error.tsx
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from "next";
|
|
2
|
+
import bcrypt from "bcrypt";
|
|
3
|
+
|
|
4
|
+
type ResponseData = {
|
|
5
|
+
name: string;
|
|
6
|
+
returnCode: number;
|
|
7
|
+
userAttr?: {
|
|
8
|
+
displayName: string;
|
|
9
|
+
username?: string;
|
|
10
|
+
};
|
|
11
|
+
cookieValue?: string;
|
|
12
|
+
cookieExpire?: number;
|
|
13
|
+
message?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getLoginCredentials = () => {
|
|
17
|
+
const username = process.env.LOGIN_USERNAME || "admin";
|
|
18
|
+
const passwordHash = process.env.LOGIN_PASSWORD_HASH;
|
|
19
|
+
|
|
20
|
+
console.warn("[SSO] getLoginCredentials - username:", username);
|
|
21
|
+
console.warn(
|
|
22
|
+
"[SSO] getLoginCredentials - passwordHash from env:",
|
|
23
|
+
passwordHash ? `${passwordHash.substring(0, 20)}...` : "undefined"
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!passwordHash) {
|
|
27
|
+
console.warn("[SSO] ⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希");
|
|
28
|
+
return {
|
|
29
|
+
username,
|
|
30
|
+
passwordHash: "$2b$10$K5O.TJLKGPmKGHJK8KzN5u8qUYKzq5vLcXlP7vGUzq5vLcXlP7vGUz",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.warn("[SSO] getLoginCredentials - final passwordHash:", `${passwordHash.substring(0, 20)}...`);
|
|
35
|
+
return { username, passwordHash };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const validateCredentials = async (inputUsername: string, inputPassword: string): Promise<boolean> => {
|
|
39
|
+
const { username, passwordHash } = getLoginCredentials();
|
|
40
|
+
|
|
41
|
+
if (inputUsername !== username) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return await bcrypt.compare(inputPassword, passwordHash);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn("[SSO] 密码验证失败:", error);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
|
|
54
|
+
if (req.method !== "GET") {
|
|
55
|
+
res.status(405).json({
|
|
56
|
+
name: "ticketCheck",
|
|
57
|
+
returnCode: -1,
|
|
58
|
+
message: "Method not allowed",
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { name } = req.query;
|
|
64
|
+
|
|
65
|
+
console.warn("[SSO] ticketCheck called, name:", name);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (!name || typeof name !== "string") {
|
|
69
|
+
throw new Error("Invalid name parameter");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 使用 Buffer 解码 Base64 字符串,然后使用 decodeURIComponent 处理特殊字符
|
|
73
|
+
const decodedBase64 = Buffer.from(name, "base64").toString("utf-8");
|
|
74
|
+
console.warn("[SSO] Decoded Base64:", decodedBase64);
|
|
75
|
+
|
|
76
|
+
const decodedName = decodeURIComponent(decodedBase64);
|
|
77
|
+
console.warn("[SSO] Decoded name:", decodedName);
|
|
78
|
+
|
|
79
|
+
const [inputUsername, inputPassword] = decodedName.split(",");
|
|
80
|
+
console.warn("[SSO] Input username:", inputUsername, "password length:", inputPassword?.length);
|
|
81
|
+
|
|
82
|
+
const { username: expectedUsername, passwordHash } = getLoginCredentials();
|
|
83
|
+
console.warn("[SSO] Expected username:", expectedUsername, "passwordHash length:", passwordHash?.length);
|
|
84
|
+
|
|
85
|
+
const isValid = await validateCredentials(inputUsername, inputPassword);
|
|
86
|
+
console.warn("[SSO] Credentials valid:", isValid);
|
|
87
|
+
|
|
88
|
+
if (isValid) {
|
|
89
|
+
console.warn("[SSO] Login successful for user:", inputUsername);
|
|
90
|
+
res.status(200).json({
|
|
91
|
+
name: "ticketCheck",
|
|
92
|
+
returnCode: 0,
|
|
93
|
+
userAttr: {
|
|
94
|
+
displayName: "System Admin",
|
|
95
|
+
username: inputUsername,
|
|
96
|
+
},
|
|
97
|
+
cookieValue: `login_cookie_${Date.now()}`,
|
|
98
|
+
cookieExpire: 7200000, // 2小时
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
console.warn(`[SSO] 登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}`);
|
|
102
|
+
|
|
103
|
+
res.status(200).json({
|
|
104
|
+
name: "ticketCheck",
|
|
105
|
+
returnCode: -1,
|
|
106
|
+
message: "用户名或密码错误",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.warn("[SSO] 登录验证出错:", error);
|
|
111
|
+
res.status(500).json({
|
|
112
|
+
name: "ticketCheck",
|
|
113
|
+
returnCode: -1,
|
|
114
|
+
message: "服务器内部错误",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
package/pages/index.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
2
|
import { Container } from "../client/styled/common";
|
|
3
|
-
import React from "react";
|
|
4
3
|
import { Card, Typography, Divider, Row, Col, Tag } from "antd";
|
|
5
4
|
import { CodeOutlined, BookOutlined, DatabaseOutlined, SettingOutlined } from "@ant-design/icons";
|
|
6
5
|
import { useTranslation } from "next-i18next";
|
|
@@ -160,12 +159,20 @@ const Page = () => {
|
|
|
160
159
|
};
|
|
161
160
|
|
|
162
161
|
export const getServerSideProps = async ({ locale }) => {
|
|
163
|
-
// 确保 locale 有默认值,避免 serverSideTranslations 报错
|
|
164
162
|
const currentLocale = locale || "zh-CN";
|
|
165
163
|
|
|
164
|
+
const path = require("path");
|
|
165
|
+
const i18nConfig = {
|
|
166
|
+
i18n: {
|
|
167
|
+
defaultLocale: "zh-CN",
|
|
168
|
+
locales: ["zh-CN", "en-US", "ja-JP"],
|
|
169
|
+
},
|
|
170
|
+
localePath: path.resolve(process.cwd(), "public/locales"),
|
|
171
|
+
};
|
|
172
|
+
|
|
166
173
|
return {
|
|
167
174
|
props: {
|
|
168
|
-
...(await serverSideTranslations(currentLocale, ["common", "home", "layout"])),
|
|
175
|
+
...(await serverSideTranslations(currentLocale, ["common", "home", "layout"], i18nConfig)),
|
|
169
176
|
},
|
|
170
177
|
};
|
|
171
178
|
};
|
package/pages/login.tsx
CHANGED
|
@@ -2,10 +2,11 @@ import MarkdownIt from "markdown-it";
|
|
|
2
2
|
import _ from "lodash";
|
|
3
3
|
import { LoginContainer } from "../client/styled/common";
|
|
4
4
|
// import getConfig from 'next/config'
|
|
5
|
-
import
|
|
5
|
+
import { useState, useEffect } from "react";
|
|
6
6
|
import { Input, Button, Form, Typography, message } from "antd";
|
|
7
7
|
import { UserOutlined, LockOutlined } from "@ant-design/icons";
|
|
8
8
|
import { directLogin } from "../client/utils/sso";
|
|
9
|
+
import { getLocalApiPrefix } from "../client/utils/common";
|
|
9
10
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
10
11
|
import { useTranslation } from "next-i18next";
|
|
11
12
|
import { useRouter } from "next/router";
|
|
@@ -27,14 +28,18 @@ const renderArr: any = [];
|
|
|
27
28
|
renderArr.push("NSGM");
|
|
28
29
|
|
|
29
30
|
const Page = ({ html }) => {
|
|
30
|
-
const { t } = useTranslation(
|
|
31
|
+
const { t } = useTranslation("login");
|
|
31
32
|
const router = useRouter();
|
|
32
33
|
const [userName, setUserName] = useState("");
|
|
33
34
|
const [userPassword, setUserPassword] = useState("");
|
|
34
35
|
const [mounted, setMounted] = useState(false);
|
|
36
|
+
const [debugInfo, setDebugInfo] = useState("");
|
|
35
37
|
|
|
36
38
|
useEffect(() => {
|
|
37
39
|
setMounted(true);
|
|
40
|
+
// 显示调试信息
|
|
41
|
+
const apiPrefix = getLocalApiPrefix();
|
|
42
|
+
setDebugInfo(`API: ${apiPrefix}`);
|
|
38
43
|
}, []);
|
|
39
44
|
|
|
40
45
|
const createMarkup = () => {
|
|
@@ -47,11 +52,11 @@ const Page = ({ html }) => {
|
|
|
47
52
|
if (!mounted || typeof window === "undefined") return;
|
|
48
53
|
|
|
49
54
|
if (userName === "") {
|
|
50
|
-
message.error(t("login
|
|
55
|
+
message.error(t("login.errors.usernameRequired"));
|
|
51
56
|
return;
|
|
52
57
|
}
|
|
53
58
|
if (userPassword === "") {
|
|
54
|
-
message.error(t("login
|
|
59
|
+
message.error(t("login.errors.passwordRequired"));
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -65,6 +70,7 @@ const Page = ({ html }) => {
|
|
|
65
70
|
// 检查是否是 Promise
|
|
66
71
|
if (result && typeof (result as any).then === "function") {
|
|
67
72
|
(result as Promise<any>).then((loginResult) => {
|
|
73
|
+
setDebugInfo(`Login result: ${JSON.stringify(loginResult)}`);
|
|
68
74
|
if (!loginResult.success) {
|
|
69
75
|
message.error(loginResult.message);
|
|
70
76
|
}
|
|
@@ -72,8 +78,9 @@ const Page = ({ html }) => {
|
|
|
72
78
|
} else {
|
|
73
79
|
// 直接返回的结果
|
|
74
80
|
const syncResult = result as { success: boolean; message?: string };
|
|
81
|
+
setDebugInfo(`Login result: ${JSON.stringify(syncResult)}`);
|
|
75
82
|
if (!syncResult.success) {
|
|
76
|
-
message.error(syncResult.message || t("login
|
|
83
|
+
message.error(syncResult.message || t("login.errors.loginFailed"));
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
};
|
|
@@ -91,15 +98,30 @@ const Page = ({ html }) => {
|
|
|
91
98
|
<div style={{ position: "absolute", top: "20px", right: "20px" }}>
|
|
92
99
|
<LanguageSwitcher />
|
|
93
100
|
</div>
|
|
101
|
+
{debugInfo && (
|
|
102
|
+
<div
|
|
103
|
+
style={{
|
|
104
|
+
position: "absolute",
|
|
105
|
+
top: "60px",
|
|
106
|
+
right: "20px",
|
|
107
|
+
fontSize: "12px",
|
|
108
|
+
color: "#999",
|
|
109
|
+
maxWidth: "300px",
|
|
110
|
+
wordBreak: "break-all",
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{debugInfo}
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
94
116
|
<div dangerouslySetInnerHTML={createMarkup()} />
|
|
95
117
|
<Typography.Title level={3} style={{ textAlign: "center", marginBottom: 24 }}>
|
|
96
|
-
{t("login
|
|
118
|
+
{t("login.title")}
|
|
97
119
|
</Typography.Title>
|
|
98
120
|
<Form layout="vertical" style={{ width: "100%" }}>
|
|
99
121
|
<Form.Item>
|
|
100
122
|
<Input
|
|
101
123
|
prefix={<UserOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
|
102
|
-
placeholder={t("login
|
|
124
|
+
placeholder={t("login.username")}
|
|
103
125
|
size="large"
|
|
104
126
|
value={userName}
|
|
105
127
|
onChange={doChangeName}
|
|
@@ -109,7 +131,7 @@ const Page = ({ html }) => {
|
|
|
109
131
|
<Form.Item>
|
|
110
132
|
<Input.Password
|
|
111
133
|
prefix={<LockOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
|
112
|
-
placeholder={t("login
|
|
134
|
+
placeholder={t("login.password")}
|
|
113
135
|
size="large"
|
|
114
136
|
value={userPassword}
|
|
115
137
|
onChange={doChangePassword}
|
|
@@ -118,7 +140,7 @@ const Page = ({ html }) => {
|
|
|
118
140
|
</Form.Item>
|
|
119
141
|
<Form.Item>
|
|
120
142
|
<Button type="primary" onClick={doLogin} size="large" block>
|
|
121
|
-
{t("login
|
|
143
|
+
{t("login.loginButton")}
|
|
122
144
|
</Button>
|
|
123
145
|
</Form.Item>
|
|
124
146
|
</Form>
|
|
@@ -127,7 +149,6 @@ const Page = ({ html }) => {
|
|
|
127
149
|
};
|
|
128
150
|
|
|
129
151
|
export const getServerSideProps = async ({ locale }) => {
|
|
130
|
-
// 确保 locale 有默认值,避免 serverSideTranslations 报错
|
|
131
152
|
const currentLocale = locale || "zh-CN";
|
|
132
153
|
|
|
133
154
|
// 处理 markdown 内容
|
|
@@ -136,10 +157,19 @@ export const getServerSideProps = async ({ locale }) => {
|
|
|
136
157
|
html += md.render(item);
|
|
137
158
|
});
|
|
138
159
|
|
|
160
|
+
const path = require("path");
|
|
161
|
+
const i18nConfig = {
|
|
162
|
+
i18n: {
|
|
163
|
+
defaultLocale: "zh-CN",
|
|
164
|
+
locales: ["zh-CN", "en-US", "ja-JP"],
|
|
165
|
+
},
|
|
166
|
+
localePath: path.resolve(process.cwd(), "public/locales"),
|
|
167
|
+
};
|
|
168
|
+
|
|
139
169
|
return {
|
|
140
170
|
props: {
|
|
141
171
|
html,
|
|
142
|
-
...(await serverSideTranslations(currentLocale, ["common", "layout", "login"])),
|
|
172
|
+
...(await serverSideTranslations(currentLocale, ["common", "layout", "login"], i18nConfig)),
|
|
143
173
|
},
|
|
144
174
|
};
|
|
145
175
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
2
|
import { ConfigProvider, Modal, Space, Upload, message } from "antd";
|
|
3
3
|
import {
|
|
4
4
|
Container,
|
|
@@ -412,7 +412,21 @@ export async function getServerSideProps(context) {
|
|
|
412
412
|
});
|
|
413
413
|
|
|
414
414
|
const { locale } = context;
|
|
415
|
-
|
|
415
|
+
|
|
416
|
+
const path = require("path");
|
|
417
|
+
const i18nConfig = {
|
|
418
|
+
i18n: {
|
|
419
|
+
defaultLocale: "zh-CN",
|
|
420
|
+
locales: ["zh-CN", "en-US", "ja-JP"],
|
|
421
|
+
},
|
|
422
|
+
localePath: path.resolve(process.cwd(), "public/locales"),
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const translations = await serverSideTranslations(
|
|
426
|
+
locale || "zh-CN",
|
|
427
|
+
["common", "template", "layout", "login"],
|
|
428
|
+
i18nConfig
|
|
429
|
+
);
|
|
416
430
|
|
|
417
431
|
return {
|
|
418
432
|
props: {
|