wip: Form改造中
This commit is contained in:
parent
89844e441d
commit
1d0f4b4c39
|
@ -1,5 +1,5 @@
|
|||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { Slots } from 'vue'
|
||||
import { unref, type Slots } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import { PlaceholderMoel } from './types'
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
@ -74,12 +74,14 @@ export const setGridProp = (col: ColProps = {}): ColProps => {
|
|||
*/
|
||||
export const setComponentProps = (item: FormSchema): Recordable => {
|
||||
// const notNeedClearable = ['ColorPicker']
|
||||
const componentProps = {
|
||||
const componentProps: Recordable = {
|
||||
clearable: true,
|
||||
...item.componentProps
|
||||
}
|
||||
// 需要删除额外的属性
|
||||
delete componentProps?.slots
|
||||
if (componentProps.slots) {
|
||||
delete componentProps.slots
|
||||
}
|
||||
return componentProps
|
||||
}
|
||||
|
||||
|
@ -93,8 +95,8 @@ export const setItemComponentSlots = (formModel: any, slotsProps: Recordable = {
|
|||
for (const key in slotsProps) {
|
||||
if (slotsProps[key]) {
|
||||
if (isFunction(slotsProps[key])) {
|
||||
slotObj[key] = () => {
|
||||
return slotsProps[key]?.(formModel)
|
||||
slotObj[key] = (item: any) => {
|
||||
return slotsProps[key]?.(unref(item?.item) || undefined, formModel)
|
||||
}
|
||||
} else {
|
||||
slotObj[key] = () => {
|
||||
|
|
|
@ -214,7 +214,9 @@ export default {
|
|||
default: 'Default',
|
||||
icon: 'Icon',
|
||||
mixed: 'Mixed',
|
||||
password: 'Password',
|
||||
textarea: 'Textarea',
|
||||
remoteSearch: 'Remote search',
|
||||
slot: 'Slot',
|
||||
position: 'Position',
|
||||
autocomplete: 'Autocomplete',
|
||||
|
|
|
@ -214,7 +214,9 @@ export default {
|
|||
default: '默认',
|
||||
icon: '图标',
|
||||
mixed: '复合型',
|
||||
password: '密码框',
|
||||
textarea: '多行文本',
|
||||
remoteSearch: '远程搜索',
|
||||
slot: '插槽',
|
||||
position: '位置',
|
||||
autocomplete: '自动补全',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { CSSProperties } from 'vue'
|
||||
import { InputProps } from 'element-plus'
|
||||
import { InputProps, AutocompleteProps, InputNumberProps } from 'element-plus'
|
||||
|
||||
export enum ComponentNameEnum {
|
||||
RADIO = 'Radio',
|
||||
|
@ -25,6 +25,16 @@ export enum ComponentNameEnum {
|
|||
EDITOR = 'Editor'
|
||||
}
|
||||
|
||||
type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
|
||||
? K extends string
|
||||
? K extends `${infer A}_${infer B}`
|
||||
? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}`
|
||||
: Capitalize<Lowercase<K>>
|
||||
: never
|
||||
: never
|
||||
|
||||
export type ComponentName = CamelCaseComponentName
|
||||
|
||||
export interface InputComponentProps {
|
||||
value?: string | number
|
||||
maxlength?: number | string
|
||||
|
@ -37,8 +47,8 @@ export interface InputComponentProps {
|
|||
showPassword?: boolean
|
||||
disabled?: boolean
|
||||
size?: InputProps['size']
|
||||
prefixIcon?: string | JSX.Element | (<T>(data: T | any) => string | JSX.Element)
|
||||
suffixIcon?: string | JSX.Element | (<T>(data: T | any) => string | JSX.Element)
|
||||
prefixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
|
||||
suffixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
|
||||
type?: InputProps['type']
|
||||
rows?: number
|
||||
autosize?: boolean | { Pows?: numer; maxRows?: number }
|
||||
|
@ -63,22 +73,69 @@ export interface InputComponentProps {
|
|||
input?: (value: string | number) => void
|
||||
}
|
||||
slots?: {
|
||||
prefix?: JSX.Element | (<T>(data: T | any) => JSX.Element)
|
||||
suffix?: JSX.Element | (<T>(data: T | any) => JSX.Element)
|
||||
prepend?: JSX.Element | (<T>(data: T | any) => JSX.Element)
|
||||
append?: JSX.Element | (<T>(data: T | any) => JSX.Element)
|
||||
prefix?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
suffix?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
prepend?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
append?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
}
|
||||
}
|
||||
|
||||
type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
|
||||
? K extends string
|
||||
? K extends `${infer A}_${infer B}`
|
||||
? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}`
|
||||
: Capitalize<Lowercase<K>>
|
||||
: never
|
||||
: never
|
||||
export interface AutocompleteComponentProps {
|
||||
value?: string
|
||||
placeholder?: string
|
||||
clearable?: boolean
|
||||
disabled?: boolean
|
||||
valueKey?: string
|
||||
debounce?: number
|
||||
placement?: AutocompleteProps['placement']
|
||||
fetchSuggestions?: (queryString: string, callback: (data: string[]) => void) => void
|
||||
triggerOnFocus?: boolean
|
||||
selectWhenUnmatched?: boolean
|
||||
name?: string
|
||||
label?: string
|
||||
hideLoading?: boolean
|
||||
popperClass?: string
|
||||
popperAppendToBody?: boolean
|
||||
teleported?: boolean
|
||||
highlightFirstItem?: boolean
|
||||
fitInputWidth?: boolean
|
||||
on?: {
|
||||
select?: (item: any) => void
|
||||
change?: (value: string | number) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
prefix?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
suffix?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
prepend?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
append?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
}
|
||||
}
|
||||
|
||||
export type ComponentName = CamelCaseComponentName
|
||||
export interface InputNumberComponentProps {
|
||||
value?: number
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
stepStrictly?: boolean
|
||||
precision?: number
|
||||
size?: InputNumberProps['size']
|
||||
readonly?: boolean
|
||||
disabled?: boolean
|
||||
controls?: boolean
|
||||
controlsPosition?: InputNumberProps['controlsPosition']
|
||||
name?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
id?: string
|
||||
valueOnClear?: number | null | 'min' | 'max'
|
||||
validateEvent?: boolean
|
||||
on?: {
|
||||
change?: (currentValue: number | undefined, oldValue: number | undefined) => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
}
|
||||
}
|
||||
|
||||
export interface ColProps {
|
||||
span?: number
|
||||
|
|
|
@ -3,8 +3,9 @@ import {
|
|||
ColProps,
|
||||
ComponentProps,
|
||||
ComponentName,
|
||||
ComponentNameEnum,
|
||||
InputComponentProps
|
||||
InputComponentProps,
|
||||
AutocompleteComponentProps,
|
||||
InputNumberComponentProps
|
||||
} from '@/types/components'
|
||||
import { FormValueType, FormValueType } from '@/types/form'
|
||||
import type { AxiosPromise } from 'axios'
|
||||
|
@ -28,29 +29,53 @@ export type FormItemProps = {
|
|||
}
|
||||
|
||||
export interface FormSchema {
|
||||
// 唯一值
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
field: string
|
||||
// 标题
|
||||
label?: string
|
||||
// 提示
|
||||
labelMessage?: string
|
||||
// col组件属性
|
||||
colProps?: ColProps
|
||||
// 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档
|
||||
// componentProps?: { slots?: Recordable } & ComponentProps
|
||||
|
||||
/**
|
||||
* 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档
|
||||
* 标题
|
||||
*/
|
||||
label?: string
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
*/
|
||||
labelMessage?: string
|
||||
|
||||
/**
|
||||
* col组件属性
|
||||
*/
|
||||
colProps?: ColProps
|
||||
|
||||
/**
|
||||
* 表单组件属性,具体可以查看element-plus文档
|
||||
*/
|
||||
componentProps?: InputComponentProps | AutocompleteComponentProps | InputNumberComponentProps
|
||||
|
||||
/**
|
||||
* formItem组件属性,具体可以查看element-plus文档
|
||||
*/
|
||||
componentProps?: InputComponentProps
|
||||
// formItem组件属性
|
||||
formItemProps?: FormItemProps
|
||||
// 渲染的组件
|
||||
|
||||
/**
|
||||
* 渲染的组件名称
|
||||
*/
|
||||
component?: ComponentName
|
||||
// 初始值
|
||||
|
||||
/**
|
||||
* 初始值
|
||||
*/
|
||||
value?: FormValueType
|
||||
// 是否隐藏
|
||||
|
||||
/**
|
||||
* 是否隐藏
|
||||
*/
|
||||
hidden?: boolean
|
||||
// 远程加载下拉项
|
||||
|
||||
/**
|
||||
* @returns 远程加载下拉项
|
||||
*/
|
||||
api?: <T = any>() => AxiosPromise<T>
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
<script setup lang="tsx">
|
||||
import { Form } from '@/components/Form'
|
||||
import { reactive, ref, onMounted, computed, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
@ -25,6 +25,17 @@ const querySearch = (queryString: string, cb: Fn) => {
|
|||
// call callback function to return suggestions
|
||||
cb(results)
|
||||
}
|
||||
let timeout: NodeJS.Timeout
|
||||
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
|
||||
const results = queryString
|
||||
? restaurants.value.filter(createFilter(queryString))
|
||||
: restaurants.value
|
||||
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
cb(results)
|
||||
}, 3000 * Math.random())
|
||||
}
|
||||
const createFilter = (queryString: string) => {
|
||||
return (restaurant: Recordable) => {
|
||||
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
|
||||
|
@ -359,7 +370,11 @@ const schema = reactive<FormSchema[]>([
|
|||
{
|
||||
field: 'field2',
|
||||
label: t('formDemo.default'),
|
||||
component: 'Input'
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
formatter: (value) => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','),
|
||||
parser: (value) => value.replace(/\$\s?|(,*)/g, '')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field3',
|
||||
|
@ -378,7 +393,7 @@ const schema = reactive<FormSchema[]>([
|
|||
component: 'Input',
|
||||
componentProps: {
|
||||
slots: {
|
||||
suffix: (data: any) => {
|
||||
suffix: (_, data: any) => {
|
||||
return unref(toggle) && data.field4
|
||||
? useIcon({ icon: 'ep:calendar' })
|
||||
: useIcon({ icon: 'ep:share' })
|
||||
|
@ -394,12 +409,20 @@ const schema = reactive<FormSchema[]>([
|
|||
componentProps: {
|
||||
slots: {
|
||||
prepend: useIcon({ icon: 'ep:calendar' }),
|
||||
append: (data: any) => {
|
||||
append: (_, data: any) => {
|
||||
return data.field5 ? useIcon({ icon: 'ep:calendar' }) : useIcon({ icon: 'ep:share' })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'input-field7',
|
||||
label: t('formDemo.password'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
showPassword: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field6',
|
||||
label: t('formDemo.textarea'),
|
||||
|
@ -408,53 +431,75 @@ const schema = reactive<FormSchema[]>([
|
|||
type: 'textarea',
|
||||
rows: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field7',
|
||||
label: t('formDemo.autocomplete'),
|
||||
component: 'Divider'
|
||||
},
|
||||
{
|
||||
field: 'field8',
|
||||
label: t('formDemo.default'),
|
||||
component: 'Autocomplete',
|
||||
componentProps: {
|
||||
fetchSuggestions: querySearch,
|
||||
on: {
|
||||
select: handleSelect
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field9',
|
||||
label: t('formDemo.slot'),
|
||||
component: 'Autocomplete',
|
||||
componentProps: {
|
||||
fetchSuggestions: querySearch,
|
||||
on: {
|
||||
select: handleSelect
|
||||
},
|
||||
slots: {
|
||||
default: (item: any) => {
|
||||
return (
|
||||
<>
|
||||
<div class="value">{item.value}</div>
|
||||
<span class="link">{item.link}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'autocomplete-field10',
|
||||
label: t('formDemo.remoteSearch'),
|
||||
component: 'Autocomplete',
|
||||
componentProps: {
|
||||
fetchSuggestions: querySearchAsync,
|
||||
on: {
|
||||
select: handleSelect
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field10',
|
||||
component: 'Divider',
|
||||
label: t('formDemo.inputNumber')
|
||||
},
|
||||
{
|
||||
field: 'field11',
|
||||
label: t('formDemo.default'),
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
field: 'field12',
|
||||
label: t('formDemo.position'),
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
controlsPosition: 'right'
|
||||
},
|
||||
value: 10
|
||||
}
|
||||
// {
|
||||
// field: 'field7',
|
||||
// label: t('formDemo.autocomplete'),
|
||||
// component: 'Divider'
|
||||
// },
|
||||
// {
|
||||
// field: 'field8',
|
||||
// label: t('formDemo.default'),
|
||||
// component: 'Autocomplete',
|
||||
// componentProps: {
|
||||
// fetchSuggestions: querySearch,
|
||||
// onSelect: handleSelect
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'field9',
|
||||
// label: t('formDemo.slot'),
|
||||
// component: 'Autocomplete',
|
||||
// componentProps: {
|
||||
// fetchSuggestions: querySearch,
|
||||
// onSelect: handleSelect,
|
||||
// slots: {
|
||||
// default: true
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'field10',
|
||||
// component: 'Divider',
|
||||
// label: t('formDemo.inputNumber')
|
||||
// },
|
||||
// {
|
||||
// field: 'field11',
|
||||
// label: t('formDemo.default'),
|
||||
// component: 'InputNumber',
|
||||
// value: 0
|
||||
// },
|
||||
// {
|
||||
// field: 'field12',
|
||||
// label: t('formDemo.position'),
|
||||
// component: 'InputNumber',
|
||||
// componentProps: {
|
||||
// controlsPosition: 'right'
|
||||
// },
|
||||
// value: 0
|
||||
// },
|
||||
// {
|
||||
// field: 'field13',
|
||||
// label: t('formDemo.select'),
|
||||
|
|
Loading…
Reference in New Issue