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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-toolkits",
3
- "version": "0.0.2",
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.1",
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' | 'onConfirm'>,
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
- : any
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
- <Button
83
+ <Button
85
84
  key="cancel"
86
85
  onClick={() => {
87
86
  closeFn?.()
88
87
  }}
89
- >
90
- 取消
91
- </Button>,
92
- <Button form={id} key="submit" type="primary" htmlType="submit" loading={confirmLoading}>
93
- 确定
94
- </Button>,
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
- ) => React.ReactElement
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: Record<string, string>) {
8
+ export function usePermissions(codes: string[]) {
9
9
  const fetcher = useFetcher()
10
10
 
11
11
  const { data, isLoading } = useSWRImmutable(
12
- Object.keys(codes).length > 0
12
+ codes.length > 0
13
13
  ? {
14
14
  method: 'POST',
15
15
  url: '/api/usystem/user/check',
16
- data: { permissions: Object.values(codes) },
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 Object.keys(codes).reduce(
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 Object.entries(codes).reduce(
31
+ return codes.reduce(
32
32
  (acc, curr) => {
33
- acc[curr[0]] = (res as Record<string, boolean>)[curr[1] as string]
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 ? { [code]: code } : {})
48
+ const { data, isLoading } = usePermissions(code ? [code] : [])
46
49
 
47
50
  if (!code) {
48
51
  return {
@@ -1,8 +1,7 @@
1
1
  import logo from '@/assets/512_orange_nobackground.png'
2
- import { Layout as AntdLayout, Spin, theme } from 'antd'
3
- import type { FC, ReactNode } from 'react'
4
- import { Suspense } from 'react'
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> = props => {
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
  )
@@ -1,7 +1,6 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ import { usePermissions } from '@/hooks'
2
2
  import { useMenuStore } from '@/stores'
3
- import Icon, * as Icons from '@ant-design/icons'
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, ForwardRefExoticComponent, ReactNode } from 'react'
13
- import { useCallback, useEffect, useMemo, useState } from 'react'
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
- Omit<MenuItemType, 'icon'>,
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
- Omit<SubMenuType, 'icon'>,
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
- // const isPass = code
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 [internalItems, setInternalItems] = useState<ItemType<MenuItemType>[]>([])
133
-
134
- useEffect(() => {
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' }}