@wzyjs/uis 0.3.27 → 0.3.29

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 (29) hide show
  1. package/package.json +2 -3
  2. package/src/antd/pro/FoldCard/index.tsx +131 -0
  3. package/src/antd/pro/{Input/components/Range.tsx → RangeInput/index.tsx} +2 -2
  4. package/src/antd/pro/Tabs/index.tsx +135 -0
  5. package/src/antd/pro/buttons/ButtonGroup.tsx +26 -0
  6. package/src/antd/pro/{Button/components/Confirm.tsx → buttons/ConfirmButton.tsx} +2 -2
  7. package/src/antd/pro/{Button/components/Copy.tsx → buttons/CopyButton.tsx} +2 -2
  8. package/src/antd/pro/{Button/components/Drawer.tsx → buttons/DrawerButton.tsx} +2 -2
  9. package/src/antd/pro/buttons/index.tsx +4 -0
  10. package/src/antd/pro/index.ts +4 -9
  11. package/src/components/Crud/components/CreateUpdate/index.tsx +122 -42
  12. package/src/components/Crud/components/Provider/index.tsx +53 -43
  13. package/src/components/DynamicSelect/index.tsx +2 -0
  14. package/src/components/DynamicSelect/types.ts +8 -0
  15. package/src/components/DynamicSelect/utils.ts +2 -0
  16. package/src/components/index.ts +0 -1
  17. package/src/antd/pro/Alert/index.tsx +0 -24
  18. package/src/antd/pro/Button/components/Group.tsx +0 -26
  19. package/src/antd/pro/Button/index.tsx +0 -11
  20. package/src/antd/pro/Card/index.tsx +0 -92
  21. package/src/antd/pro/Collapse/components/Item.tsx +0 -30
  22. package/src/antd/pro/Collapse/index.tsx +0 -27
  23. package/src/antd/pro/Image/index.tsx +0 -17
  24. package/src/antd/pro/Input/index.tsx +0 -61
  25. package/src/antd/pro/Popconfirm/index.tsx +0 -16
  26. package/src/antd/pro/Radio/components/Cancel.tsx +0 -30
  27. package/src/antd/pro/Radio/index.tsx +0 -7
  28. package/src/antd/pro/Space/index.tsx +0 -15
  29. package/src/components/Fold/index.tsx +0 -52
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wzyjs/uis",
3
- "version": "0.3.27",
3
+ "version": "0.3.29",
4
4
  "description": "description",
5
5
  "author": "wzy",
6
6
  "sideEffects": false,
@@ -39,7 +39,6 @@
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@wzyjs/hooks": "^0.2.37",
42
- "@wzyjs/types": "^0.2.37",
43
42
  "@wzyjs/utils": "^0.2.37",
44
43
  "antd": "^6.0.0"
45
44
  },
@@ -47,7 +46,7 @@
47
46
  "@types/js-beautify": "^1.14.3",
48
47
  "@types/react-grid-layout": "1.3.5"
49
48
  },
50
- "gitHead": "f9e5dcf56fea3b93a2614ad468ecb466e5da7945",
49
+ "gitHead": "a78702bb9367a10779eaa376ed70834d8b42b5a8",
51
50
  "publishConfig": {
52
51
  "access": "public"
53
52
  }
@@ -0,0 +1,131 @@
1
+ 'use client'
2
+
3
+ import React, { useState, useRef, useEffect, forwardRef, CSSProperties } from 'react'
4
+ import { Card, Button, CardProps } from 'antd'
5
+ import { DownOutlined, UpOutlined } from '@ant-design/icons'
6
+
7
+ export interface FoldCardProps extends CardProps {
8
+ max?: number; // 默认折叠高度,可根据需要调整
9
+ buttonStyle?: CSSProperties;
10
+ defaultCollapsed?: boolean;
11
+ showButtonOnHover?: boolean;
12
+ }
13
+
14
+ export const FoldCard = forwardRef((props: FoldCardProps, ref: any) => {
15
+ const {
16
+ max,
17
+ buttonStyle,
18
+ defaultCollapsed = true,
19
+ showButtonOnHover = true,
20
+ children,
21
+ style,
22
+ onMouseEnter,
23
+ onMouseLeave,
24
+ ...rest
25
+ } = props
26
+
27
+ const maxHeight = max ?? 300
28
+
29
+ const [collapsed, setCollapsed] = useState(defaultCollapsed)
30
+ const [hovered, setHovered] = useState(false)
31
+ const [isOverflow, setIsOverflow] = useState(false)
32
+ const contentRef = useRef<HTMLDivElement>(null)
33
+
34
+ const measureOverflow = () => {
35
+ const contentEl = contentRef.current
36
+ if (!contentEl) {
37
+ return
38
+ }
39
+
40
+ setIsOverflow(contentEl.scrollHeight > maxHeight)
41
+ }
42
+
43
+ const toggleCollapse = (ev: React.MouseEvent<HTMLElement>) => {
44
+ ev.stopPropagation()
45
+ setCollapsed(value => !value)
46
+ }
47
+
48
+ useEffect(() => {
49
+ measureOverflow()
50
+
51
+ const contentEl = contentRef.current
52
+ if (!contentEl || typeof ResizeObserver === 'undefined') {
53
+ return
54
+ }
55
+
56
+ const observer = new ResizeObserver(measureOverflow)
57
+ observer.observe(contentEl)
58
+ return () => observer.disconnect()
59
+ }, [children, maxHeight])
60
+
61
+ const contentStyle: CSSProperties = {
62
+ maxHeight: collapsed ? maxHeight : undefined,
63
+ transition: 'max-height 0.3s ease',
64
+ overflow: collapsed ? 'hidden' : 'visible',
65
+ }
66
+
67
+ const hoverButtonContainerStyle: CSSProperties = {
68
+ position: 'absolute',
69
+ bottom: hovered ? 10 : -40,
70
+ left: '50%',
71
+ transform: 'translateX(-50%)',
72
+ transition: 'bottom 0.3s ease, opacity 0.3s ease',
73
+ display: 'flex',
74
+ justifyContent: 'center',
75
+ width: '100%',
76
+ pointerEvents: 'none',
77
+ opacity: hovered && isOverflow ? 1 : 0,
78
+ }
79
+
80
+ const inlineButtonContainerStyle: CSSProperties = {
81
+ textAlign: 'center',
82
+ marginTop: 8,
83
+ }
84
+
85
+ const finalButtonStyle: CSSProperties = {
86
+ pointerEvents: 'auto',
87
+ border: 'none',
88
+ boxShadow: showButtonOnHover ? '0 2px 8px rgba(0, 0, 0, 0.15)' : undefined,
89
+ ...buttonStyle,
90
+ }
91
+
92
+ const button = (
93
+ <Button
94
+ size='small'
95
+ icon={collapsed ? <DownOutlined /> : <UpOutlined />}
96
+ onClick={toggleCollapse}
97
+ style={finalButtonStyle}
98
+ />
99
+ )
100
+
101
+ return (
102
+ <Card
103
+ ref={ref}
104
+ style={{ position: 'relative', overflow: 'hidden', ...style }}
105
+ onMouseEnter={(ev) => {
106
+ setHovered(true)
107
+ onMouseEnter?.(ev)
108
+ }}
109
+ onMouseLeave={(ev) => {
110
+ setHovered(false)
111
+ onMouseLeave?.(ev)
112
+ }}
113
+ {...rest}
114
+ >
115
+ <div style={contentStyle} ref={contentRef}>
116
+ {children}
117
+ </div>
118
+ {isOverflow && (
119
+ showButtonOnHover ? (
120
+ <div style={hoverButtonContainerStyle}>
121
+ {button}
122
+ </div>
123
+ ) : (
124
+ <div style={inlineButtonContainerStyle}>
125
+ {button}
126
+ </div>
127
+ )
128
+ )}
129
+ </Card>
130
+ )
131
+ })
@@ -4,7 +4,7 @@ import React from 'react'
4
4
  import { InputNumber, InputNumberProps, Space } from 'antd'
5
5
  import { _ } from '@wzyjs/utils/web'
6
6
 
7
- export interface RangeProps extends Omit<InputNumberProps, 'value' | 'onChange'> {
7
+ export interface RangeInputProps extends Omit<InputNumberProps, 'value' | 'onChange'> {
8
8
  value?: number[];
9
9
  min?: number;
10
10
  max?: number;
@@ -12,7 +12,7 @@ export interface RangeProps extends Omit<InputNumberProps, 'value' | 'onChange'>
12
12
  }
13
13
 
14
14
  // 最小值 最大值 的输入框
15
- export const Range = (props: RangeProps) => {
15
+ export const RangeInput = (props: RangeInputProps) => {
16
16
  const { value = [], min, max, onChange } = props
17
17
 
18
18
  const onNumberChange = (index: 0 | 1, v: number | null) => {
@@ -0,0 +1,135 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import { Tabs, type TabsProps } from 'antd'
5
+
6
+ type SyncMode = 'replace' | 'push'
7
+
8
+ export interface TabsProProps extends TabsProps {
9
+ syncToUrl?: boolean
10
+ queryKey?: string
11
+ historyMode?: SyncMode
12
+ }
13
+
14
+ const getItemKeys = (items?: TabsProps['items']): string[] => {
15
+ return (items || [])
16
+ .map(item => item?.key)
17
+ .filter((key): key is string => typeof key === 'string' && !!key)
18
+ }
19
+
20
+ const getUrlTabKey = (queryKey: string): string | undefined => {
21
+ if (typeof window === 'undefined') {
22
+ return undefined
23
+ }
24
+
25
+ const value = new URLSearchParams(window.location.search).get(queryKey)
26
+ return value || undefined
27
+ }
28
+
29
+ const updateUrlTabKey = (queryKey: string, key: string, mode: SyncMode) => {
30
+ if (typeof window === 'undefined') {
31
+ return
32
+ }
33
+
34
+ const url = new URL(window.location.href)
35
+ url.searchParams.set(queryKey, key)
36
+
37
+ const nextUrl = `${url.pathname}${url.search}${url.hash}`
38
+ if (mode === 'push') {
39
+ window.history.pushState(window.history.state, '', nextUrl)
40
+ return
41
+ }
42
+
43
+ window.history.replaceState(window.history.state, '', nextUrl)
44
+ }
45
+
46
+ export const TabsPro = (props: TabsProProps) => {
47
+ const {
48
+ syncToUrl = false,
49
+ queryKey = 'tab',
50
+ historyMode = 'replace',
51
+ activeKey,
52
+ defaultActiveKey,
53
+ items,
54
+ onChange,
55
+ ...rest
56
+ } = props
57
+
58
+ const itemKeys = React.useMemo(() => getItemKeys(items), [items])
59
+
60
+ const initialKey = React.useMemo(() => {
61
+ const urlKey = syncToUrl ? getUrlTabKey(queryKey) : undefined
62
+ if (urlKey && itemKeys.includes(urlKey)) {
63
+ return urlKey
64
+ }
65
+
66
+ if (typeof defaultActiveKey === 'string' && itemKeys.includes(defaultActiveKey)) {
67
+ return defaultActiveKey
68
+ }
69
+
70
+ return itemKeys[0]
71
+ }, [defaultActiveKey, itemKeys, queryKey, syncToUrl])
72
+
73
+ const [innerActiveKey, setInnerActiveKey] = React.useState<string | undefined>(initialKey)
74
+
75
+ const mergedActiveKey = typeof activeKey === 'string' ? activeKey : innerActiveKey
76
+
77
+ React.useEffect(() => {
78
+ if (typeof activeKey === 'string') {
79
+ return
80
+ }
81
+
82
+ setInnerActiveKey(prev => {
83
+ if (prev && itemKeys.includes(prev)) {
84
+ return prev
85
+ }
86
+ return initialKey
87
+ })
88
+ }, [activeKey, initialKey, itemKeys])
89
+
90
+ React.useEffect(() => {
91
+ if (!syncToUrl || !mergedActiveKey) {
92
+ return
93
+ }
94
+
95
+ updateUrlTabKey(queryKey, mergedActiveKey, historyMode)
96
+ }, [historyMode, mergedActiveKey, queryKey, syncToUrl])
97
+
98
+ React.useEffect(() => {
99
+ if (!syncToUrl) {
100
+ return
101
+ }
102
+
103
+ const onPopState = () => {
104
+ const urlKey = getUrlTabKey(queryKey)
105
+ if (!urlKey || !itemKeys.includes(urlKey)) {
106
+ return
107
+ }
108
+
109
+ if (typeof activeKey !== 'string') {
110
+ setInnerActiveKey(urlKey)
111
+ }
112
+
113
+ onChange?.(urlKey)
114
+ }
115
+
116
+ window.addEventListener('popstate', onPopState)
117
+ return () => {
118
+ window.removeEventListener('popstate', onPopState)
119
+ }
120
+ }, [activeKey, itemKeys, onChange, queryKey, syncToUrl])
121
+
122
+ return (
123
+ <Tabs
124
+ {...rest}
125
+ items={items}
126
+ activeKey={mergedActiveKey}
127
+ onChange={key => {
128
+ if (typeof activeKey !== 'string') {
129
+ setInnerActiveKey(key)
130
+ }
131
+ onChange?.(key)
132
+ }}
133
+ />
134
+ )
135
+ }
@@ -0,0 +1,26 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import { Space } from 'antd'
5
+ import { ConfirmButton, ConfirmButtonProps } from './ConfirmButton'
6
+
7
+ export interface ButtonGroupProps {
8
+ list?: (ConfirmButtonProps & { visible?: boolean, title?: string })[]
9
+ }
10
+
11
+ // 1. 按钮组
12
+ export const ButtonGroup = (props: ButtonGroupProps) => {
13
+ const { list = [] } = props
14
+
15
+ if (!list?.length) {
16
+ return null
17
+ }
18
+
19
+ return (
20
+ <Space.Compact>
21
+ {list.filter(item => item.visible).map((item, index) => (
22
+ <ConfirmButton key={index} {...item}>{item.title}</ConfirmButton>
23
+ ))}
24
+ </Space.Compact>
25
+ )
26
+ }
@@ -4,12 +4,12 @@ import React from 'react'
4
4
  import { _ } from '@wzyjs/utils/web'
5
5
  import { Button, Popconfirm, PopconfirmProps, ButtonProps } from 'antd'
6
6
 
7
- export interface ConfirmProps extends PopconfirmProps {
7
+ export interface ConfirmButtonProps extends PopconfirmProps {
8
8
  btnProps?: ButtonProps
9
9
  }
10
10
 
11
11
  // 1. 给按钮增加了确认功能
12
- export const Confirm = (props: ConfirmProps) => {
12
+ export const ConfirmButton = (props: ConfirmButtonProps) => {
13
13
  const { children, btnProps } = props
14
14
 
15
15
  return (
@@ -8,12 +8,12 @@ import { CheckOutlined } from '@ant-design/icons'
8
8
  import { copy, readClipboard } from '@wzyjs/utils/web'
9
9
  import { useControllableValue } from '@wzyjs/hooks/web'
10
10
 
11
- export interface CopyProps extends ButtonProps {
11
+ export interface CopyButtonProps extends ButtonProps {
12
12
  value?: string,
13
13
  canPaste?: boolean,
14
14
  }
15
15
 
16
- export const Copy = (props: CopyProps) => {
16
+ export const CopyButton = (props: CopyButtonProps) => {
17
17
  const [icon, setIcon] = useState<ReactNode>(null)
18
18
  const { message } = App.useApp()
19
19
 
@@ -4,12 +4,12 @@ import React, { useState } from 'react'
4
4
  import { _ } from '@wzyjs/utils/web'
5
5
  import { Button, Drawer as AntdDrawer, DrawerProps as AntdDrawerProps, ButtonProps } from 'antd'
6
6
 
7
- export interface DrawerProps extends AntdDrawerProps {
7
+ export interface DrawerButtonProps extends AntdDrawerProps {
8
8
  btnProps?: ButtonProps
9
9
  defaultOpen?: boolean
10
10
  }
11
11
 
12
- export const Drawer = (props: DrawerProps) => {
12
+ export const DrawerButton = (props: DrawerButtonProps) => {
13
13
  const { children, defaultOpen, btnProps } = props
14
14
  const [open, setOpen] = useState<boolean>(defaultOpen ?? false)
15
15
 
@@ -0,0 +1,4 @@
1
+ export * from './ButtonGroup'
2
+ export * from './ConfirmButton'
3
+ export * from './CopyButton'
4
+ export * from './DrawerButton'
@@ -1,10 +1,5 @@
1
- export * from './Alert'
2
- export * from './Button'
3
- export * from './Card'
4
- export * from './Collapse'
5
- export * from './Image'
6
- export * from './Input'
7
- export * from './Radio'
8
- export * from './Space'
1
+ export * from './buttons'
2
+ export * from './FoldCard'
3
+ export * from './RangeInput'
4
+ export * from './Tabs'
9
5
  export * from './Typography'
10
- export * from './Popconfirm'
@@ -1,13 +1,50 @@
1
+ 'use client'
2
+
3
+ import { useMemo, useState } from 'react'
1
4
  import { Button, type DrawerProps, type ModalProps } from 'antd'
2
5
  import { PlusOutlined, EditOutlined } from '@ant-design/icons'
3
6
  import { BetaSchemaForm } from '@ant-design/pro-components'
4
7
 
5
- import type { CreateUpdateProps } from '../../types'
8
+ import type { CreateUpdateProps, ProColumnsPro } from '../../types'
9
+
10
+ const isActionColumn = (column: ProColumnsPro) => (
11
+ column.valueType === 'option'
12
+ || column.key === 'option'
13
+ || column.key === 'action'
14
+ || column.title === '操作'
15
+ )
16
+
17
+ const getFormColumns = (columns: ProColumnsPro[]) => {
18
+ return columns
19
+ .filter(column => !column.hideInForm && !isActionColumn(column) && column.key !== '__drag_sort__')
20
+ .map((column) => {
21
+ const {
22
+ fixed,
23
+ sorter,
24
+ sortOrder,
25
+ defaultSortOrder,
26
+ filters,
27
+ onFilter,
28
+ onCell,
29
+ onHeaderCell,
30
+ ellipsis,
31
+ copyable,
32
+ render,
33
+ renderText,
34
+ width,
35
+ ...formColumn
36
+ } = column as ProColumnsPro & Record<string, unknown>
37
+
38
+ return formColumn as ProColumnsPro
39
+ })
40
+ }
6
41
 
7
42
  export const CreateUpdate = (props: CreateUpdateProps) => {
8
43
  const { crud, columns, config, record } = props
9
44
 
10
45
  const isEdit = !!record
46
+ const [open, setOpen] = useState(false)
47
+ const [formRendered, setFormRendered] = useState(false)
11
48
 
12
49
  const {
13
50
  mode = 'drawer',
@@ -19,20 +56,49 @@ export const CreateUpdate = (props: CreateUpdateProps) => {
19
56
  width,
20
57
  } = config
21
58
 
22
- const { drawerProps, modalProps, ...formSafeProps } = formProps as unknown as { drawerProps?: DrawerProps; modalProps?: ModalProps } & Record<string, unknown>
59
+ const {
60
+ drawerProps,
61
+ modalProps,
62
+ initialValues,
63
+ onOpenChange,
64
+ onVisibleChange,
65
+ open: _open,
66
+ visible: _visible,
67
+ ...formSafeProps
68
+ } = formProps as unknown as {
69
+ drawerProps?: DrawerProps
70
+ modalProps?: ModalProps
71
+ initialValues?: Record<string, unknown>
72
+ onOpenChange?: (open: boolean) => void
73
+ onVisibleChange?: (open: boolean) => void
74
+ open?: boolean
75
+ visible?: boolean
76
+ } & Record<string, unknown>
23
77
 
24
78
  const layoutType = mode === 'modal' ? 'ModalForm' : 'DrawerForm'
25
79
 
26
- // const finalDrawerProps: DrawerProps | undefined = layoutType === 'DrawerForm'
27
- // ? { forceRender: false, ...drawerProps }
28
- // : undefined
29
- //
30
- // const finalModalProps: ModalProps | undefined = layoutType === 'ModalForm'
31
- // ? { forceRender: false, ...modalProps }
32
- // : undefined
80
+ const formColumns = useMemo(() => getFormColumns(columns), [columns])
81
+
82
+ const handleOpenChange = (nextOpen: boolean) => {
83
+ setOpen(nextOpen)
84
+ if (nextOpen) {
85
+ setFormRendered(true)
86
+ }
87
+ onOpenChange?.(nextOpen)
88
+ onVisibleChange?.(nextOpen)
89
+ }
90
+
91
+ const openForm = (event: any) => {
92
+ buttonProps.onClick?.(event)
93
+ if (event.defaultPrevented) {
94
+ return
95
+ }
96
+ setFormRendered(true)
97
+ setOpen(true)
98
+ }
33
99
 
34
100
  const onFinish = async (values: Record<string, unknown>) => {
35
- const finalValues = transformData ? transformData(values, record) : values
101
+ const finalValues = transformData ? await transformData(values, record) : values
36
102
 
37
103
  if (isEdit) {
38
104
  await crud.updateState.mutateAsync(record.id, finalValues)
@@ -47,7 +113,6 @@ export const CreateUpdate = (props: CreateUpdateProps) => {
47
113
  return record.id
48
114
  }
49
115
 
50
- const initialValues: unknown = (formSafeProps as { initialValues?: unknown }).initialValues
51
116
  if (!initialValues) {
52
117
  return 'create'
53
118
  }
@@ -63,37 +128,52 @@ export const CreateUpdate = (props: CreateUpdateProps) => {
63
128
  }
64
129
  })()
65
130
 
66
- return (
67
- <BetaSchemaForm
68
- key={formKey}
69
- layoutType={layoutType as any}
70
- title={title}
71
- width={width}
72
- columns={columns as any}
73
- onFinish={onFinish}
74
- layout='horizontal'
75
- labelCol={{ span: 4 }}
76
- initialValues={{
77
- ...(record ?? {}),
78
- ...(coverData ? coverData(record) : {}),
79
- }}
80
- {...(drawerProps ? { drawerProps } : {})}
81
- {...(modalProps ? { modalProps } : {})}
82
- trigger={
83
- isEdit ? (
84
- <Button
85
- type='link'
86
- size='small'
87
- icon={<EditOutlined />}
88
- {...buttonProps}
89
- />
90
- ) : (
91
- <Button type='primary' icon={<PlusOutlined />} {...buttonProps}>
92
- 新建
93
- </Button>
94
- )
95
- }
96
- {...formSafeProps}
131
+ const finalInitialValues = {
132
+ ...(record ?? {}),
133
+ ...(initialValues ?? {}),
134
+ ...(coverData ? coverData(record) : {}),
135
+ }
136
+
137
+ const trigger = isEdit ? (
138
+ <Button
139
+ type='link'
140
+ size='small'
141
+ icon={<EditOutlined />}
142
+ {...buttonProps}
143
+ onClick={openForm}
97
144
  />
145
+ ) : (
146
+ <Button
147
+ type='primary'
148
+ icon={<PlusOutlined />}
149
+ {...buttonProps}
150
+ onClick={openForm}
151
+ >
152
+ 新建
153
+ </Button>
154
+ )
155
+
156
+ return (
157
+ <>
158
+ {trigger}
159
+ {formRendered ? (
160
+ <BetaSchemaForm
161
+ key={formKey}
162
+ layoutType={layoutType as any}
163
+ title={title}
164
+ width={width}
165
+ columns={formColumns as any}
166
+ onFinish={onFinish}
167
+ layout='horizontal'
168
+ labelCol={{ span: 4 }}
169
+ initialValues={finalInitialValues}
170
+ open={open}
171
+ onOpenChange={handleOpenChange}
172
+ {...(drawerProps ? { drawerProps } : {})}
173
+ {...(modalProps ? { modalProps } : {})}
174
+ {...formSafeProps}
175
+ />
176
+ ) : null}
177
+ </>
98
178
  )
99
179
  }
@@ -1,6 +1,9 @@
1
- import { type PropsWithChildren, useContext } from 'react'
1
+ 'use client'
2
+
3
+ import { type PropsWithChildren, useContext, useMemo } from 'react'
2
4
  import { Tag, Switch } from 'antd'
3
5
  import { ProProvider, ProFormSelect } from '@ant-design/pro-components'
6
+ import type { ProRenderFieldPropsType } from '@ant-design/pro-provider'
4
7
 
5
8
  import type { Crud } from '../../types'
6
9
 
@@ -19,52 +22,59 @@ export const Provider = (props: ProviderProps) => {
19
22
  }
20
23
  }
21
24
 
25
+ const valueTypeMap = useMemo<Record<string, ProRenderFieldPropsType>>(() => ({
26
+ ...defaultContext.valueTypeMap,
27
+ tags: {
28
+ renderFormItem: (_: unknown, props: any) => {
29
+ const { options, ...restFieldProps } = props.fieldProps || {}
30
+
31
+ let processedOptions = options
32
+
33
+ if (Array.isArray(options) && options.length > 0 && typeof options[0] === 'string') {
34
+ processedOptions = options.map((item: string) => ({
35
+ label: item,
36
+ value: item,
37
+ }))
38
+ }
39
+
40
+ return (
41
+ <ProFormSelect
42
+ placeholder='请输入标签'
43
+ mode='tags'
44
+ options={processedOptions}
45
+ {...restFieldProps}
46
+ />
47
+ )
48
+ },
49
+ render: (tags: string[]) => {
50
+ return (
51
+ <>
52
+ {tags?.map((tag: string) => (
53
+ <Tag key={tag}>{tag}</Tag>
54
+ ))}
55
+ </>
56
+ )
57
+ },
58
+ },
59
+ enabled: {
60
+ render: (enabled: boolean, props: any) => {
61
+ return (
62
+ <Switch
63
+ loading={crud?.updateState.isLoading}
64
+ checked={enabled}
65
+ onChange={enabled => onSwitchChange(props.record?.id, enabled)}
66
+ disabled={!crud?.updateState}
67
+ />
68
+ )
69
+ },
70
+ },
71
+ }), [crud?.updateState, defaultContext.valueTypeMap])
72
+
22
73
  return (
23
74
  <ProProvider.Provider
24
75
  value={{
25
76
  ...defaultContext,
26
- valueTypeMap: {
27
- tags: {
28
- renderFormItem: (_: unknown, props) => {
29
- const { options, ...restFieldProps } = props.fieldProps || {}
30
-
31
- let processedOptions = options
32
-
33
- if (Array.isArray(options) && options.length > 0 && typeof options[0] === 'string') {
34
- processedOptions = options.map((item: string) => ({
35
- label: item,
36
- value: item,
37
- }))
38
- }
39
-
40
- return (
41
- <ProFormSelect
42
- placeholder='请输入标签'
43
- mode='tags'
44
- options={processedOptions}
45
- {...restFieldProps}
46
- />
47
- )
48
- },
49
- render: (tags: string[]) => {
50
- return tags?.map((tag: string) => (
51
- <Tag key={tag}>{tag}</Tag>
52
- ))
53
- },
54
- },
55
- enabled: {
56
- render: (enabled: boolean, props: any) => {
57
- return (
58
- <Switch
59
- loading={crud?.updateState.isLoading}
60
- checked={enabled}
61
- onChange={enabled => onSwitchChange(props.record?.id, enabled)}
62
- disabled={!crud?.updateState}
63
- />
64
- )
65
- },
66
- },
67
- },
77
+ valueTypeMap,
68
78
  }}
69
79
  >
70
80
  {children}
@@ -1,6 +1,8 @@
1
1
  import React from 'react'
2
2
  import { AutoComplete, Button, Space } from 'antd'
3
3
 
4
+ import type { Option } from './types'
5
+
4
6
  interface DynamicSelectProps {
5
7
  value?: string[]
6
8
  onChange?: (value: string[]) => void
@@ -0,0 +1,8 @@
1
+ export type OptionValue = string | number | boolean
2
+
3
+ export interface Option<V extends OptionValue = OptionValue> {
4
+ label: string
5
+ value: V
6
+ children?: Option<V>[]
7
+ extra?: Record<string, any>
8
+ }
@@ -1,3 +1,5 @@
1
+ import type { Option } from './types'
2
+
1
3
  export const transformOptions = (data: string[][]): Option[] => {
2
4
  const result: Option[] = []
3
5
 
@@ -6,7 +6,6 @@ export * from './Com2Canvas'
6
6
  export * from './Video'
7
7
  export * from './MultiImageDisplay'
8
8
  export * from './DownloadLink'
9
- export * from './Fold'
10
9
  export * from './DragSort'
11
10
  export * from './DateSwitcher'
12
11
  export * from './GroupLayout'
@@ -1,24 +0,0 @@
1
- 'use client'
2
-
3
- import React, { ReactNode } from 'react'
4
- import { Alert, AlertProps, Space } from 'antd'
5
-
6
- export interface AlertProProps extends AlertProps {
7
- children?: ReactNode,
8
- }
9
-
10
- // 1. 支持 children 作为 description 显示
11
- export const AlertPro = (props: AlertProProps) => {
12
- const { children, description = children } = props
13
-
14
- return (
15
- <Alert
16
- {...props}
17
- description={(
18
- <Space style={{ width: '100%' }} orientation='vertical'>
19
- {description}
20
- </Space>
21
- )}
22
- />
23
- )
24
- }
@@ -1,26 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { Button } from 'antd'
5
- import { Confirm, ConfirmProps } from './Confirm'
6
-
7
- export interface GroupProps {
8
- list?: (ConfirmProps & { visible?: boolean, title?: string })[]
9
- }
10
-
11
- // 1. 按钮组
12
- export const Group = (props: GroupProps) => {
13
- const { list = [] } = props
14
-
15
- if (!list?.length) {
16
- return null
17
- }
18
-
19
- return (
20
- <Button.Group>
21
- {list.filter(item => item.visible).map((item, index) => (
22
- <Confirm key={index} {...item}>{item.title}</Confirm>
23
- ))}
24
- </Button.Group>
25
- )
26
- }
@@ -1,11 +0,0 @@
1
- import { Group } from './components/Group'
2
- import { Confirm } from './components/Confirm'
3
- import { Copy } from './components/Copy'
4
- import { Drawer } from './components/Drawer'
5
-
6
- export const ButtonPro = {
7
- Group,
8
- Confirm,
9
- Copy,
10
- Drawer,
11
- }
@@ -1,92 +0,0 @@
1
- 'use client'
2
-
3
- import React, { useState, useRef, useEffect, forwardRef } from 'react'
4
- import { Card, Button, CardProps } from 'antd'
5
- import { DownOutlined, UpOutlined } from '@ant-design/icons'
6
-
7
- interface CardProProps extends CardProps {
8
- collapsedHeight?: number; // 默认折叠高度,可根据需要调整
9
- }
10
-
11
- export const CardPro = forwardRef((props: CardProProps, ref: any) => {
12
- const { collapsedHeight = 300, children, ...rest } = props
13
-
14
- const [collapsed, setCollapsed] = useState(true)
15
- const [hovered, setHovered] = useState(false)
16
- const [isOverflow, setIsOverflow] = useState(false)
17
- const contentRef = useRef<HTMLSpanElement>(null)
18
-
19
- const toggleCollapse = (ev: any) => {
20
- ev.stopPropagation()
21
- setCollapsed(!collapsed)
22
- }
23
-
24
- useEffect(() => {
25
- const contentEl = contentRef.current
26
- if (contentEl) {
27
- // 获取内容的实际高度
28
- const actualHeight = contentEl.scrollHeight
29
- if (actualHeight > collapsedHeight) {
30
- setIsOverflow(true)
31
- } else {
32
- setIsOverflow(false)
33
- }
34
- }
35
- }, [children, collapsedHeight])
36
-
37
- const cardStyle: React.CSSProperties = {
38
- position: 'relative',
39
- overflow: 'hidden',
40
- }
41
-
42
- const contentStyle: React.CSSProperties = {
43
- maxHeight: collapsed ? `${collapsedHeight}px` : 'none',
44
- overflowY: collapsed ? 'auto' : 'visible',
45
- transition: 'max-height 0.3s ease',
46
- display: 'block',
47
- textOverflow: 'ellipsis',
48
- overflow: 'hidden',
49
- }
50
-
51
- const buttonContainerStyle: React.CSSProperties = {
52
- position: 'absolute',
53
- bottom: hovered ? '10px' : '-40px', // 增加隐藏时的负值,确保按钮完全隐藏
54
- left: '50%',
55
- transform: 'translateX(-50%)',
56
- transition: 'bottom 0.3s ease, opacity 0.3s ease',
57
- display: 'flex',
58
- justifyContent: 'center',
59
- width: '100%',
60
- pointerEvents: 'none', // 默认情况下,禁止点击
61
- opacity: hovered && isOverflow ? 1 : 0, // 控制按钮的透明度
62
- }
63
-
64
- const buttonStyle: React.CSSProperties = {
65
- pointerEvents: 'auto', // 允许按钮在悬停时可点击
66
- border: 'none',
67
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
68
- }
69
-
70
- return (
71
- <Card
72
- ref={ref}
73
- style={cardStyle}
74
- onMouseEnter={() => setHovered(true)}
75
- onMouseLeave={() => setHovered(false)}
76
- {...rest}
77
- >
78
- <span style={contentStyle} ref={contentRef}>
79
- {children}
80
- </span>
81
- {isOverflow && (
82
- <div style={buttonContainerStyle}>
83
- <Button
84
- icon={collapsed ? <DownOutlined /> : <UpOutlined />}
85
- onClick={toggleCollapse}
86
- style={buttonStyle}
87
- />
88
- </div>
89
- )}
90
- </Card>
91
- )
92
- })
@@ -1,30 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { Collapse, CollapsePanelProps, Space } from 'antd'
5
-
6
- export interface CollapseItemProps extends Omit<CollapsePanelProps, 'key'> {
7
- step?: boolean; // 为true自动给标题添加 `第{index}步:`
8
- index?: number; // step为true时才需要
9
- space?: boolean; // 子元素是否有间距
10
- }
11
-
12
- export const Item = (props: CollapseItemProps) => {
13
- const { step = true, header, index, space } = props
14
-
15
- return (
16
- <Collapse.Panel
17
- key={String(header)}
18
- {...props}
19
- header={step ? `第${index}步: ${header}` : header}
20
- >
21
- {space ? (
22
- <Space orientation='vertical' size='small' style={{ width: '100%' }}>
23
- {props.children}
24
- </Space>
25
- ) : (
26
- props.children
27
- )}
28
- </Collapse.Panel>
29
- )
30
- }
@@ -1,27 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { Collapse, CollapseProps } from 'antd'
5
- import { Item, CollapseItemProps } from './components/Item'
6
-
7
- export interface CollapseProProps extends CollapseProps {
8
- list?: CollapseItemProps[],
9
- step?: boolean; // 为true自动给标题添加 `第{index}步:`
10
- space?: boolean; // 子元素是否有间距
11
- }
12
-
13
- export const CollapsePro = (props: CollapseProProps) => {
14
- const { list = [], step = false, space = true, accordion = true } = props
15
-
16
- if (!props.children && list.length) {
17
- props.children = list.map((item, index) => (
18
- <Item key={index} step={step} space={space} index={index + 1} {...item} />
19
- ))
20
- }
21
-
22
- return (
23
- <Collapse accordion={accordion} {...props} />
24
- )
25
- }
26
-
27
- CollapsePro.Pane = Item
@@ -1,17 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { Image, ImageProps } from 'antd'
5
-
6
- export type ImageProProps = ImageProps
7
-
8
- export const ImagePro = (props: ImageProProps) => {
9
- const { preview = false } = props
10
-
11
- return (
12
- <Image
13
- {...props}
14
- preview={preview}
15
- />
16
- )
17
- }
@@ -1,61 +0,0 @@
1
- 'use client'
2
-
3
- import React, { useState, useEffect, useMemo } from 'react'
4
- import { Input, InputProps } from 'antd'
5
- import { getStrLength } from '@wzyjs/utils/web'
6
- import { Range } from './components/Range'
7
-
8
- export interface InputProProps extends Omit<InputProps, 'onChange'> {
9
- maxLengthChinese?: boolean; // 限制长度:中文算2个字符
10
- onChange?: (value: string) => void
11
- }
12
-
13
- // 1. 增加对中文字符串的长度限制 (Input的maxLength会认为汉字为1个长度)
14
- export const InputPro = (props: InputProProps) => {
15
- const {
16
- value: _value,
17
- onChange: _onChange,
18
- addonAfter: _addonAfter,
19
- maxLength,
20
- maxLengthChinese,
21
- ...rest
22
- } = props
23
-
24
- const [value, setValue] = useState<string>('')
25
-
26
- const addonAfter = useMemo(() => {
27
- if (!maxLengthChinese) {
28
- return _addonAfter
29
- }
30
-
31
- return (
32
- <>
33
- {_addonAfter}
34
- {maxLength ? `${getStrLength(value)} / ${maxLength}` : null}
35
- </>
36
- )
37
- }, [_addonAfter, value, maxLength])
38
-
39
- useEffect(() => {
40
- _onChange?.(value)
41
- }, [value])
42
-
43
- const onChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
44
- if (maxLengthChinese && maxLength && getStrLength(ev.target.value) > maxLength) {
45
- return
46
- }
47
- setValue(ev.target.value)
48
- }
49
-
50
- return (
51
- <Input
52
- value={value}
53
- onChange={onChange}
54
- addonAfter={addonAfter}
55
- maxLength={maxLength}
56
- {...rest}
57
- />
58
- )
59
- }
60
-
61
- InputPro.Range = Range
@@ -1,16 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { Popconfirm, PopconfirmProps } from 'antd'
5
-
6
- export type PopconfirmProProps = PopconfirmProps
7
-
8
- export const PopconfirmPro = (props: PopconfirmProProps) => {
9
- return (
10
- <Popconfirm
11
- okText="确定"
12
- cancelText="取消"
13
- {...props}
14
- />
15
- )
16
- }
@@ -1,30 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { Checkbox, CheckboxProps, CheckboxOptionType } from 'antd'
5
-
6
- export interface CancelProps {
7
- value: CheckboxProps['value'];
8
- options: CheckboxOptionType[];
9
- onChange?: CheckboxProps['onChange'];
10
- }
11
-
12
- // 给Radio组件增加取消选择的功能,是用Checkbox组件实现的
13
- export const Cancel = (props: CancelProps) => {
14
- const { value, options, onChange } = props
15
-
16
- const evs = {
17
- onChange: (checkedValue: CheckboxProps['value'][]) => {
18
- onChange?.(checkedValue.find(i => i !== value) || '')
19
- },
20
- }
21
-
22
- return (
23
- <Checkbox.Group
24
- value={[value]}
25
- prefixCls='ant-radio'
26
- options={options}
27
- onChange={evs.onChange}
28
- />
29
- )
30
- }
@@ -1,7 +0,0 @@
1
- 'use client'
2
-
3
- import { Cancel } from './components/Cancel'
4
-
5
- export const RadioPro = {
6
- Cancel,
7
- }
@@ -1,15 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { Space, SpaceProps } from 'antd'
5
-
6
- export type SpaceProProps = SpaceProps
7
-
8
- export const SpacePro = (props: SpaceProProps) => {
9
- return (
10
- <Space
11
- style={{ width: '100%' }}
12
- {...props}
13
- />
14
- )
15
- }
@@ -1,52 +0,0 @@
1
- 'use client'
2
-
3
- import React, { useState } from 'react'
4
- import { Button } from 'antd'
5
- import { DownOutlined, UpOutlined } from '@ant-design/icons'
6
-
7
- interface FoldProps {
8
- max: number
9
- children?: React.ReactNode
10
- btnStyle?: React.CSSProperties
11
- }
12
-
13
- export const Fold = (props: FoldProps) => {
14
- const { max, children, btnStyle = {} } = props
15
-
16
- const [collapsed, setCollapsed] = useState<boolean | undefined>(undefined)
17
-
18
- const divRef = React.useRef<HTMLDivElement>(null)
19
-
20
- React.useEffect(() => {
21
- if (divRef.current) {
22
- const { clientHeight } = divRef.current
23
- if (clientHeight > max) {
24
- setCollapsed(true)
25
- }
26
- }
27
- }, [divRef.current])
28
-
29
- return (
30
- <div ref={divRef} style={{ overflow: 'hidden' }}>
31
- <div style={{ maxHeight: collapsed ? max : undefined }}>
32
- {children}
33
- </div>
34
-
35
- <div style={{ textAlign: 'center', ...btnStyle }}>
36
- {collapsed === undefined ? null : collapsed ? (
37
- <Button
38
- size='small'
39
- icon={<DownOutlined />}
40
- onClick={() => setCollapsed(false)}
41
- />
42
- ) : (
43
- <Button
44
- size='small'
45
- icon={<UpOutlined />}
46
- onClick={() => setCollapsed(true)}
47
- />
48
- )}
49
- </div>
50
- </div>
51
- )
52
- }