nsgm-cli 2.1.13 → 2.1.15
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 +394 -156
- package/client/components/Button.tsx +3 -5
- package/client/components/ClientProviders.tsx +29 -0
- package/client/components/LanguageSwitcher.tsx +59 -0
- package/client/components/SSRSafeAntdProvider.tsx +24 -0
- package/client/components/SuppressHydrationWarnings.tsx +55 -0
- package/client/components/__tests__/Button.test.tsx +10 -10
- package/client/layout/index.tsx +153 -185
- package/client/redux/reducers.ts +1 -1
- package/client/redux/store.ts +2 -1
- package/client/redux/template/manage/actions.ts +77 -88
- package/client/redux/template/manage/reducers.ts +25 -37
- package/client/redux/template/manage/types.ts +1 -1
- package/client/service/template/manage.ts +20 -21
- package/client/styled/common.ts +12 -14
- package/client/styled/layout/index.ts +234 -120
- package/client/styled/template/manage.ts +102 -14
- package/client/utils/common.ts +23 -21
- package/client/utils/cookie.ts +18 -19
- package/client/utils/fetch.ts +64 -100
- package/client/utils/i18n.ts +68 -0
- package/client/utils/menu.tsx +42 -23
- package/client/utils/navigation.ts +58 -0
- package/client/utils/sso.ts +74 -84
- package/client/utils/suppressWarnings.ts +32 -0
- package/eslint.config.js +53 -19
- package/generation/README.md +25 -6
- package/generation/__tests__/example.test.js +41 -0
- package/generation/client/redux/reducers.ts +1 -1
- package/generation/client/utils/menu.tsx +36 -23
- package/generation/env +3 -0
- package/generation/env.example +3 -0
- package/generation/eslint.config.js +112 -0
- package/generation/gitignore +6 -1
- package/generation/jest.config.js +40 -0
- package/generation/next.config.js +7 -3
- package/generation/package.json +28 -4
- package/generation/tsconfig.json +6 -19
- package/jest.config.js +23 -6
- package/lib/args.js +9 -1
- package/lib/cli/app.d.ts +28 -0
- package/lib/cli/app.js +99 -0
- package/lib/cli/commands/build.d.ts +2 -0
- package/lib/cli/commands/build.js +29 -0
- package/lib/cli/commands/create.d.ts +2 -0
- package/lib/cli/commands/create.js +113 -0
- package/lib/cli/commands/delete.d.ts +3 -0
- package/lib/cli/commands/delete.js +151 -0
- package/lib/cli/commands/export.d.ts +2 -0
- package/lib/cli/commands/export.js +42 -0
- package/lib/cli/commands/help.d.ts +2 -0
- package/lib/cli/commands/help.js +42 -0
- package/lib/cli/commands/init.d.ts +2 -0
- package/lib/cli/commands/init.js +115 -0
- package/lib/cli/commands/server.d.ts +3 -0
- package/lib/cli/commands/server.js +26 -0
- package/lib/cli/commands/upgrade.d.ts +2 -0
- package/lib/cli/commands/upgrade.js +38 -0
- package/lib/cli/commands/version.d.ts +2 -0
- package/lib/cli/commands/version.js +24 -0
- package/lib/cli/index.d.ts +16 -0
- package/lib/cli/index.js +33 -0
- package/lib/cli/parser.d.ts +22 -0
- package/lib/cli/parser.js +115 -0
- package/lib/cli/registry.d.ts +33 -0
- package/lib/cli/registry.js +81 -0
- package/lib/cli/types/project.d.ts +10 -0
- package/lib/cli/types/project.js +2 -0
- package/lib/cli/types.d.ts +31 -0
- package/lib/cli/types.js +20 -0
- package/lib/cli/utils/console.d.ts +62 -0
- package/lib/cli/utils/console.js +148 -0
- package/lib/cli/utils/index.d.ts +2 -0
- package/lib/cli/utils/index.js +7 -0
- package/lib/cli/utils/prompt.d.ts +83 -0
- package/lib/cli/utils/prompt.js +327 -0
- package/lib/constants.d.ts +65 -0
- package/lib/constants.js +177 -0
- package/lib/generate.d.ts +25 -3
- package/lib/generate.js +98 -621
- package/lib/generate_create.d.ts +9 -0
- package/lib/generate_create.js +329 -0
- package/lib/generate_delete.d.ts +8 -0
- package/lib/generate_delete.js +233 -0
- package/lib/generate_init.d.ts +56 -0
- package/lib/generate_init.js +612 -0
- package/lib/generators/base-generator.d.ts +47 -0
- package/lib/generators/base-generator.js +92 -0
- package/lib/generators/file-generator.d.ts +48 -0
- package/lib/generators/file-generator.js +455 -0
- package/lib/generators/generator-factory.d.ts +20 -0
- package/lib/generators/generator-factory.js +25 -0
- package/lib/generators/i18n-generator.d.ts +51 -0
- package/lib/generators/i18n-generator.js +320 -0
- package/lib/generators/page-generator.d.ts +45 -0
- package/lib/generators/page-generator.js +578 -0
- package/lib/generators/resolver-generator.d.ts +14 -0
- package/lib/generators/resolver-generator.js +342 -0
- package/lib/generators/schema-generator.d.ts +7 -0
- package/lib/generators/schema-generator.js +57 -0
- package/lib/generators/service-generator.d.ts +11 -0
- package/lib/generators/service-generator.js +233 -0
- package/lib/generators/sql-generator.d.ts +8 -0
- package/lib/generators/sql-generator.js +52 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +14 -173
- package/lib/server/csrf.js +9 -16
- package/lib/server/db.js +6 -7
- package/lib/server/graphql.js +5 -6
- package/lib/server/plugins/date.js +1 -1
- package/lib/server/utils/graphql-cache.js +3 -3
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils/project-config.d.ts +5 -0
- package/lib/utils/project-config.js +145 -0
- package/lib/utils.js +1 -1
- package/next-i18next.config.js +18 -0
- package/next.config.js +61 -18
- package/package.json +16 -8
- package/pages/_app.tsx +77 -33
- package/pages/_document.tsx +78 -21
- package/pages/_error.tsx +66 -0
- package/pages/index.tsx +109 -47
- package/pages/login.tsx +66 -41
- package/pages/template/manage.tsx +162 -151
- package/public/fonts/font-awesome.min.css +4 -0
- package/public/fonts/fontawesome-webfont.woff +0 -0
- package/public/fonts/fontawesome-webfont.woff2 +0 -0
- package/public/locales/en-US/common.json +48 -0
- package/public/locales/en-US/home.json +57 -0
- package/public/locales/en-US/layout.json +22 -0
- package/public/locales/en-US/login.json +13 -0
- package/public/locales/en-US/template.json +42 -0
- package/public/locales/ja-JP/common.json +48 -0
- package/public/locales/ja-JP/home.json +57 -0
- package/public/locales/ja-JP/layout.json +22 -0
- package/public/locales/ja-JP/login.json +13 -0
- package/public/locales/ja-JP/template.json +42 -0
- package/public/locales/zh-CN/common.json +48 -0
- package/public/locales/zh-CN/home.json +57 -0
- package/public/locales/zh-CN/layout.json +22 -0
- package/public/locales/zh-CN/login.json +13 -0
- package/public/locales/zh-CN/template.json +42 -0
- package/public/slbhealthcheck.html +1 -1
- package/server/apis/template.js +0 -2
- package/server/utils/validation.js +163 -0
- package/types/i18next.d.ts +10 -0
- package/generation/eslintrc.js +0 -16
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { Select } from 'antd'
|
|
3
|
+
import { useRouter } from 'next/router'
|
|
4
|
+
import { GlobalOutlined } from '@ant-design/icons'
|
|
5
|
+
|
|
6
|
+
const { Option } = Select
|
|
7
|
+
|
|
8
|
+
interface LanguageSwitcherProps {
|
|
9
|
+
style?: React.CSSProperties
|
|
10
|
+
size?: 'small' | 'middle' | 'large'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({ style, size = 'middle' }) => {
|
|
14
|
+
const router = useRouter()
|
|
15
|
+
const [mounted, setMounted] = useState(false)
|
|
16
|
+
const [currentLocale, setCurrentLocale] = useState('zh-CN')
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setMounted(true)
|
|
20
|
+
// 只在客户端获取当前语言
|
|
21
|
+
if (typeof window !== 'undefined' && router.locale) {
|
|
22
|
+
setCurrentLocale(router.locale)
|
|
23
|
+
}
|
|
24
|
+
}, [router.locale])
|
|
25
|
+
|
|
26
|
+
const languages = [
|
|
27
|
+
{ code: 'zh-CN', name: '中文', flag: '🇨🇳' },
|
|
28
|
+
{ code: 'en-US', name: 'English', flag: '🇺🇸' },
|
|
29
|
+
{ code: 'ja-JP', name: '日本語', flag: '🇯🇵' },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const handleLanguageChange = (locale: string) => {
|
|
33
|
+
if (mounted && typeof window !== 'undefined') {
|
|
34
|
+
const { pathname, asPath, query } = router
|
|
35
|
+
router.push({ pathname, query }, asPath, { locale })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Select
|
|
41
|
+
value={currentLocale}
|
|
42
|
+
onChange={handleLanguageChange}
|
|
43
|
+
style={{ minWidth: 120, ...style }}
|
|
44
|
+
size={size}
|
|
45
|
+
suffixIcon={<GlobalOutlined />}
|
|
46
|
+
placeholder="Language"
|
|
47
|
+
disabled={!mounted}
|
|
48
|
+
>
|
|
49
|
+
{languages.map((language) => (
|
|
50
|
+
<Option key={language.code} value={language.code}>
|
|
51
|
+
<span style={{ marginRight: 8 }}>{language.flag}</span>
|
|
52
|
+
{language.name}
|
|
53
|
+
</Option>
|
|
54
|
+
))}
|
|
55
|
+
</Select>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default LanguageSwitcher
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { ConfigProvider } from 'antd'
|
|
3
|
+
|
|
4
|
+
interface SSRSafeAntdProviderProps {
|
|
5
|
+
children: React.ReactNode
|
|
6
|
+
locale?: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SSRSafeAntdProvider: React.FC<SSRSafeAntdProviderProps> = ({ children, locale }) => {
|
|
10
|
+
// 在服务端渲染时,我们仍然使用 ConfigProvider,但使用简化的配置
|
|
11
|
+
return (
|
|
12
|
+
<ConfigProvider
|
|
13
|
+
locale={locale}
|
|
14
|
+
theme={{
|
|
15
|
+
// 确保服务端渲染的一致性
|
|
16
|
+
cssVar: false,
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
</ConfigProvider>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default SSRSafeAntdProvider
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
// 全局抑制 useLayoutEffect 警告的函数
|
|
4
|
+
const suppressUseLayoutEffectWarnings = () => {
|
|
5
|
+
if (typeof window === 'undefined' && process.env.NODE_ENV === 'development') {
|
|
6
|
+
const originalError = console.error
|
|
7
|
+
const originalWarn = console.warn
|
|
8
|
+
|
|
9
|
+
console.error = (...args) => {
|
|
10
|
+
const errorMessage = args[0]
|
|
11
|
+
if (
|
|
12
|
+
typeof errorMessage === 'string' &&
|
|
13
|
+
(errorMessage.includes('useLayoutEffect does nothing on the server') ||
|
|
14
|
+
errorMessage.includes('Warning: useLayoutEffect does nothing on the server'))
|
|
15
|
+
) {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
originalError.apply(console, args)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.warn = (...args) => {
|
|
22
|
+
const warnMessage = args[0]
|
|
23
|
+
if (
|
|
24
|
+
typeof warnMessage === 'string' &&
|
|
25
|
+
(warnMessage.includes('useLayoutEffect does nothing on the server') ||
|
|
26
|
+
warnMessage.includes('Warning: useLayoutEffect does nothing on the server'))
|
|
27
|
+
) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
originalWarn.apply(console, args)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 返回清理函数
|
|
34
|
+
return () => {
|
|
35
|
+
console.error = originalError
|
|
36
|
+
console.warn = originalWarn
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 在模块加载时立即执行
|
|
43
|
+
suppressUseLayoutEffectWarnings()
|
|
44
|
+
|
|
45
|
+
const SuppressHydrationWarnings = () => {
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// 在客户端也抑制这些警告(以防万一)
|
|
48
|
+
const cleanup = suppressUseLayoutEffectWarnings()
|
|
49
|
+
return cleanup
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default SuppressHydrationWarnings
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/components/Button.test.js
|
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
-
import Button from '../Button'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import Button from '../Button'
|
|
4
4
|
|
|
5
5
|
test('renders button with text', () => {
|
|
6
|
-
render(<Button>Click me</Button>)
|
|
7
|
-
expect(screen.getByText('Click me')).toBeInTheDocument()
|
|
8
|
-
})
|
|
6
|
+
render(<Button>Click me</Button>)
|
|
7
|
+
expect(screen.getByText('Click me')).toBeInTheDocument()
|
|
8
|
+
})
|
|
9
9
|
|
|
10
10
|
test('calls onClick when clicked', () => {
|
|
11
|
-
const handleClick = jest.fn()
|
|
12
|
-
render(<Button onClick={handleClick}>Click me</Button>)
|
|
13
|
-
fireEvent.click(screen.getByText('Click me'))
|
|
14
|
-
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
15
|
-
})
|
|
11
|
+
const handleClick = jest.fn()
|
|
12
|
+
render(<Button onClick={handleClick}>Click me</Button>)
|
|
13
|
+
fireEvent.click(screen.getByText('Click me'))
|
|
14
|
+
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
15
|
+
})
|
package/client/layout/index.tsx
CHANGED
|
@@ -1,110 +1,46 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react'
|
|
2
|
-
import { Layout, Menu,
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { Layout, Menu, Dropdown, Space } from 'antd'
|
|
3
|
+
import {
|
|
4
|
+
Container,
|
|
5
|
+
FlexLayout,
|
|
6
|
+
StyledSider,
|
|
7
|
+
SideMenu,
|
|
8
|
+
ContentLayout,
|
|
9
|
+
StyledHeader,
|
|
10
|
+
StyledBreadcrumb,
|
|
11
|
+
StyledContent,
|
|
12
|
+
} from '@/styled/layout'
|
|
5
13
|
import { useRouter } from 'next/router'
|
|
6
14
|
import _ from 'lodash'
|
|
7
|
-
import menuConfig from '@/utils/menu'
|
|
8
|
-
import { logout } from '@/utils/sso'
|
|
15
|
+
import menuConfig, { getMenuConfig } from '@/utils/menu'
|
|
9
16
|
import getConfig from 'next/config'
|
|
10
|
-
import { LogoutOutlined
|
|
17
|
+
import { LogoutOutlined } from '@ant-design/icons'
|
|
18
|
+
import LanguageSwitcher from '@/components/LanguageSwitcher'
|
|
19
|
+
import { useTranslation } from 'next-i18next'
|
|
20
|
+
import { navigateToLogin } from '@/utils/navigation'
|
|
21
|
+
|
|
22
|
+
interface SubMenuItem {
|
|
23
|
+
key: string
|
|
24
|
+
text: string
|
|
25
|
+
url: string
|
|
26
|
+
}
|
|
11
27
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
interface MenuItem {
|
|
29
|
+
key: string
|
|
30
|
+
text: string
|
|
31
|
+
url: string
|
|
32
|
+
icon?: React.ReactNode
|
|
33
|
+
subMenus?: SubMenuItem[]
|
|
34
|
+
}
|
|
15
35
|
|
|
16
36
|
const nextConfig = getConfig()
|
|
17
37
|
const { publicRuntimeConfig } = nextConfig
|
|
18
38
|
const { prefix } = publicRuntimeConfig
|
|
19
39
|
|
|
20
|
-
// styled-components
|
|
21
|
-
const FlexLayout = styled(Layout)`
|
|
22
|
-
display: flex;
|
|
23
|
-
flex: 1;
|
|
24
|
-
`
|
|
25
|
-
const StyledSider = styled(Sider)`
|
|
26
|
-
display: flex;
|
|
27
|
-
flex-direction: column;
|
|
28
|
-
box-shadow: 2px 0 8px -4px rgba(0, 0, 0, 0.1);
|
|
29
|
-
z-index: 5;
|
|
30
|
-
position: relative;
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
const SideMenu = styled(Menu)`
|
|
34
|
-
height: 100%;
|
|
35
|
-
border-right: 0;
|
|
36
|
-
padding: 8px 0;
|
|
37
|
-
`
|
|
38
|
-
|
|
39
|
-
const ContentLayout = styled(Layout)`
|
|
40
|
-
display: flex;
|
|
41
|
-
flex-direction: column;
|
|
42
|
-
flex: 1;
|
|
43
|
-
background: #f5f7fa;
|
|
44
|
-
position: relative;
|
|
45
|
-
z-index: 1;
|
|
46
|
-
`
|
|
47
|
-
const StyledHeader = styled(Header)`
|
|
48
|
-
display: flex;
|
|
49
|
-
align-items: center;
|
|
50
|
-
padding: 0 24px;
|
|
51
|
-
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
|
|
52
|
-
z-index: 11;
|
|
53
|
-
|
|
54
|
-
.logo {
|
|
55
|
-
margin-right: 24px;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.main-menu {
|
|
59
|
-
flex: 1;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.user-actions {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
|
|
66
|
-
.action-icon {
|
|
67
|
-
font-size: 18px;
|
|
68
|
-
color: rgba(255, 255, 255, 0.85);
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
padding: 0 8px;
|
|
71
|
-
transition: color 0.3s;
|
|
72
|
-
|
|
73
|
-
&:hover {
|
|
74
|
-
color: #fff;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.user-dropdown {
|
|
79
|
-
cursor: pointer;
|
|
80
|
-
padding: 0 8px;
|
|
81
|
-
|
|
82
|
-
.username {
|
|
83
|
-
color: rgba(255, 255, 255, 0.85);
|
|
84
|
-
margin-left: 8px;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
`
|
|
89
|
-
const StyledBreadcrumb = styled(Breadcrumb)`
|
|
90
|
-
margin: 16px 24px;
|
|
91
|
-
font-size: 14px;
|
|
92
|
-
`
|
|
93
|
-
const StyledContent = styled(Content)`
|
|
94
|
-
margin: 0 24px 24px;
|
|
95
|
-
padding: 24px;
|
|
96
|
-
background: #fff;
|
|
97
|
-
border-radius: 4px;
|
|
98
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
99
|
-
min-height: calc(100vh - 180px);
|
|
100
|
-
position: relative;
|
|
101
|
-
z-index: 1;
|
|
102
|
-
`
|
|
103
|
-
|
|
104
40
|
const getLocationKey = () => {
|
|
105
|
-
|
|
41
|
+
const result = {
|
|
106
42
|
topMenu: '1',
|
|
107
|
-
slideMenu: '0'
|
|
43
|
+
slideMenu: '0',
|
|
108
44
|
}
|
|
109
45
|
|
|
110
46
|
if (typeof window !== 'undefined') {
|
|
@@ -123,13 +59,11 @@ const getLocationKey = () => {
|
|
|
123
59
|
locationStr = locationStr.split(prefix)[1]
|
|
124
60
|
}
|
|
125
61
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
_.each(menuConfig, (item, index) => {
|
|
62
|
+
_.each(menuConfig, (item) => {
|
|
129
63
|
const { key, url, subMenus } = item
|
|
130
64
|
|
|
131
65
|
if (subMenus) {
|
|
132
|
-
_.each(subMenus, (subItem
|
|
66
|
+
_.each(subMenus, (subItem: MenuItem) => {
|
|
133
67
|
const { key: subKey, url: subUrl } = subItem
|
|
134
68
|
|
|
135
69
|
if (locationStr === subUrl.split('?')[0]) {
|
|
@@ -142,6 +76,7 @@ const getLocationKey = () => {
|
|
|
142
76
|
|
|
143
77
|
return false
|
|
144
78
|
}
|
|
79
|
+
return true
|
|
145
80
|
})
|
|
146
81
|
} else {
|
|
147
82
|
if (url && locationStr === url.split('?')[0]) {
|
|
@@ -149,17 +84,16 @@ const getLocationKey = () => {
|
|
|
149
84
|
return false
|
|
150
85
|
}
|
|
151
86
|
}
|
|
87
|
+
return true
|
|
152
88
|
})
|
|
153
89
|
}
|
|
154
90
|
}
|
|
155
91
|
}
|
|
156
|
-
// console.log('result', result)
|
|
157
92
|
return result
|
|
158
93
|
}
|
|
159
94
|
|
|
160
95
|
const routerPush = (router: any, url: string) => {
|
|
161
|
-
|
|
162
|
-
if (router && url) {
|
|
96
|
+
if (router && url && typeof window !== 'undefined') {
|
|
163
97
|
if (prefix && url.indexOf(prefix) === -1) {
|
|
164
98
|
url = prefix + url
|
|
165
99
|
}
|
|
@@ -168,12 +102,35 @@ const routerPush = (router: any, url: string) => {
|
|
|
168
102
|
}
|
|
169
103
|
|
|
170
104
|
const LayoutComponent = ({ user, children }) => {
|
|
105
|
+
const { t } = useTranslation(['layout', 'common'])
|
|
171
106
|
const router = useRouter()
|
|
172
107
|
const [topMenuKey, setTopMenuKey] = useState('1')
|
|
173
108
|
const [sliderMenuKey, setSliderMenuKey] = useState('1')
|
|
174
109
|
const [collapsed, setCollapsed] = useState(false)
|
|
110
|
+
const [mounted, setMounted] = useState(false)
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
setMounted(true)
|
|
114
|
+
}, [])
|
|
175
115
|
|
|
176
|
-
//
|
|
116
|
+
// 使用翻译后的菜单配置
|
|
117
|
+
const translatedMenuConfig = getMenuConfig(t)
|
|
118
|
+
|
|
119
|
+
// 自定义退出登录函数,保持语言设置
|
|
120
|
+
const handleLogout = () => {
|
|
121
|
+
if (!mounted || typeof window === 'undefined') return
|
|
122
|
+
|
|
123
|
+
// 删除登录相关的 cookie
|
|
124
|
+
const deleteCookie = (name: string) => {
|
|
125
|
+
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
deleteCookie('_cas_nsgm')
|
|
129
|
+
deleteCookie('_cas_nsgm_user')
|
|
130
|
+
|
|
131
|
+
// 跳转到登录页面,保持当前语言
|
|
132
|
+
navigateToLogin(router)
|
|
133
|
+
}
|
|
177
134
|
|
|
178
135
|
useEffect(() => {
|
|
179
136
|
const { topMenu, slideMenu } = getLocationKey()
|
|
@@ -184,23 +141,25 @@ const LayoutComponent = ({ user, children }) => {
|
|
|
184
141
|
const menuItems: any = []
|
|
185
142
|
const menuItemsVertical: any = []
|
|
186
143
|
|
|
187
|
-
_.each(
|
|
144
|
+
_.each(translatedMenuConfig, (item) => {
|
|
188
145
|
const { key, text, url, icon, subMenus } = item
|
|
189
146
|
|
|
190
|
-
if (key) {
|
|
147
|
+
if (key && text && url) {
|
|
191
148
|
const menuObj = {
|
|
192
149
|
label: text,
|
|
193
150
|
key,
|
|
194
151
|
onClick: () => {
|
|
195
|
-
|
|
196
|
-
|
|
152
|
+
if (mounted) {
|
|
153
|
+
routerPush(router, url)
|
|
154
|
+
setTopMenuKey(key)
|
|
197
155
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
156
|
+
if (subMenus) {
|
|
157
|
+
setSliderMenuKey('1')
|
|
158
|
+
} else {
|
|
159
|
+
setSliderMenuKey('0')
|
|
160
|
+
}
|
|
202
161
|
}
|
|
203
|
-
}
|
|
162
|
+
},
|
|
204
163
|
}
|
|
205
164
|
|
|
206
165
|
menuItems.push(menuObj)
|
|
@@ -209,51 +168,57 @@ const LayoutComponent = ({ user, children }) => {
|
|
|
209
168
|
if (subMenus) {
|
|
210
169
|
const subMenusChildren: any = []
|
|
211
170
|
|
|
212
|
-
_.each(subMenus, (subItem
|
|
171
|
+
_.each(subMenus, (subItem: MenuItem) => {
|
|
213
172
|
const { key: subKey, text: subText, url: subUrl } = subItem
|
|
214
173
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const subKeyArrLen = subKeyArr.length
|
|
174
|
+
if (subKey && subText && subUrl) {
|
|
175
|
+
const subMenusChildrenObj = {
|
|
176
|
+
key: `slider_${subKey}`,
|
|
177
|
+
label: subText,
|
|
178
|
+
onClick: () => {
|
|
179
|
+
if (mounted) {
|
|
180
|
+
routerPush(router, subUrl)
|
|
223
181
|
|
|
224
|
-
|
|
182
|
+
const subKeyArr = subKey.split('_')
|
|
183
|
+
const subKeyArrLen = subKeyArr.length
|
|
225
184
|
|
|
226
|
-
|
|
227
|
-
|
|
185
|
+
if (subKeyArrLen >= 1) setTopMenuKey(subKeyArr[0])
|
|
186
|
+
if (subKeyArrLen >= 2) setSliderMenuKey(subKeyArr[1])
|
|
187
|
+
}
|
|
188
|
+
},
|
|
228
189
|
}
|
|
229
|
-
}
|
|
230
190
|
|
|
231
|
-
|
|
191
|
+
subMenusChildren.push(subMenusChildrenObj)
|
|
192
|
+
}
|
|
232
193
|
})
|
|
233
194
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
195
|
+
if (key && text && icon) {
|
|
196
|
+
const subMenuObjVertical = {
|
|
197
|
+
key: `slider_${key}`,
|
|
198
|
+
icon,
|
|
199
|
+
label: text,
|
|
200
|
+
onTitleClick: () => {
|
|
201
|
+
setTopMenuKey(key)
|
|
202
|
+
setSliderMenuKey('1')
|
|
203
|
+
},
|
|
204
|
+
children: subMenusChildren,
|
|
205
|
+
}
|
|
244
206
|
|
|
245
|
-
|
|
207
|
+
menuItemsVertical.push(subMenuObjVertical)
|
|
208
|
+
}
|
|
246
209
|
} else {
|
|
247
|
-
if (key) {
|
|
210
|
+
if (key && text && url) {
|
|
248
211
|
const menuObjVertical = {
|
|
249
212
|
label: text,
|
|
250
213
|
icon,
|
|
251
|
-
key:
|
|
214
|
+
key: `slider_${key}_0`,
|
|
252
215
|
onClick: () => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
216
|
+
if (mounted) {
|
|
217
|
+
routerPush(router, url)
|
|
218
|
+
setTopMenuKey(key)
|
|
219
|
+
setSliderMenuKey('0')
|
|
220
|
+
}
|
|
221
|
+
},
|
|
257
222
|
}
|
|
258
223
|
|
|
259
224
|
menuItemsVertical.push(menuObjVertical)
|
|
@@ -266,7 +231,7 @@ const LayoutComponent = ({ user, children }) => {
|
|
|
266
231
|
<Container>
|
|
267
232
|
<StyledHeader>
|
|
268
233
|
<div className="logo">
|
|
269
|
-
<
|
|
234
|
+
<span className="logo-text">NSGM</span>
|
|
270
235
|
</div>
|
|
271
236
|
<Menu
|
|
272
237
|
theme="dark"
|
|
@@ -278,39 +243,40 @@ const LayoutComponent = ({ user, children }) => {
|
|
|
278
243
|
/>
|
|
279
244
|
<div className="user-actions">
|
|
280
245
|
<Space size={20} align="center">
|
|
281
|
-
<
|
|
246
|
+
<LanguageSwitcher size="small" />
|
|
247
|
+
{/* <Tooltip title="通知">
|
|
282
248
|
<BellOutlined className="action-icon" />
|
|
283
249
|
</Tooltip>
|
|
284
250
|
<Tooltip title="设置">
|
|
285
251
|
<SettingOutlined className="action-icon" />
|
|
286
|
-
</Tooltip>
|
|
252
|
+
</Tooltip> */}
|
|
287
253
|
<Dropdown
|
|
288
254
|
menu={{
|
|
289
255
|
items: [
|
|
290
|
-
{
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
},
|
|
256
|
+
// {
|
|
257
|
+
// key: '1',
|
|
258
|
+
// icon: <UserOutlined />,
|
|
259
|
+
// label: t('layout:layout.userActions.profile'),
|
|
260
|
+
// },
|
|
261
|
+
// {
|
|
262
|
+
// key: '2',
|
|
263
|
+
// icon: <SettingOutlined />,
|
|
264
|
+
// label: t('layout:layout.userActions.settings'),
|
|
265
|
+
// },
|
|
300
266
|
{
|
|
301
267
|
type: 'divider',
|
|
302
268
|
},
|
|
303
269
|
{
|
|
304
270
|
key: '3',
|
|
305
271
|
icon: <LogoutOutlined />,
|
|
306
|
-
label: '
|
|
307
|
-
onClick: () =>
|
|
272
|
+
label: t('layout:layout.userActions.logout'),
|
|
273
|
+
onClick: () => handleLogout(),
|
|
308
274
|
},
|
|
309
275
|
],
|
|
310
276
|
}}
|
|
311
277
|
>
|
|
312
278
|
<Space className="user-dropdown">
|
|
313
|
-
<span className="username">{user?.displayName || '
|
|
279
|
+
<span className="username">{user?.displayName || t('layout:layout.userActions.user')}</span>
|
|
314
280
|
</Space>
|
|
315
281
|
</Dropdown>
|
|
316
282
|
</Space>
|
|
@@ -329,39 +295,41 @@ const LayoutComponent = ({ user, children }) => {
|
|
|
329
295
|
mode="inline"
|
|
330
296
|
defaultSelectedKeys={['slider_1_0']}
|
|
331
297
|
defaultOpenKeys={['slider_1']}
|
|
332
|
-
selectedKeys={[
|
|
333
|
-
openKeys={[
|
|
298
|
+
selectedKeys={[`slider_${topMenuKey}_${sliderMenuKey}`]}
|
|
299
|
+
openKeys={[`slider_${topMenuKey}`]}
|
|
334
300
|
items={menuItemsVertical}
|
|
335
301
|
className="side-menu"
|
|
336
302
|
/>
|
|
337
303
|
</div>
|
|
338
304
|
</StyledSider>
|
|
339
|
-
<ContentLayout className="content-layout">
|
|
340
|
-
<StyledBreadcrumb
|
|
341
|
-
{_.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
305
|
+
<ContentLayout collapsed={collapsed} className="content-layout">
|
|
306
|
+
<StyledBreadcrumb
|
|
307
|
+
items={_.compact(
|
|
308
|
+
_.flatMap(translatedMenuConfig, (item, index) => {
|
|
309
|
+
const { key, text, subMenus } = item
|
|
310
|
+
|
|
311
|
+
if (subMenus) {
|
|
312
|
+
const subItems: any = []
|
|
313
|
+
_.each(subMenus, (subItem: MenuItem, subIndex: number) => {
|
|
314
|
+
const { key: subKey, text: subText } = subItem
|
|
315
|
+
if (subKey === `${topMenuKey}_${sliderMenuKey}`) {
|
|
316
|
+
subItems.push({ title: text, key: `breadcrumb${subIndex}` })
|
|
317
|
+
subItems.push({ title: subText, key: `breadcrumb${subIndex}_sub` })
|
|
318
|
+
return false
|
|
319
|
+
}
|
|
320
|
+
return true
|
|
321
|
+
})
|
|
322
|
+
return subItems
|
|
323
|
+
} else {
|
|
324
|
+
if (key && key === topMenuKey) {
|
|
325
|
+
return { title: text, key: `breadcrumb${index}` }
|
|
352
326
|
}
|
|
353
|
-
})
|
|
354
|
-
return subContent
|
|
355
|
-
} else {
|
|
356
|
-
if (key && key === topMenuKey) {
|
|
357
|
-
return <Breadcrumb.Item key={'breadcrumb' + index}>{text}</Breadcrumb.Item>
|
|
358
327
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
</StyledContent>
|
|
328
|
+
return null
|
|
329
|
+
})
|
|
330
|
+
)}
|
|
331
|
+
/>
|
|
332
|
+
<StyledContent>{children}</StyledContent>
|
|
365
333
|
</ContentLayout>
|
|
366
334
|
</FlexLayout>
|
|
367
335
|
</Container>
|
package/client/redux/reducers.ts
CHANGED
package/client/redux/store.ts
CHANGED
|
@@ -16,6 +16,7 @@ if (reducersKeysLen > 0) {
|
|
|
16
16
|
export type RootState = ReturnType<typeof combineReducer>
|
|
17
17
|
|
|
18
18
|
// 创建一个临时 store 实例来获取正确的 dispatch 类型
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
20
|
const tempStore = configureStore({
|
|
20
21
|
reducer: combineReducer,
|
|
21
22
|
middleware: (getDefaultMiddleware) =>
|
|
@@ -48,7 +49,7 @@ export const initializeStore = (preloadedState?: any): EnhancedStore => {
|
|
|
48
49
|
if (preloadedState && store) {
|
|
49
50
|
_store = initStore({
|
|
50
51
|
...store.getState(),
|
|
51
|
-
...preloadedState
|
|
52
|
+
...preloadedState,
|
|
52
53
|
})
|
|
53
54
|
store = undefined
|
|
54
55
|
}
|