wip: Form改造中
This commit is contained in:
parent
89844e441d
commit
1d0f4b4c39
|
@ -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] = () => {
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -214,7 +214,9 @@ export default {
|
||||||
default: '默认',
|
default: '默认',
|
||||||
icon: '图标',
|
icon: '图标',
|
||||||
mixed: '复合型',
|
mixed: '复合型',
|
||||||
|
password: '密码框',
|
||||||
textarea: '多行文本',
|
textarea: '多行文本',
|
||||||
|
remoteSearch: '远程搜索',
|
||||||
slot: '插槽',
|
slot: '插槽',
|
||||||
position: '位置',
|
position: '位置',
|
||||||
autocomplete: '自动补全',
|
autocomplete: '自动补全',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}-xxx,具体可以查看element-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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
Loading…
Reference in New Issue