wip: Form改造中

This commit is contained in:
kailong321200875 2023-04-27 17:44:43 +08:00
parent 89844e441d
commit 1d0f4b4c39
6 changed files with 221 additions and 88 deletions

View File

@ -1,5 +1,5 @@
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import type { Slots } from 'vue' import { unref, type Slots } from 'vue'
import { getSlot } from '@/utils/tsxHelper' import { getSlot } from '@/utils/tsxHelper'
import { PlaceholderMoel } from './types' import { PlaceholderMoel } from './types'
import { FormSchema } from '@/types/form' import { FormSchema } from '@/types/form'
@ -74,12 +74,14 @@ export const setGridProp = (col: ColProps = {}): ColProps => {
*/ */
export const setComponentProps = (item: FormSchema): Recordable => { export const setComponentProps = (item: FormSchema): Recordable => {
// const notNeedClearable = ['ColorPicker'] // const notNeedClearable = ['ColorPicker']
const componentProps = { const componentProps: Recordable = {
clearable: true, clearable: true,
...item.componentProps ...item.componentProps
} }
// 需要删除额外的属性 // 需要删除额外的属性
delete componentProps?.slots if (componentProps.slots) {
delete componentProps.slots
}
return componentProps return componentProps
} }
@ -93,8 +95,8 @@ export const setItemComponentSlots = (formModel: any, slotsProps: Recordable = {
for (const key in slotsProps) { for (const key in slotsProps) {
if (slotsProps[key]) { if (slotsProps[key]) {
if (isFunction(slotsProps[key])) { if (isFunction(slotsProps[key])) {
slotObj[key] = () => { slotObj[key] = (item: any) => {
return slotsProps[key]?.(formModel) return slotsProps[key]?.(unref(item?.item) || undefined, formModel)
} }
} else { } else {
slotObj[key] = () => { slotObj[key] = () => {

View File

@ -214,7 +214,9 @@ export default {
default: 'Default', default: 'Default',
icon: 'Icon', icon: 'Icon',
mixed: 'Mixed', mixed: 'Mixed',
password: 'Password',
textarea: 'Textarea', textarea: 'Textarea',
remoteSearch: 'Remote search',
slot: 'Slot', slot: 'Slot',
position: 'Position', position: 'Position',
autocomplete: 'Autocomplete', autocomplete: 'Autocomplete',

View File

@ -214,7 +214,9 @@ export default {
default: '默认', default: '默认',
icon: '图标', icon: '图标',
mixed: '复合型', mixed: '复合型',
password: '密码框',
textarea: '多行文本', textarea: '多行文本',
remoteSearch: '远程搜索',
slot: '插槽', slot: '插槽',
position: '位置', position: '位置',
autocomplete: '自动补全', autocomplete: '自动补全',

View File

@ -1,5 +1,5 @@
import { CSSProperties } from 'vue' import { CSSProperties } from 'vue'
import { InputProps } from 'element-plus' import { InputProps, AutocompleteProps, InputNumberProps } from 'element-plus'
export enum ComponentNameEnum { export enum ComponentNameEnum {
RADIO = 'Radio', RADIO = 'Radio',
@ -25,6 +25,16 @@ export enum ComponentNameEnum {
EDITOR = 'Editor' 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 { export interface InputComponentProps {
value?: string | number value?: string | number
maxlength?: number | string maxlength?: number | string
@ -37,8 +47,8 @@ export interface InputComponentProps {
showPassword?: boolean showPassword?: boolean
disabled?: boolean disabled?: boolean
size?: InputProps['size'] size?: InputProps['size']
prefixIcon?: 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 | (<T>(data: T | any) => string | JSX.Element) suffixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
type?: InputProps['type'] type?: InputProps['type']
rows?: number rows?: number
autosize?: boolean | { Pows?: numer; maxRows?: number } autosize?: boolean | { Pows?: numer; maxRows?: number }
@ -63,22 +73,69 @@ export interface InputComponentProps {
input?: (value: string | number) => void input?: (value: string | number) => void
} }
slots?: { slots?: {
prefix?: JSX.Element | (<T>(data: T | any) => JSX.Element) prefix?: JSX.Element | ((item: any, data: any) => JSX.Element)
suffix?: JSX.Element | (<T>(data: T | any) => JSX.Element) suffix?: JSX.Element | ((item: any, data: any) => JSX.Element)
prepend?: JSX.Element | (<T>(data: T | any) => JSX.Element) prepend?: JSX.Element | ((item: any, data: any) => JSX.Element)
append?: JSX.Element | (<T>(data: T | any) => JSX.Element) append?: JSX.Element | ((item: any, data: any) => JSX.Element)
} }
} }
type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K export interface AutocompleteComponentProps {
? K extends string value?: string
? K extends `${infer A}_${infer B}` placeholder?: string
? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}` clearable?: boolean
: Capitalize<Lowercase<K>> disabled?: boolean
: never valueKey?: string
: never 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 { export interface ColProps {
span?: number span?: number

61
src/types/form.d.ts vendored
View File

@ -3,8 +3,9 @@ import {
ColProps, ColProps,
ComponentProps, ComponentProps,
ComponentName, ComponentName,
ComponentNameEnum, InputComponentProps,
InputComponentProps AutocompleteComponentProps,
InputNumberComponentProps
} from '@/types/components' } from '@/types/components'
import { FormValueType, FormValueType } from '@/types/form' import { FormValueType, FormValueType } from '@/types/form'
import type { AxiosPromise } from 'axios' import type { AxiosPromise } from 'axios'
@ -28,29 +29,53 @@ export type FormItemProps = {
} }
export interface FormSchema { export interface FormSchema {
// 唯一值 /**
*
*/
field: string field: string
// 标题
label?: string
// 提示
labelMessage?: string
// col组件属性
colProps?: ColProps
// 表单组件属性slots对应的是表单组件的插槽规则${field}-xxx具体可以查看element-plus文档
// componentProps?: { slots?: Recordable } & ComponentProps
/** /**
* slots对应的是表单组件的插槽${field}-xxxelement-plus文档 *
*/
label?: string
/**
*
*/
labelMessage?: string
/**
* col组件属性
*/
colProps?: ColProps
/**
* element-plus文档
*/
componentProps?: InputComponentProps | AutocompleteComponentProps | InputNumberComponentProps
/**
* formItem组件属性element-plus文档
*/ */
componentProps?: InputComponentProps
// formItem组件属性
formItemProps?: FormItemProps formItemProps?: FormItemProps
// 渲染的组件
/**
*
*/
component?: ComponentName component?: ComponentName
// 初始值
/**
*
*/
value?: FormValueType value?: FormValueType
// 是否隐藏
/**
*
*/
hidden?: boolean hidden?: boolean
// 远程加载下拉项
/**
* @returns
*/
api?: <T = any>() => AxiosPromise<T> api?: <T = any>() => AxiosPromise<T>
} }

View File

@ -1,4 +1,4 @@
<script setup lang="ts"> <script setup lang="tsx">
import { Form } from '@/components/Form' import { Form } from '@/components/Form'
import { reactive, ref, onMounted, computed, unref } from 'vue' import { reactive, ref, onMounted, computed, unref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
@ -25,6 +25,17 @@ const querySearch = (queryString: string, cb: Fn) => {
// call callback function to return suggestions // call callback function to return suggestions
cb(results) 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) => { const createFilter = (queryString: string) => {
return (restaurant: Recordable) => { return (restaurant: Recordable) => {
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0 return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
@ -359,7 +370,11 @@ const schema = reactive<FormSchema[]>([
{ {
field: 'field2', field: 'field2',
label: t('formDemo.default'), 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', field: 'field3',
@ -378,7 +393,7 @@ const schema = reactive<FormSchema[]>([
component: 'Input', component: 'Input',
componentProps: { componentProps: {
slots: { slots: {
suffix: (data: any) => { suffix: (_, data: any) => {
return unref(toggle) && data.field4 return unref(toggle) && data.field4
? useIcon({ icon: 'ep:calendar' }) ? useIcon({ icon: 'ep:calendar' })
: useIcon({ icon: 'ep:share' }) : useIcon({ icon: 'ep:share' })
@ -394,12 +409,20 @@ const schema = reactive<FormSchema[]>([
componentProps: { componentProps: {
slots: { slots: {
prepend: useIcon({ icon: 'ep:calendar' }), prepend: useIcon({ icon: 'ep:calendar' }),
append: (data: any) => { append: (_, data: any) => {
return data.field5 ? useIcon({ icon: 'ep:calendar' }) : useIcon({ icon: 'ep:share' }) 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', field: 'field6',
label: t('formDemo.textarea'), label: t('formDemo.textarea'),
@ -408,53 +431,75 @@ const schema = reactive<FormSchema[]>([
type: 'textarea', type: 'textarea',
rows: 2 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', // field: 'field13',
// label: t('formDemo.select'), // label: t('formDemo.select'),