@wzyjs/uis 0.3.10 → 0.3.16

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 (45) hide show
  1. package/package.json +2 -2
  2. package/src/antd/form/FileUploader/index.tsx +163 -0
  3. package/src/antd/form/RadioButton/index.tsx +3 -1
  4. package/src/antd/form/index.ts +1 -0
  5. package/src/antd/index.ts +2 -0
  6. package/src/components/Crud/components/CardList/index.tsx +174 -0
  7. package/src/components/Crud/components/CreateUpdate/index.tsx +99 -0
  8. package/src/components/Crud/components/Provider/index.tsx +73 -0
  9. package/src/components/Crud/components/Remove/index.tsx +56 -0
  10. package/src/components/Crud/components/index.ts +4 -0
  11. package/src/components/Crud/hooks/index.ts +4 -0
  12. package/src/components/Crud/hooks/useColumns.tsx +169 -0
  13. package/src/components/Crud/hooks/useList.ts +54 -0
  14. package/src/components/Crud/hooks/useOrderable.tsx +107 -0
  15. package/src/components/Crud/hooks/useRequest.ts +41 -0
  16. package/src/components/Crud/index.tsx +91 -0
  17. package/src/components/Crud/types/index.ts +188 -0
  18. package/src/components/Crud/utils/index.ts +87 -0
  19. package/src/components/MindMap/context.tsx +29 -0
  20. package/src/components/MindMap/hooks/useAlignmentSnap.ts +220 -0
  21. package/src/components/MindMap/hooks/useCopyPaste.ts +272 -0
  22. package/src/components/MindMap/hooks/useDropToReparent.ts +288 -0
  23. package/src/components/MindMap/hooks/useExpandCollapse.ts +146 -0
  24. package/src/components/MindMap/hooks/useMoveDescendants.ts +136 -0
  25. package/src/components/MindMap/hooks/useUndoRedo.ts +232 -0
  26. package/src/components/MindMap/index.tsx +117 -0
  27. package/src/components/ProgressButton/index.module.scss +65 -0
  28. package/src/components/ProgressButton/index.tsx +96 -0
  29. package/src/components/TimelineBar/components/CurrentWeekHighlight/index.tsx +64 -0
  30. package/src/components/TimelineBar/components/Guides/index.tsx +61 -0
  31. package/src/components/TimelineBar/components/Ticks/index.tsx +56 -0
  32. package/src/components/TimelineBar/components/TodayIndicator/index.tsx +54 -0
  33. package/src/components/TimelineBar/components/index.ts +4 -0
  34. package/src/components/TimelineBar/const.ts +3 -0
  35. package/src/components/TimelineBar/hooks/index.ts +5 -0
  36. package/src/components/TimelineBar/hooks/useHighlightRange.ts +21 -0
  37. package/src/components/TimelineBar/hooks/useMonthGuides.ts +40 -0
  38. package/src/components/TimelineBar/hooks/useTickValues.ts +18 -0
  39. package/src/components/TimelineBar/hooks/useVisibleRange.ts +43 -0
  40. package/src/components/TimelineBar/hooks/useWeekGuides.ts +39 -0
  41. package/src/components/TimelineBar/index.tsx +63 -0
  42. package/src/components/TimelineBar/utils.ts +27 -0
  43. package/src/components/index.ts +4 -0
  44. package/src/rn.ts +1 -0
  45. package/src/rns/index.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wzyjs/uis",
3
- "version": "0.3.10",
3
+ "version": "0.3.16",
4
4
  "description": "description",
5
5
  "author": "wzy",
6
6
  "sideEffects": false,
@@ -42,7 +42,7 @@
42
42
  "@wzyjs/utils": "^0.2.37",
43
43
  "antd": "^6.0.0"
44
44
  },
45
- "gitHead": "74b286716c6df30605f96415a28ee8084cd18923",
45
+ "gitHead": "58a36c9fd59e43928b80701cdd7896754ea0db97",
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  }
@@ -0,0 +1,163 @@
1
+ 'use client'
2
+
3
+ import { useState, type ReactElement } from 'react'
4
+
5
+ import { Upload, Button, } from 'antd'
6
+ import { UploadOutlined, InboxOutlined } from '@ant-design/icons'
7
+
8
+ import type { UploadProps, UploadFile as AntUploadFile } from 'antd'
9
+
10
+ export interface FileUploaderProps {
11
+ value?: AntUploadFile[]
12
+ onChange?: (fileList: AntUploadFile[]) => void
13
+
14
+ // 模式
15
+ mode?: 'file' | 'http' // http 暂未实现
16
+
17
+ // 限制
18
+ accept?: string
19
+ multiple?: boolean
20
+ maxCount?: number
21
+
22
+ // 展示
23
+ trigger?: 'button' | 'dragger' | ReactElement
24
+ listType?: UploadProps['listType']
25
+
26
+ // 校验
27
+ beforeUpload?: (file: AntUploadFile) => boolean | Promise<void>
28
+
29
+ // 上传行为
30
+ immediateUpload?: boolean
31
+ action?: string
32
+ data?: Record<string, unknown> | ((file: AntUploadFile) => Record<string, unknown>)
33
+ headers?: UploadProps['headers']
34
+ withCredentials?: boolean
35
+
36
+ disabled?: boolean
37
+ showUploadList?: boolean
38
+ onUploadChange?: UploadProps['onChange']
39
+ }
40
+
41
+ export const FileUploader = (props: FileUploaderProps) => {
42
+ const {
43
+ value,
44
+ onChange,
45
+ mode = 'file',
46
+ accept,
47
+ multiple = true,
48
+ maxCount,
49
+ trigger = 'dragger',
50
+ listType = 'text',
51
+ beforeUpload,
52
+ immediateUpload = false,
53
+ action = '/api/file/upload',
54
+ data,
55
+ headers,
56
+ withCredentials,
57
+ disabled,
58
+ showUploadList = trigger === 'dragger',
59
+ onUploadChange,
60
+ } = props
61
+
62
+ const [internalFileList, setInternalFileList] = useState<AntUploadFile[]>([])
63
+
64
+ const fileList = value || internalFileList
65
+
66
+ const handleChange: UploadProps['onChange'] = (info) => {
67
+ let newFileList = [...info.fileList]
68
+
69
+ // 限制数量
70
+ if (maxCount && newFileList.length > maxCount) {
71
+ newFileList = newFileList.slice(-maxCount)
72
+ }
73
+
74
+ // 如果不是立即上传模式,我们需要手动管理状态
75
+ if (!immediateUpload) {
76
+ // 这里的逻辑主要依赖 beforeUpload 返回 false 来阻止上传
77
+ // 但 onChange 依然会被触发
78
+ }
79
+
80
+ // 更新状态
81
+ if (!value) {
82
+ setInternalFileList(newFileList)
83
+ }
84
+
85
+ // 通知外部
86
+ onChange?.(newFileList)
87
+ onUploadChange?.(info)
88
+ }
89
+
90
+ const handleBeforeUpload = async (file: AntUploadFile) => {
91
+ // 1. 外部校验
92
+ if (beforeUpload) {
93
+ const result = await beforeUpload(file)
94
+ if (result === false) {
95
+ return Upload.LIST_IGNORE
96
+ }
97
+ }
98
+
99
+ // 2. 立即上传模式
100
+ if (immediateUpload) {
101
+ return true
102
+ }
103
+
104
+ // 3. 非立即上传模式,阻止默认上传行为,但添加到列表
105
+ return false
106
+ }
107
+
108
+ const uploadProps: UploadProps = {
109
+ fileList,
110
+ accept,
111
+ multiple,
112
+ maxCount,
113
+ listType,
114
+ disabled,
115
+ showUploadList,
116
+ beforeUpload: handleBeforeUpload as UploadProps['beforeUpload'],
117
+ onChange: handleChange,
118
+ onDrop: (e: unknown) => {
119
+ // console.log('Dropped files', e.dataTransfer.files)
120
+ },
121
+ }
122
+
123
+ // 立即上传相关配置
124
+ if (immediateUpload) {
125
+ uploadProps.action = action
126
+ uploadProps.data = data
127
+ uploadProps.headers = headers
128
+ uploadProps.withCredentials = withCredentials
129
+ } else {
130
+ // 非立即上传模式,不需要 action
131
+ uploadProps.customRequest = () => undefined // 覆盖默认上传行为,防止自动上传尝试
132
+ }
133
+
134
+ if (mode === 'http') {
135
+ return <div>HTTP 输入模式暂未支持</div>
136
+ }
137
+
138
+ if (trigger === 'button') {
139
+ return (
140
+ <Upload {...uploadProps}>
141
+ <Button type="primary" icon={<UploadOutlined />}>
142
+ 上传文件
143
+ </Button>
144
+ </Upload>
145
+ )
146
+ }
147
+
148
+ if (trigger === 'dragger') {
149
+ return (
150
+ <Upload.Dragger {...uploadProps}>
151
+ <p className="ant-upload-drag-icon">
152
+ <InboxOutlined />
153
+ </p>
154
+ <p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
155
+ <p className="ant-upload-hint">
156
+ 支持单个或批量上传{maxCount ? `,最多 ${maxCount} 个文件` : ''}
157
+ </p>
158
+ </Upload.Dragger>
159
+ )
160
+ }
161
+
162
+ return trigger
163
+ }
@@ -9,10 +9,11 @@ interface FilterButtonProps<V> {
9
9
  buttonStyle?: 'outline' | 'solid'
10
10
  onChange?: (value: V) => void
11
11
  style?: CSSProperties
12
+ disabled?: boolean
12
13
  }
13
14
 
14
15
  export const RadioButton = <V = string | number | boolean | null>(props: FilterButtonProps<V>) => {
15
- const { value, options, buttonStyle = 'outline', style, onChange } = props
16
+ const { value, options, buttonStyle = 'outline', disabled, style, onChange } = props
16
17
 
17
18
  return (
18
19
  <Radio.Group
@@ -20,6 +21,7 @@ export const RadioButton = <V = string | number | boolean | null>(props: FilterB
20
21
  buttonStyle={buttonStyle}
21
22
  value={value}
22
23
  style={style}
24
+ disabled={disabled}
23
25
  onChange={e => onChange?.(e.target.value as V)}
24
26
  >
25
27
  {options.filter(item => item.label).map(item => (
@@ -1,5 +1,6 @@
1
1
  export * from './Upload'
2
2
  export * from './UploadImage'
3
+ export * from './FileUploader'
3
4
 
4
5
  export * from './CheckboxButton'
5
6
  export * from './RadioButton'
package/src/antd/index.ts CHANGED
@@ -15,6 +15,8 @@ export {
15
15
  ProTable,
16
16
  ProDescriptions,
17
17
 
18
+ ProForm,
19
+ ProFormList,
18
20
  ProFormText,
19
21
  ProFormCaptcha,
20
22
  ProFormCheckbox,
@@ -0,0 +1,174 @@
1
+ 'use client'
2
+
3
+ import React, { type ReactNode } from 'react'
4
+
5
+ import { Card, Col, Row, Avatar } from 'antd'
6
+ import { Remove } from '../Remove'
7
+ import { CreateUpdate } from '../CreateUpdate'
8
+
9
+ import type { ListProps } from '../../types'
10
+
11
+ export const CardList = (props: ListProps) => {
12
+ const { crud, columns, list, update, remove } = props
13
+
14
+ const cardConfig = list.cardConfig!
15
+ const {
16
+ gutter = [16, 16],
17
+ col = { xs: 24, md: 8, lg: 6 },
18
+ height = 'calc(100vh - 210px)',
19
+ render: customRender,
20
+ card,
21
+ } = cardConfig
22
+
23
+ const dataSource = crud.listState.data || []
24
+
25
+ // 默认操作按钮生成函数
26
+ const getDefaultActions = (record: any): ReactNode[] => {
27
+ const actions: ReactNode[] = []
28
+
29
+ if (update !== false) {
30
+ actions.push(
31
+ <CreateUpdate
32
+ key='edit'
33
+ crud={crud}
34
+ columns={columns}
35
+ config={update || {}}
36
+ record={record}
37
+ />
38
+ )
39
+ }
40
+
41
+ if (remove !== false) {
42
+ actions.push(
43
+ <Remove
44
+ key='delete'
45
+ crud={crud}
46
+ config={remove || {}}
47
+ record={record}
48
+ />
49
+ )
50
+ }
51
+
52
+ return actions
53
+ }
54
+
55
+ // 解析卡片字段值
56
+ const parseFieldValue = (field: any, record: any): any => {
57
+ if (typeof field === 'string') {
58
+ return record[field]
59
+ }
60
+ return field
61
+ }
62
+
63
+ // 渲染单个卡片项
64
+ const renderCardItem = (record: any, index: number) => {
65
+ const defaultActions = getDefaultActions(record)
66
+
67
+ // 如果有完全自定义渲染函数,直接使用
68
+ if (customRender) {
69
+ return customRender(record, index, defaultActions)
70
+ }
71
+
72
+ // 获取卡片配置(支持函数和对象两种形式)
73
+ const cardConfig = typeof card === 'function' ? card(record, index) : card
74
+ const {
75
+ title,
76
+ extra,
77
+ content,
78
+ cover,
79
+ meta,
80
+ actions: configActions,
81
+ props: cardProps = {},
82
+ } = cardConfig
83
+
84
+ // 渲染封面
85
+ const renderCover = () => {
86
+ if (!cover) return undefined
87
+ return parseFieldValue(cover, record)
88
+ }
89
+
90
+ // 渲染标题
91
+ const renderTitle = () => {
92
+ if (title !== undefined) {
93
+ return parseFieldValue(title, record)
94
+ }
95
+ // 默认标题逻辑
96
+ return record.title || record.name || record.id
97
+ }
98
+
99
+ // 渲染右上角
100
+ const renderExtra = () => {
101
+ if (extra !== undefined) {
102
+ return parseFieldValue(extra, record)
103
+ }
104
+ return null
105
+ }
106
+
107
+ // 渲染内容
108
+ const renderContent = () => {
109
+ if (content !== undefined) {
110
+ return parseFieldValue(content, record)
111
+ }
112
+ return null
113
+ }
114
+
115
+ // 渲染操作按钮
116
+ const renderActions = () => {
117
+ if (configActions && configActions.length > 0) {
118
+ return configActions.map(action => parseFieldValue(action, record))
119
+ }
120
+ return defaultActions
121
+ }
122
+
123
+ // 渲染元数据
124
+ const renderMeta = () => {
125
+ if (!meta) return undefined
126
+
127
+ const metaTitle = meta.title ? parseFieldValue(meta.title, record) : undefined
128
+ const metaDescription = meta.description ? parseFieldValue(meta.description, record) : undefined
129
+ const metaAvatar = meta.avatar ? parseFieldValue(meta.avatar, record) : undefined
130
+
131
+ if (!metaTitle && !metaDescription && !metaAvatar) {
132
+ return undefined
133
+ }
134
+
135
+ return (
136
+ <Card.Meta
137
+ title={metaTitle}
138
+ description={metaDescription}
139
+ avatar={metaAvatar && (
140
+ typeof metaAvatar === 'string' ? (
141
+ <Avatar src={metaAvatar} />
142
+ ) : (
143
+ metaAvatar
144
+ )
145
+ )}
146
+ />
147
+ )
148
+ }
149
+
150
+ return (
151
+ <Card
152
+ size='small'
153
+ title={renderTitle()}
154
+ extra={renderExtra()}
155
+ cover={renderCover()}
156
+ actions={renderActions()}
157
+ {...cardProps}
158
+ >
159
+ {renderMeta()}
160
+ {renderContent()}
161
+ </Card>
162
+ )
163
+ }
164
+
165
+ return (
166
+ <Row gutter={gutter} style={{ overflow: 'auto', height }}>
167
+ {dataSource.map((record, index) => (
168
+ <Col key={record.id || index} {...col}>
169
+ {renderCardItem(record, index)}
170
+ </Col>
171
+ ))}
172
+ </Row>
173
+ )
174
+ }
@@ -0,0 +1,99 @@
1
+ import { Button, type DrawerProps, type ModalProps } from 'antd'
2
+ import { PlusOutlined, EditOutlined } from '@ant-design/icons'
3
+ import { BetaSchemaForm } from '@ant-design/pro-components'
4
+
5
+ import type { CreateUpdateProps } from '../../types'
6
+
7
+ export const CreateUpdate = (props: CreateUpdateProps) => {
8
+ const { crud, columns, config, record } = props
9
+
10
+ const isEdit = !!record
11
+
12
+ const {
13
+ mode = 'drawer',
14
+ title = isEdit ? '编辑' : '新建',
15
+ buttonProps = {},
16
+ formProps = {},
17
+ transformData,
18
+ coverData,
19
+ width,
20
+ } = config
21
+
22
+ const { drawerProps, modalProps, ...formSafeProps } = formProps as unknown as { drawerProps?: DrawerProps; modalProps?: ModalProps } & Record<string, unknown>
23
+
24
+ const layoutType = mode === 'modal' ? 'ModalForm' : 'DrawerForm'
25
+
26
+ const finalDrawerProps: DrawerProps | undefined = layoutType === 'DrawerForm'
27
+ ? { forceRender: true, ...drawerProps }
28
+ : undefined
29
+
30
+ const finalModalProps: ModalProps | undefined = layoutType === 'ModalForm'
31
+ ? { forceRender: false, ...modalProps }
32
+ : undefined
33
+
34
+ const onFinish = async (values: Record<string, unknown>) => {
35
+ const finalValues = transformData ? transformData(values, record) : values
36
+
37
+ if (isEdit) {
38
+ await crud.updateState.mutateAsync(record.id, finalValues)
39
+ } else {
40
+ await crud.createState.mutateAsync(finalValues)
41
+ }
42
+ return true
43
+ }
44
+
45
+ const formKey = (() => {
46
+ if (isEdit && record) {
47
+ return record.id
48
+ }
49
+
50
+ const initialValues: unknown = (formSafeProps as { initialValues?: unknown }).initialValues
51
+ if (!initialValues) {
52
+ return 'create'
53
+ }
54
+
55
+ if (typeof initialValues !== 'object') {
56
+ return `create-${JSON.stringify(initialValues)}`
57
+ }
58
+
59
+ try {
60
+ return `create-${JSON.stringify(initialValues)}`
61
+ } catch {
62
+ return 'create'
63
+ }
64
+ })()
65
+
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
+ {...(finalDrawerProps ? { drawerProps: finalDrawerProps } : {})}
81
+ {...(finalModalProps ? { modalProps: finalModalProps } : {})}
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}
97
+ />
98
+ )
99
+ }
@@ -0,0 +1,73 @@
1
+ import { type PropsWithChildren, useContext } from 'react'
2
+ import { Tag, Switch } from 'antd'
3
+ import { ProProvider, ProFormSelect } from '@ant-design/pro-components'
4
+
5
+ import type { Crud } from '../../types'
6
+
7
+ interface ProviderProps extends PropsWithChildren {
8
+ crud?: Crud
9
+ }
10
+
11
+ export const Provider = (props: ProviderProps) => {
12
+ const { children, crud } = props
13
+
14
+ const defaultContext = useContext(ProProvider)
15
+
16
+ const onSwitchChange = (id: string, enabled: boolean) => {
17
+ if (crud?.updateState) {
18
+ crud.updateState.mutate(id, { enabled })
19
+ }
20
+ }
21
+
22
+ return (
23
+ <ProProvider.Provider
24
+ value={{
25
+ ...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
+ },
68
+ }}
69
+ >
70
+ {children}
71
+ </ProProvider.Provider>
72
+ )
73
+ }
@@ -0,0 +1,56 @@
1
+ import { Popconfirm, Button } from 'antd'
2
+ import { DeleteOutlined } from '@ant-design/icons'
3
+
4
+ import type { RemoveProps } from '../../types'
5
+
6
+ export const Remove = (props: RemoveProps) => {
7
+ const { crud, config, record } = props
8
+
9
+ const {
10
+ confirm = true,
11
+ buttonProps = {},
12
+ title = '确定要删除吗?',
13
+ content,
14
+ } = config
15
+
16
+ const onConfirm = () => {
17
+ crud.removeState.mutate(record.id)
18
+ }
19
+
20
+ // 如果不需要确认,直接执行删除
21
+ if (!confirm) {
22
+ return (
23
+ <Button
24
+ type='link'
25
+ danger
26
+ size='small'
27
+ icon={<DeleteOutlined />}
28
+ loading={crud.removeState.isLoading}
29
+ onClick={onConfirm}
30
+ {...buttonProps}
31
+ />
32
+ )
33
+ }
34
+
35
+ // 需要确认的删除按钮
36
+ return (
37
+ <Popconfirm
38
+ title={title}
39
+ description={content}
40
+ onConfirm={onConfirm}
41
+ okText='确定'
42
+ cancelText='取消'
43
+ okButtonProps={{
44
+ loading: crud.removeState.isLoading,
45
+ }}
46
+ >
47
+ <Button
48
+ type='link'
49
+ danger
50
+ size='small'
51
+ icon={<DeleteOutlined />}
52
+ {...buttonProps}
53
+ />
54
+ </Popconfirm>
55
+ )
56
+ }
@@ -0,0 +1,4 @@
1
+ export { CreateUpdate } from './CreateUpdate'
2
+ export { Remove } from './Remove'
3
+ export { CardList } from './CardList'
4
+ export { Provider } from './Provider'
@@ -0,0 +1,4 @@
1
+ export { useColumns } from './useColumns'
2
+ export { useList } from './useList'
3
+ export { useRequest } from './useRequest'
4
+ export { useOrderable } from './useOrderable'