@wzyjs/uis 0.3.10 → 0.3.17
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 +2 -2
- package/src/antd/form/FileUploader/index.tsx +163 -0
- package/src/antd/form/RadioButton/index.tsx +3 -1
- package/src/antd/form/index.ts +1 -0
- package/src/antd/index.ts +2 -0
- package/src/components/Crud/components/CardList/index.tsx +174 -0
- package/src/components/Crud/components/CreateUpdate/index.tsx +99 -0
- package/src/components/Crud/components/Provider/index.tsx +73 -0
- package/src/components/Crud/components/Remove/index.tsx +56 -0
- package/src/components/Crud/components/index.ts +4 -0
- package/src/components/Crud/hooks/index.ts +4 -0
- package/src/components/Crud/hooks/useColumns.tsx +169 -0
- package/src/components/Crud/hooks/useList.ts +54 -0
- package/src/components/Crud/hooks/useOrderable.tsx +107 -0
- package/src/components/Crud/hooks/useRequest.ts +41 -0
- package/src/components/Crud/index.tsx +91 -0
- package/src/components/Crud/types/index.ts +188 -0
- package/src/components/Crud/utils/index.ts +87 -0
- package/src/components/MindMap/context.tsx +29 -0
- package/src/components/MindMap/hooks/useAlignmentSnap.ts +220 -0
- package/src/components/MindMap/hooks/useCopyPaste.ts +272 -0
- package/src/components/MindMap/hooks/useDropToReparent.ts +288 -0
- package/src/components/MindMap/hooks/useExpandCollapse.ts +146 -0
- package/src/components/MindMap/hooks/useMoveDescendants.ts +136 -0
- package/src/components/MindMap/hooks/useUndoRedo.ts +232 -0
- package/src/components/MindMap/index.tsx +117 -0
- package/src/components/ProgressButton/index.module.scss +65 -0
- package/src/components/ProgressButton/index.tsx +96 -0
- package/src/components/TimelineBar/components/CurrentWeekHighlight/index.tsx +64 -0
- package/src/components/TimelineBar/components/Guides/index.tsx +61 -0
- package/src/components/TimelineBar/components/Ticks/index.tsx +56 -0
- package/src/components/TimelineBar/components/TodayIndicator/index.tsx +54 -0
- package/src/components/TimelineBar/components/index.ts +4 -0
- package/src/components/TimelineBar/const.ts +3 -0
- package/src/components/TimelineBar/hooks/index.ts +5 -0
- package/src/components/TimelineBar/hooks/useHighlightRange.ts +21 -0
- package/src/components/TimelineBar/hooks/useMonthGuides.ts +40 -0
- package/src/components/TimelineBar/hooks/useTickValues.ts +18 -0
- package/src/components/TimelineBar/hooks/useVisibleRange.ts +43 -0
- package/src/components/TimelineBar/hooks/useWeekGuides.ts +39 -0
- package/src/components/TimelineBar/index.tsx +63 -0
- package/src/components/TimelineBar/utils.ts +27 -0
- package/src/components/index.ts +5 -0
- package/src/rn.ts +1 -0
- 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.
|
|
3
|
+
"version": "0.3.17",
|
|
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": "
|
|
45
|
+
"gitHead": "2b3434df547bcd524ee9afd17773d6b789cbe2b2",
|
|
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 => (
|
package/src/antd/form/index.ts
CHANGED
package/src/antd/index.ts
CHANGED
|
@@ -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
|
+
}
|