react-toolkits 0.0.2 → 0.0.4
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +12 -0
- package/dist/index.css +3 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +9 -12
- package/dist/index.esm.js +40 -43
- package/dist/index.esm.js.map +1 -1
- package/package.json +2 -3
- package/src/components/FormModal/hooks.tsx +2 -3
- package/src/components/FormModal/index.tsx +11 -12
- package/src/hooks/use-permission.tsx +10 -7
- package/src/layouts/Layout.tsx +7 -23
- package/src/layouts/NavBar.tsx +25 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-toolkits",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"sideEffects": [
|
|
5
5
|
"**/*.css"
|
|
6
6
|
],
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"@types/react": "^18.2.16",
|
|
40
40
|
"@types/react-dom": "^18.2.7",
|
|
41
41
|
"autoprefixer": "^10.4.14",
|
|
42
|
-
"eslint-import-resolver-typescript": "^3.5.5",
|
|
43
42
|
"postcss": "^8.4.27",
|
|
44
43
|
"prettier": "^3.0.0",
|
|
45
44
|
"react": "^18.2.0",
|
|
@@ -48,7 +47,7 @@
|
|
|
48
47
|
"ts-essentials": "^9.3.2",
|
|
49
48
|
"tsup": "^7.1.0",
|
|
50
49
|
"typescript": "^5.1.6",
|
|
51
|
-
"@flow97/eslint-config-mono": "0.0.
|
|
50
|
+
"@flow97/eslint-config-mono": "0.0.2",
|
|
52
51
|
"tailwind-config": "0.0.1",
|
|
53
52
|
"tsconfig": "0.0.1"
|
|
54
53
|
},
|
|
@@ -5,10 +5,9 @@ import type { Merge } from 'ts-essentials'
|
|
|
5
5
|
import { createPortal } from 'react-dom'
|
|
6
6
|
|
|
7
7
|
export type UseFormModalProps<T> = Merge<
|
|
8
|
-
Omit<FormModalProps<T>, 'open' | 'onCancel' | 'closeFn' | 'children'
|
|
8
|
+
Omit<FormModalProps<T>, 'open' | 'onCancel' | 'closeFn' | 'children'>,
|
|
9
9
|
{
|
|
10
10
|
content: FormModalProps<T>['children']
|
|
11
|
-
onConfirm?: (values: T) => void
|
|
12
11
|
}
|
|
13
12
|
>
|
|
14
13
|
|
|
@@ -34,7 +33,7 @@ export function useFormModal<T extends object>(props: UseFormModalProps<T>) {
|
|
|
34
33
|
|
|
35
34
|
const Modal = useMemo(() => {
|
|
36
35
|
return (
|
|
37
|
-
<FormModal {...restProps} open={open} closeFn={closeModal} title={title} onConfirm={onConfirm}>
|
|
36
|
+
<FormModal ref={formRef} {...restProps} open={open} closeFn={closeModal} title={title} onConfirm={onConfirm}>
|
|
38
37
|
{content}
|
|
39
38
|
</FormModal>
|
|
40
39
|
)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/* eslint-disable react/jsx-indent */
|
|
2
1
|
import type { FormInstance, FormProps, ModalProps } from 'antd'
|
|
3
2
|
import { Button, Form, Modal } from 'antd'
|
|
4
|
-
import type { ForwardedRef } from 'react'
|
|
3
|
+
import type { ForwardedRef, ReactElement } from 'react'
|
|
5
4
|
import { forwardRef, useId, useImperativeHandle, useRef, useState } from 'react'
|
|
6
5
|
|
|
7
6
|
type RenderChildren<T> = (props: {
|
|
@@ -20,7 +19,7 @@ export type RecursivePartial<T> = T extends object
|
|
|
20
19
|
? RecursivePartial<T[P]>
|
|
21
20
|
: T[P]
|
|
22
21
|
}
|
|
23
|
-
:
|
|
22
|
+
: unknown
|
|
24
23
|
|
|
25
24
|
export interface FormModalProps<T>
|
|
26
25
|
extends Pick<ModalProps, 'width' | 'title' | 'open' | 'afterClose' | 'bodyStyle' | 'maskClosable'>,
|
|
@@ -29,7 +28,7 @@ export interface FormModalProps<T>
|
|
|
29
28
|
footer?: ModalProps['footer']
|
|
30
29
|
closeFn?: VoidFunction
|
|
31
30
|
initialValues?: RecursivePartial<T>
|
|
32
|
-
onConfirm?: (values: T) => void
|
|
31
|
+
onConfirm?: (values: T) => Promise<void>
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
export interface FormModalRef<T = object> {
|
|
@@ -81,17 +80,17 @@ const InternalFormModal = <T extends object>(props: FormModalProps<T>, ref: Forw
|
|
|
81
80
|
typeof footer === 'object'
|
|
82
81
|
? footer
|
|
83
82
|
: [
|
|
84
|
-
|
|
83
|
+
<Button
|
|
85
84
|
key="cancel"
|
|
86
85
|
onClick={() => {
|
|
87
86
|
closeFn?.()
|
|
88
87
|
}}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
>
|
|
89
|
+
取消
|
|
90
|
+
</Button>,
|
|
91
|
+
<Button form={id} key="submit" type="primary" htmlType="submit" loading={confirmLoading}>
|
|
92
|
+
确定
|
|
93
|
+
</Button>,
|
|
95
94
|
]
|
|
96
95
|
}
|
|
97
96
|
afterClose={() => {
|
|
@@ -133,6 +132,6 @@ const InternalFormModal = <T extends object>(props: FormModalProps<T>, ref: Forw
|
|
|
133
132
|
|
|
134
133
|
const FormModal = forwardRef(InternalFormModal) as <T extends object>(
|
|
135
134
|
props: FormModalProps<T> & { ref?: ForwardedRef<FormModalRef<T>> },
|
|
136
|
-
) =>
|
|
135
|
+
) => ReactElement
|
|
137
136
|
|
|
138
137
|
export default FormModal
|
|
@@ -5,21 +5,21 @@ export interface PermissionCheckResult {
|
|
|
5
5
|
[k: string]: boolean
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export function usePermissions(codes:
|
|
8
|
+
export function usePermissions(codes: string[]) {
|
|
9
9
|
const fetcher = useFetcher()
|
|
10
10
|
|
|
11
11
|
const { data, isLoading } = useSWRImmutable(
|
|
12
|
-
|
|
12
|
+
codes.length > 0
|
|
13
13
|
? {
|
|
14
14
|
method: 'POST',
|
|
15
15
|
url: '/api/usystem/user/check',
|
|
16
|
-
data: { permissions:
|
|
16
|
+
data: { permissions: codes },
|
|
17
17
|
}
|
|
18
18
|
: null,
|
|
19
19
|
config =>
|
|
20
20
|
fetcher<PermissionCheckResult>(config).then(res => {
|
|
21
21
|
if (res.has_all) {
|
|
22
|
-
return
|
|
22
|
+
return codes.reduce(
|
|
23
23
|
(acc, curr) => {
|
|
24
24
|
acc[curr] = true
|
|
25
25
|
return acc
|
|
@@ -28,21 +28,24 @@ export function usePermissions(codes: Record<string, string>) {
|
|
|
28
28
|
)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
return
|
|
31
|
+
return codes.reduce(
|
|
32
32
|
(acc, curr) => {
|
|
33
|
-
acc[curr
|
|
33
|
+
acc[curr] = (res as Record<string, boolean>)[curr]
|
|
34
34
|
return acc
|
|
35
35
|
},
|
|
36
36
|
{} as Record<string, boolean>,
|
|
37
37
|
)
|
|
38
38
|
}),
|
|
39
|
+
{
|
|
40
|
+
shouldRetryOnError: false,
|
|
41
|
+
},
|
|
39
42
|
)
|
|
40
43
|
|
|
41
44
|
return { data, isLoading }
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export function usePermission(code?: string) {
|
|
45
|
-
const { data, isLoading } = usePermissions(code ?
|
|
48
|
+
const { data, isLoading } = usePermissions(code ? [code] : [])
|
|
46
49
|
|
|
47
50
|
if (!code) {
|
|
48
51
|
return {
|
package/src/layouts/Layout.tsx
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import logo from '@/assets/512_orange_nobackground.png'
|
|
2
|
-
import { Layout as AntdLayout,
|
|
3
|
-
import type { FC, ReactNode } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
import { Link, Outlet } from 'react-router-dom'
|
|
2
|
+
import { Layout as AntdLayout, theme } from 'antd'
|
|
3
|
+
import type { FC, PropsWithChildren, ReactNode } from 'react'
|
|
4
|
+
import { Link } from 'react-router-dom'
|
|
6
5
|
import type { ItemType2 } from './NavBar'
|
|
7
6
|
import NavBar from './NavBar'
|
|
8
7
|
|
|
@@ -14,8 +13,8 @@ export interface LayoutProps {
|
|
|
14
13
|
header?: ReactNode
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
const Layout: FC<LayoutProps
|
|
18
|
-
const { title, items, header } = props
|
|
16
|
+
const Layout: FC<PropsWithChildren<LayoutProps>> = props => {
|
|
17
|
+
const { title, items, header, children } = props
|
|
19
18
|
const {
|
|
20
19
|
token: { colorBgContainer, colorBorder },
|
|
21
20
|
} = theme.useToken()
|
|
@@ -38,7 +37,7 @@ const Layout: FC<LayoutProps> = props => {
|
|
|
38
37
|
theme="light"
|
|
39
38
|
>
|
|
40
39
|
<div className="flex items-end px-6 py-4">
|
|
41
|
-
<img src={logo} alt="logo" className="w-8" />
|
|
40
|
+
<img src={logo} alt="logo" className="w-8 h-8" />
|
|
42
41
|
<Link className="font-bold text-lg ml-2" to="/">
|
|
43
42
|
{title}
|
|
44
43
|
</Link>
|
|
@@ -57,22 +56,7 @@ const Layout: FC<LayoutProps> = props => {
|
|
|
57
56
|
>
|
|
58
57
|
{header}
|
|
59
58
|
</Header>
|
|
60
|
-
<Content className="p-6 overflow-auto bg-gray-50">
|
|
61
|
-
<Suspense
|
|
62
|
-
fallback={
|
|
63
|
-
<Spin
|
|
64
|
-
style={{
|
|
65
|
-
display: 'flex',
|
|
66
|
-
justifyContent: 'center',
|
|
67
|
-
alignItems: 'center',
|
|
68
|
-
height: '50vh',
|
|
69
|
-
}}
|
|
70
|
-
/>
|
|
71
|
-
}
|
|
72
|
-
>
|
|
73
|
-
<Outlet />
|
|
74
|
-
</Suspense>
|
|
75
|
-
</Content>
|
|
59
|
+
<Content className="p-6 overflow-auto bg-gray-50">{children}</Content>
|
|
76
60
|
</AntdLayout>
|
|
77
61
|
</AntdLayout>
|
|
78
62
|
)
|
package/src/layouts/NavBar.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { usePermissions } from '@/hooks'
|
|
2
2
|
import { useMenuStore } from '@/stores'
|
|
3
|
-
import
|
|
4
|
-
import { Menu } from 'antd'
|
|
3
|
+
import { Menu, Spin } from 'antd'
|
|
5
4
|
import type {
|
|
6
5
|
ItemType,
|
|
7
6
|
MenuDividerType,
|
|
@@ -9,26 +8,24 @@ import type {
|
|
|
9
8
|
MenuItemType,
|
|
10
9
|
SubMenuType,
|
|
11
10
|
} from 'antd/es/menu/hooks/useItems'
|
|
12
|
-
import type { FC,
|
|
13
|
-
import { useCallback, useEffect, useMemo
|
|
11
|
+
import type { FC, ReactNode } from 'react'
|
|
12
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
14
13
|
import { Link, useLocation } from 'react-router-dom'
|
|
15
14
|
import type { Merge } from 'ts-essentials'
|
|
16
15
|
|
|
17
16
|
// 扩展 antd Menu 的类型,使其支持一些我们想要的自定义字段。
|
|
18
17
|
type MenuItemType2 = Merge<
|
|
19
|
-
|
|
18
|
+
MenuItemType,
|
|
20
19
|
{
|
|
21
20
|
code /** 权限编号 **/?: string
|
|
22
21
|
route /** 前端路由地址 **/?: string
|
|
23
|
-
icon?: string
|
|
24
22
|
}
|
|
25
23
|
>
|
|
26
24
|
|
|
27
25
|
type SubMenuType2 = Merge<
|
|
28
|
-
|
|
26
|
+
SubMenuType,
|
|
29
27
|
{
|
|
30
28
|
children?: ItemType2[]
|
|
31
|
-
icon?: string
|
|
32
29
|
}
|
|
33
30
|
>
|
|
34
31
|
|
|
@@ -48,12 +45,7 @@ const withLink = (label?: ReactNode, route?: string): ReactNode => {
|
|
|
48
45
|
return label
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
* 转换导航配置,主要做了以下几件事情
|
|
53
|
-
* 1. 用 Link 元素包装 route
|
|
54
|
-
* 2. 收集 code,用于权限判断
|
|
55
|
-
*/
|
|
56
|
-
function transformItems(items: ItemType2[]) {
|
|
48
|
+
function transformItems(items: ItemType2[], permissions?: Record<string, boolean>) {
|
|
57
49
|
const result: ItemType[] = []
|
|
58
50
|
|
|
59
51
|
for (let i = 0; i < items.length; i++) {
|
|
@@ -62,35 +54,20 @@ function transformItems(items: ItemType2[]) {
|
|
|
62
54
|
} else if ((items[i] as MenuDividerType).type === 'divider') {
|
|
63
55
|
result[i] = { ...items[i] } as MenuDividerType
|
|
64
56
|
} else {
|
|
65
|
-
// 引入 icon
|
|
66
|
-
const iconName = (items[i] as MenuItemType2 | SubMenuType2).icon
|
|
67
|
-
|
|
68
57
|
if ((items[i] as SubMenuType2 | MenuItemGroupType2).children) {
|
|
69
58
|
const { children, ...restProps } = items[i] as SubMenuType2 | MenuItemGroupType2
|
|
70
59
|
result[i] = {
|
|
71
60
|
...restProps,
|
|
72
|
-
children: transformItems(children ?? []),
|
|
73
|
-
icon: iconName ? <Icon component={(Icons as any)[iconName] as ForwardRefExoticComponent<any>} /> : null,
|
|
61
|
+
children: transformItems(children ?? [], permissions),
|
|
74
62
|
} as SubMenuType | MenuItemGroupType
|
|
75
63
|
} else {
|
|
76
64
|
const { route, label, code, ...restProps } = items[i] as MenuItemType2
|
|
77
|
-
|
|
78
|
-
// ? await httpClient.post<PermissionCheckResult>('/usystem/user/check', { permissions: [code] }).then(res => {
|
|
79
|
-
// if (res.has_all) {
|
|
80
|
-
// return true
|
|
81
|
-
// }
|
|
82
|
-
//
|
|
83
|
-
// return res[code] ?? false
|
|
84
|
-
// })
|
|
85
|
-
// : true
|
|
86
|
-
|
|
87
|
-
const isPass = true
|
|
65
|
+
const isPass = !code || !permissions || permissions[code]
|
|
88
66
|
|
|
89
67
|
result[i] = isPass
|
|
90
68
|
? ({
|
|
91
69
|
...restProps,
|
|
92
70
|
label: withLink(label, route),
|
|
93
|
-
icon: iconName ? <Icon component={(Icons as any)[iconName] as ForwardRefExoticComponent<any>} /> : null,
|
|
94
71
|
} as MenuItemType)
|
|
95
72
|
: null
|
|
96
73
|
}
|
|
@@ -129,11 +106,9 @@ const NavBar: FC<NavBarProps> = props => {
|
|
|
129
106
|
const { items } = props
|
|
130
107
|
const location = useLocation()
|
|
131
108
|
const flattenItems = useMemo(() => flatItems(items ?? []), [items])
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
setInternalItems(transformItems(items ?? []))
|
|
136
|
-
}, [items])
|
|
109
|
+
const codes = flattenItems.map(item => item.code).filter(Boolean) as string[]
|
|
110
|
+
const { data: permissions, isLoading } = usePermissions(codes)
|
|
111
|
+
const internalItems = useMemo(() => transformItems(items ?? [], permissions), [items, permissions])
|
|
137
112
|
|
|
138
113
|
const openKeys = useMenuStore(state => state.openKeys)
|
|
139
114
|
const selectedKeys = useMenuStore(state => state.selectedKeys)
|
|
@@ -161,6 +136,19 @@ const NavBar: FC<NavBarProps> = props => {
|
|
|
161
136
|
}
|
|
162
137
|
}, [flattenItems, location, setOpenKeys, setSelectedKeys])
|
|
163
138
|
|
|
139
|
+
if (isLoading) {
|
|
140
|
+
return (
|
|
141
|
+
<Spin
|
|
142
|
+
style={{
|
|
143
|
+
display: 'flex',
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
height: 'calc(100vh - 64px)',
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
164
152
|
return (
|
|
165
153
|
<Menu
|
|
166
154
|
style={{ borderRight: 'none' }}
|