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.
Files changed (41) hide show
  1. package/README.md +40 -0
  2. package/client/components/Button.tsx +0 -2
  3. package/client/layout/index.tsx +2 -4
  4. package/client/utils/common.ts +12 -10
  5. package/client/utils/fetch.ts +1 -1
  6. package/client/utils/menu.tsx +0 -1
  7. package/client/utils/sso.ts +13 -3
  8. package/generation/client/utils/menu.tsx +0 -1
  9. package/jest.config.js +4 -4
  10. package/lib/generate_create.js +9 -0
  11. package/lib/generators/dataloader-generator.d.ts +12 -0
  12. package/lib/generators/dataloader-generator.js +221 -0
  13. package/lib/generators/resolver-generator.d.ts +2 -1
  14. package/lib/generators/resolver-generator.js +117 -24
  15. package/lib/generators/schema-generator.js +1 -0
  16. package/lib/index.js +11 -8
  17. package/lib/server/dataloaders/index.d.ts +38 -0
  18. package/lib/server/dataloaders/index.js +33 -0
  19. package/lib/server/dataloaders/template-dataloader.d.ts +48 -0
  20. package/lib/server/dataloaders/template-dataloader.js +131 -0
  21. package/lib/server/debug/dataloader-debug.d.ts +63 -0
  22. package/lib/server/debug/dataloader-debug.js +192 -0
  23. package/lib/server/graphql.js +9 -0
  24. package/lib/server/utils/dataloader-monitor.d.ts +87 -0
  25. package/lib/server/utils/dataloader-monitor.js +199 -0
  26. package/lib/tsconfig.build.tsbuildinfo +1 -1
  27. package/lib/utils.js +1 -1
  28. package/next-env.d.ts +1 -0
  29. package/next-i18next.config.js +7 -5
  30. package/next.config.js +34 -112
  31. package/package.json +6 -3
  32. package/pages/_app.tsx +7 -7
  33. package/pages/_document.tsx +0 -1
  34. package/pages/_error.tsx +0 -1
  35. package/pages/api/sso/ticketCheck.ts +117 -0
  36. package/pages/index.tsx +10 -3
  37. package/pages/login.tsx +41 -11
  38. package/pages/template/manage.tsx +16 -2
  39. package/server/apis/sso.js +22 -4
  40. package/server/modules/template/resolver.js +101 -21
  41. package/server/modules/template/schema.js +1 -0
package/lib/utils.js CHANGED
@@ -21,7 +21,7 @@ exports.firstUpperCase = firstUpperCase;
21
21
  const mkdirSync = (dirPath) => {
22
22
  if (mkdirFlag) {
23
23
  if (!fs_1.default.existsSync(dirPath)) {
24
- fs_1.default.mkdirSync(dirPath);
24
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
25
25
  }
26
26
  }
27
27
  };
package/next-env.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
+ import "./build/types/routes.d.ts";
3
4
 
4
5
  // NOTE: This file should not be edited
5
6
  // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
@@ -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: './public/locales',
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: true,
14
- serializeConfig: false,
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
- const { i18n } = require('./next-i18next.config')
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
- webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
94
- // 抑制服务端渲染时的 useLayoutEffect 警告
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: 'build',
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.21",
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": "15.4.4",
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 React, { useEffect, useState } from "react";
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
- // 移除 getInitialProps 以启用静态优化
137
- // 如果需要页面级别的数据获取,请在各个页面中使用 getStaticProps 或 getServerSideProps
138
-
139
- export default appWithTranslation(App, nextI18NextConfig);
138
+ // appWithTranslation 会自动从 next.config.js 读取 i18n 配置
139
+ export default appWithTranslation(App);
@@ -1,5 +1,4 @@
1
1
  import Document, { Html, Head, Main, NextScript, DocumentContext } from "next/document";
2
- import React from "react";
3
2
  import { ServerStyleSheet } from "styled-components";
4
3
 
5
4
  const MyDocument = (props) => {
package/pages/_error.tsx CHANGED
@@ -1,4 +1,3 @@
1
- import React from "react";
2
1
  import { Result, Button } from "antd";
3
2
  import { NextPageContext } from "next";
4
3
 
@@ -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 React, { useState, useEffect } from "react";
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(["login"]);
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:login.errors.usernameRequired"));
55
+ message.error(t("login.errors.usernameRequired"));
51
56
  return;
52
57
  }
53
58
  if (userPassword === "") {
54
- message.error(t("login:login.errors.passwordRequired"));
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:login.errors.loginFailed"));
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:login.title")}
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:login.username")}
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:login.password")}
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:login.loginButton")}
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 React, { useState, useEffect } from "react";
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
- const translations = await serverSideTranslations(locale || "zh-CN", ["common", "template", "layout", "login"]);
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: {