@wzyjs/components 0.2.41

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.
@@ -0,0 +1,73 @@
1
+ 'use client'
2
+
3
+ import { getRequire } from '../utils'
4
+ import { Apis, Column } from '../types'
5
+
6
+ interface ValidatorProps<I> {
7
+ columns: Column<I>[],
8
+ findApi?: Apis<I>['find']
9
+ }
10
+
11
+ const validatorValue = <I>(item: Column<I>, findApi: Apis<I>['find']): any[] => {
12
+ const { validator, title } = item
13
+
14
+ if (!validator?.length) {
15
+ return []
16
+ }
17
+
18
+ return validator.map(item => {
19
+ if (!item) {
20
+ return
21
+ }
22
+
23
+ const [type, option] = Array.isArray(item) ? item : [item]
24
+
25
+ switch (type) {
26
+ case 'require':
27
+ return getRequire(title as string)
28
+
29
+ case 'unique':
30
+ if (!findApi) {
31
+ return
32
+ }
33
+
34
+ return {
35
+ asyncValidator: async ({ field }: { field: keyof I }, value: string) => {
36
+ if (!value || !findApi) {
37
+ return
38
+ }
39
+
40
+ const { data } = await findApi({ [field]: { type: 'eq', value } } as any)
41
+ if (data) {
42
+ return Promise.reject(option)
43
+ }
44
+
45
+ return Promise.resolve()
46
+ },
47
+ }
48
+
49
+ default:
50
+ return {}
51
+ }
52
+ }).filter(item => item)
53
+ }
54
+
55
+ export default <I>(props: ValidatorProps<I>): Column<I>[] => {
56
+ const { columns, findApi } = props
57
+
58
+ return columns.map(item => {
59
+ if (item?.validator) {
60
+ item.formItemProps = {
61
+ ...(item.formItemProps || {}),
62
+ rules: [
63
+ // @ts-ignore
64
+ ...(item.rules || []),
65
+ ...validatorValue(item, findApi),
66
+ ].flat(),
67
+ }
68
+ delete item.validator
69
+ }
70
+
71
+ return item
72
+ })
73
+ }
@@ -0,0 +1,36 @@
1
+ import { TableProColumns } from '../TablePro'
2
+ import { RequestRes, Pagination, Where, Order } from '@wzyjs/types'
3
+
4
+ // 扩展了 ProColumns 的能力
5
+ export type Column<I, ValueType = 'text'> = TableProColumns<I, ValueType> & {
6
+ hideInCreate?: true, // 创建表单里隐藏
7
+ hideInUpdate?: true, // 编辑表单里隐藏
8
+ validator?: ('require' | null | ['unique', string])[], // 使用内置的校验规则
9
+ }
10
+
11
+ export type ColumnContent = {
12
+ type: 'create' | 'update' | 'list',
13
+ isCreate?: boolean,
14
+ isUpdate?: boolean,
15
+ isList?: boolean,
16
+ }
17
+
18
+ export type Columns<I, ValueType = 'text'> = Column<I, ValueType>[] | ((content: ColumnContent) => Column<I>[])
19
+
20
+ export type Apis<I> = {
21
+ list: (params: {
22
+ where?: Where,
23
+ order?: Order,
24
+ pagination?: Pagination
25
+ }) => Promise<RequestRes<{ data: I[], total: number }>>,
26
+ find?: (params: I) => Promise<RequestRes<unknown>>,
27
+ create?: (data: Partial<I>) => Promise<RequestRes<unknown>>,
28
+ update?: (id: string, data: Partial<I>) => Promise<RequestRes<unknown>>,
29
+ remove?: (id: string) => Promise<RequestRes<unknown>>,
30
+ }
31
+
32
+ // 调用各个接口时的附加参数
33
+ export type ApiParams = {
34
+ list?: Where,
35
+ create?: Where,
36
+ }
@@ -0,0 +1,17 @@
1
+ import { isFunction, cloneDeep } from '@wzyjs/utils'
2
+ import { Column, Columns, ColumnContent } from './types'
3
+
4
+ export const getRequireFormProps = (label?: string) => ({
5
+ rules: [
6
+ getRequire(label),
7
+ ],
8
+ })
9
+
10
+ export const getRequire = (label?: string) => ({
11
+ required: true,
12
+ message: `${label + ' ' || '此项'}为必填项`,
13
+ })
14
+
15
+ export const handleColumns = <I>(columns: Columns<I>, content: ColumnContent, mids: any[]): Column<I>[] => {
16
+ return mids.reduce((columns, mid) => mid(columns), isFunction(columns) ? columns(content) : cloneDeep(columns))
17
+ }
@@ -0,0 +1,10 @@
1
+ .picker {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 0.5rem;
5
+
6
+ :global(.ant-picker-input input) {
7
+ text-align: center !important;
8
+ cursor: pointer;
9
+ }
10
+ }
@@ -0,0 +1,75 @@
1
+ 'use client'
2
+
3
+ import React, { useState } from 'react'
4
+
5
+ import { DatePicker, Button } from 'antd'
6
+ import { LeftOutlined, RightOutlined } from '@ant-design/icons'
7
+
8
+ import { dayjs, type Dayjs } from '@wzyjs/utils'
9
+
10
+ import styles from './index.module.scss'
11
+
12
+ interface DateSwitcherProps {
13
+ value?: Dayjs
14
+ onChange?: (date: Dayjs) => void
15
+ }
16
+
17
+ export const DateSwitcher = (props: DateSwitcherProps) => {
18
+ const { value = dayjs(), onChange } = props
19
+
20
+ const [selectedDate, setSelectedDate] = useState(value)
21
+
22
+ const handleDateChange = (date: Dayjs | null) => {
23
+ if (date) {
24
+ setSelectedDate(date)
25
+ onChange?.(date)
26
+ }
27
+ }
28
+
29
+ const handlePrevDay = () => {
30
+ const newDate = selectedDate.subtract(1, 'day')
31
+ handleDateChange(newDate)
32
+ }
33
+
34
+ const handleNextDay = () => {
35
+ const newDate = selectedDate.add(1, 'day')
36
+ handleDateChange(newDate)
37
+ }
38
+
39
+ const isToday = selectedDate.isSame(dayjs(), 'day')
40
+
41
+ return (
42
+ <div className={styles.picker}>
43
+ <Button
44
+ type='text'
45
+ icon={<LeftOutlined />}
46
+ onClick={handlePrevDay}
47
+ className='flex items-center justify-center'
48
+ />
49
+ <DatePicker
50
+ value={selectedDate}
51
+ onChange={handleDateChange}
52
+ allowClear={false}
53
+ suffixIcon={null}
54
+ className='w-40 text-center cursor-pointer'
55
+ style={{ border: 'none' }}
56
+ components={{
57
+ input: props => (
58
+ <div {...props} className={isToday ? 'text-green-600' : ''}>
59
+ <span>{selectedDate.format('YYYY-MM-DD')}</span>
60
+ <span style={{ marginLeft: 5 }}>
61
+ (周{['日', '一', '二', '三', '四', '五', '六'][dayjs(selectedDate).day()]})
62
+ </span>
63
+ </div>
64
+ ),
65
+ }}
66
+ />
67
+ <Button
68
+ type='text'
69
+ icon={<RightOutlined />}
70
+ onClick={handleNextDay}
71
+ className='flex items-center justify-center'
72
+ />
73
+ </div>
74
+ )
75
+ }
@@ -0,0 +1,36 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+
5
+ interface DownloadLinkProps {
6
+ url: string
7
+ filename?: string
8
+ }
9
+
10
+ export const DownloadLink = ({ url, filename }: DownloadLinkProps) => {
11
+ const handleDownload = (e: any) => {
12
+ e.preventDefault()
13
+ fetch(url, { method: 'GET' })
14
+ .then(response => response.blob())
15
+ .then(blob => {
16
+ const downloadUrl = window.URL.createObjectURL(blob)
17
+ const a = document.createElement('a')
18
+ a.href = downloadUrl
19
+ a.download = filename || '下载文件'
20
+ document.body.appendChild(a)
21
+ a.click()
22
+ a.remove()
23
+ window.URL.revokeObjectURL(downloadUrl)
24
+ })
25
+ .catch((err) => {
26
+ console.log(666, err)
27
+ alert('下载失败')
28
+ })
29
+ }
30
+
31
+ return (
32
+ <a href={url} onClick={handleDownload}>
33
+ 下载
34
+ </a>
35
+ )
36
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+
5
+ import { Space } from 'antd'
6
+ import {
7
+ DragDropContext,
8
+ Draggable,
9
+ Droppable,
10
+ type OnDragEndResponder,
11
+ type DraggableProvided,
12
+ } from 'react-beautiful-dnd'
13
+
14
+ interface DragSortProps<T> {
15
+ direction?: 'vertical' | 'horizontal'
16
+ droppableId?: string
17
+ list?: T[]
18
+ children: (item: T, provided: DraggableProvided) => React.ReactNode
19
+ lastChildren?: React.ReactNode
20
+ onDragEnd?: OnDragEndResponder
21
+ hasContext?: boolean
22
+ dropType?: string
23
+ }
24
+
25
+ export { type DropResult, type DraggableProvided } from 'react-beautiful-dnd'
26
+
27
+ export const DragSort = <T extends { id: string }>(props: DragSortProps<T>) => {
28
+ const {
29
+ dropType,
30
+ direction = 'vertical',
31
+ droppableId = 'list',
32
+ list,
33
+ children,
34
+ hasContext = true,
35
+ lastChildren,
36
+ onDragEnd,
37
+ } = props
38
+
39
+ const content = (
40
+ <Droppable
41
+ type={dropType}
42
+ droppableId={droppableId}
43
+ direction={direction}
44
+ isDropDisabled={false}
45
+ isCombineEnabled={false}
46
+ ignoreContainerClipping
47
+ >
48
+ {provided => (
49
+ <div ref={provided.innerRef} {...provided.droppableProps}>
50
+ <Space direction={direction} wrap style={{ width: '100%' }}>
51
+ {list?.map((item, index) => (
52
+ <Draggable key={item.id} draggableId={item.id.toString()} index={index}>
53
+ {provided => (
54
+ <div ref={provided.innerRef} {...provided.draggableProps} >
55
+ {children(item, provided)}
56
+ </div>
57
+ )}
58
+ </Draggable>
59
+ ))}
60
+ {provided.placeholder}
61
+ {lastChildren}
62
+ </Space>
63
+ </div>
64
+ )}
65
+ </Droppable>
66
+ )
67
+
68
+ if (!hasContext) {
69
+ return content
70
+ }
71
+
72
+ return (
73
+ <DragDropContext onDragEnd={onDragEnd}>
74
+ {content}
75
+ </DragDropContext>
76
+ )
77
+ }
@@ -0,0 +1,57 @@
1
+ 'use client'
2
+
3
+ import { useImperativeHandle } from 'react'
4
+ import { Select, SelectProps, Spin } from 'antd'
5
+ import { useRequest } from 'ahooks'
6
+
7
+ export interface FetchSelectProps<I> extends SelectProps {
8
+ selectRef?: any,
9
+ isDetail?: boolean, // 只返回id 还是返回整个信息
10
+ searchApi: (params: { search: string }) => Promise<any>,
11
+ convertData?: (data: I[]) => any[],
12
+ onChange?: (value: any) => void
13
+ }
14
+
15
+ export interface FetchSelectRef {
16
+ refresh: () => void
17
+ }
18
+
19
+ export const FetchSelect = <I extends { id: string | number }, >(props: FetchSelectProps<I>) => {
20
+ const { searchApi, convertData, onChange, isDetail = false, selectRef, ...other } = props
21
+
22
+ const { data, run, loading, refresh } = useRequest(searchApi, {
23
+ debounceWait: 100,
24
+ })
25
+
26
+ const onSearch = (value: string) => {
27
+ run({ search: value })
28
+ }
29
+
30
+ useImperativeHandle(selectRef, () => ({
31
+ refresh,
32
+ }))
33
+
34
+ return (
35
+ <Select
36
+ showSearch
37
+ defaultActiveFirstOption={false}
38
+ popupMatchSelectWidth={false}
39
+ suffixIcon={null}
40
+ filterOption={false}
41
+ notFoundContent={loading ? <Spin size='small' /> : null}
42
+ loading={loading}
43
+ {...other}
44
+
45
+ options={convertData ? convertData(data?.data || []) : data?.data}
46
+ onSearch={onSearch}
47
+ onChange={value => {
48
+ if (isDetail) {
49
+ const detail = data?.data?.find(item => item.id === value)
50
+ onChange?.(detail)
51
+ } else {
52
+ onChange?.(value)
53
+ }
54
+ }}
55
+ />
56
+ )
57
+ }
@@ -0,0 +1,52 @@
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
+ }
@@ -0,0 +1,28 @@
1
+ 'use client'
2
+
3
+ import React, { useImperativeHandle, useRef } from 'react'
4
+ import { BetaSchemaForm, ProFormInstance } from '@ant-design/pro-components'
5
+ import { FormSchema } from '@ant-design/pro-form/es/components/SchemaForm'
6
+
7
+ export type { ProFormColumnsType, ProFormInstance } from '@ant-design/pro-components'
8
+
9
+ export type FormProProps<T, ValueType> = FormSchema<T, ValueType>
10
+
11
+ export const FormPro = <T, ValueType>(props: FormProProps<T, ValueType>) => {
12
+
13
+ const formRef = useRef<ProFormInstance>(null)
14
+ useImperativeHandle(props.formRef, () => formRef?.current, [formRef])
15
+
16
+ // useEffect(() => {
17
+ // if (props.value) {
18
+ // formRef.current?.setFieldsValue(props.value)
19
+ // }
20
+ // }, [JSON.stringify(props.value)])
21
+
22
+ return (
23
+ <BetaSchemaForm
24
+ {...props}
25
+ formRef={formRef}
26
+ />
27
+ )
28
+ }
@@ -0,0 +1,18 @@
1
+ 'use client'
2
+
3
+ import React, { CSSProperties } from 'react'
4
+
5
+ export interface HtmlProProps {
6
+ html: string;
7
+ style?: CSSProperties
8
+ }
9
+
10
+ export const HtmlPro = (props: HtmlProProps) => {
11
+ const { html, style = {} } = props
12
+ return (
13
+ <div
14
+ style={{ minWidth: '100%', ...style }}
15
+ dangerouslySetInnerHTML={{ __html: html }}
16
+ />
17
+ )
18
+ }
@@ -0,0 +1,52 @@
1
+ 'use client'
2
+
3
+ import React, { useEffect, CSSProperties } from 'react'
4
+ import { Spin } from 'antd'
5
+ import { useBoolean } from '@wzyjs/hooks'
6
+
7
+ export interface IframeProProps {
8
+ url: string;
9
+ errMessage?: string;
10
+ style?: CSSProperties;
11
+ isShowLoading?: boolean;
12
+ }
13
+
14
+ export const IframePro = (props: IframeProProps) => {
15
+ const { url, style, errMessage = '', isShowLoading = false } = props
16
+
17
+ const [loading, { setTrue, setFalse }] = useBoolean(true)
18
+
19
+ useEffect(() => {
20
+ setTrue()
21
+ }, [url])
22
+
23
+ if (!url) {
24
+ return (
25
+ <span
26
+ style={{
27
+ ...style,
28
+ color: '#ccc',
29
+ fontSize: 14,
30
+ display: 'flex',
31
+ flexDirection: 'column',
32
+ justifyContent: 'center',
33
+ alignItems: 'center',
34
+ }}
35
+ >
36
+ {errMessage || '没有url'}
37
+ </span>
38
+ )
39
+ }
40
+
41
+ return (
42
+ <Spin spinning={loading && isShowLoading}>
43
+ <iframe
44
+ src={url}
45
+ width='100%'
46
+ height='100%'
47
+ style={style}
48
+ onLoad={setFalse}
49
+ />
50
+ </Spin>
51
+ )
52
+ }
@@ -0,0 +1,21 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import ReactJson, { ReactJsonViewProps } from 'react-json-view'
5
+
6
+ export type JsonViewProps = ReactJsonViewProps
7
+
8
+ export const JsonView = (props: JsonViewProps) => {
9
+ const { style } = props
10
+
11
+ return (
12
+ <ReactJson
13
+ enableClipboard={false}
14
+ collapsed={true}
15
+ displayObjectSize={false}
16
+ displayDataTypes={false}
17
+ {...props}
18
+ style={{ overflow: 'auto', ...style }}
19
+ />
20
+ )
21
+ }
@@ -0,0 +1,63 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import { Row, Col, Image } from 'antd'
5
+
6
+ export const MultiImageDisplay = ({ images = [], preview = false }) => {
7
+ const renderImages = () => {
8
+ const count = images.length
9
+
10
+ if (count === 1) {
11
+ return (
12
+ <Image src={images[0]} preview={preview} style={{ width: '100%', height: 'auto' }} />
13
+ )
14
+ }
15
+
16
+ if (count === 2) {
17
+ return (
18
+ <Row gutter={8}>
19
+ {images.map((img, index) => (
20
+ <Col span={12} key={index}>
21
+ <Image src={img} preview={preview} style={{ width: '100%', height: 'auto' }} />
22
+ </Col>
23
+ ))}
24
+ </Row>
25
+ )
26
+ }
27
+
28
+ if (count === 3) {
29
+ return (
30
+ <div>
31
+ <Row gutter={8}>
32
+ {images.slice(0, 2).map((img, index) => (
33
+ <Col span={12} key={index}>
34
+ <Image src={img} preview={preview} style={{ width: '100%', height: 'auto' }} />
35
+ </Col>
36
+ ))}
37
+ </Row>
38
+ <Row gutter={8} style={{ marginTop: '8px' }}>
39
+ <Col span={24}>
40
+ <Image src={images[2]} preview={preview} style={{ width: '100%', height: 'auto' }} />
41
+ </Col>
42
+ </Row>
43
+ </div>
44
+ )
45
+ }
46
+
47
+ if (count >= 4) {
48
+ return (
49
+ <Row>
50
+ {images.map((img, index) => (
51
+ <Col span={12} key={index}>
52
+ <Image src={img} preview={preview} style={{ width: '100%', height: 'auto' }} />
53
+ </Col>
54
+ ))}
55
+ </Row>
56
+ )
57
+ }
58
+
59
+ return null
60
+ }
61
+
62
+ return <div>{renderImages()}</div>
63
+ }
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ import React, { ReactNode, useImperativeHandle, useRef } from 'react'
4
+ import { _ } from '@wzyjs/utils'
5
+ import {
6
+ ActionType,
7
+ ParamsType,
8
+ ProColumns,
9
+ ProFormInstance,
10
+ ProTable,
11
+ ProTableProps,
12
+ } from '@ant-design/pro-components'
13
+
14
+ export type { ActionType, ProColumns } from '@ant-design/pro-components'
15
+
16
+ export type TableProColumns<T, ValueType> = ProColumns<T, ValueType> & {
17
+ summary?: (data: readonly T[]) => ReactNode,
18
+ }
19
+
20
+ export type TableProProps<T, U, ValueType = 'text'> = Omit<ProTableProps<T, U, ValueType>, 'columns'> & {
21
+ columns: TableProColumns<T, ValueType>[]
22
+ }
23
+
24
+ const defaultTableProps = {
25
+ rowKey: 'id',
26
+ cardBordered: true,
27
+ options: false,
28
+ dateFormatter: 'string',
29
+ headerTitle: '列表',
30
+ scroll: {
31
+ x: 'max-content',
32
+ },
33
+ }
34
+
35
+ export const TablePro = <T extends Record<string, any>, U extends ParamsType, ValueType = 'text'>(props: TableProProps<T, U, ValueType>) => {
36
+ const tableProps = _.merge(_.cloneDeep(defaultTableProps), props)
37
+
38
+ const actionRef = useRef<ActionType>(undefined)
39
+ useImperativeHandle(props.actionRef, () => actionRef?.current, [actionRef])
40
+
41
+ const formRef = useRef<ProFormInstance>(undefined)
42
+ useImperativeHandle(props.formRef, () => formRef?.current, [formRef])
43
+
44
+ const defaultsSummary = (data: readonly T[]) => {
45
+ return (
46
+ <ProTable.Summary.Row>
47
+ {tableProps.columns?.map((item: any, index: number) => {
48
+ return (
49
+ <ProTable.Summary.Cell key={index} index={index}>
50
+ {item.summary?.(data) || ''}
51
+ </ProTable.Summary.Cell>
52
+ )
53
+ })}
54
+ </ProTable.Summary.Row>
55
+ )
56
+ }
57
+
58
+ return (
59
+ <ProTable
60
+ {...tableProps}
61
+ formRef={formRef}
62
+ actionRef={actionRef}
63
+ summary={tableProps.summary || defaultsSummary}
64
+ />
65
+ )
66
+ }