Merge branch 'v2' of github.com:kailong321200875/vue-element-plus-admin into v2
This commit is contained in:
commit
c72ac07de9
|
@ -0,0 +1,302 @@
|
|||
<script lang="tsx">
|
||||
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
|
||||
import { componentMap } from './componentMap'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import {
|
||||
setTextPlaceholder,
|
||||
setGridProp,
|
||||
setComponentProps,
|
||||
setItemComponentSlots,
|
||||
initModel,
|
||||
setFormItemSlots
|
||||
} from './helper'
|
||||
import { useRenderSelect } from './components/useRenderSelect'
|
||||
import { useRenderRadio } from './components/useRenderRadio'
|
||||
import { useRenderCheckbox } from './components/useRenderCheckbox'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { findIndex } from '@/utils'
|
||||
import { set } from 'lodash-es'
|
||||
import { FormProps } from './types'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('form')
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Form',
|
||||
props: {
|
||||
// 生成Form的布局结构数组
|
||||
schema: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 是否需要栅格布局
|
||||
isCol: propTypes.bool.def(true),
|
||||
// 表单数据对象
|
||||
model: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({})
|
||||
},
|
||||
// 是否自动设置placeholder
|
||||
autoSetPlaceholder: propTypes.bool.def(true),
|
||||
// 是否自定义内容
|
||||
isCustom: propTypes.bool.def(false),
|
||||
// 表单label宽度
|
||||
labelWidth: propTypes.oneOfType([String, Number]).def('auto')
|
||||
},
|
||||
emits: ['register'],
|
||||
setup(props, { slots, expose, emit }) {
|
||||
// element form 实例
|
||||
const elFormRef = ref<ComponentRef<typeof ElForm>>()
|
||||
|
||||
// useForm传入的props
|
||||
const outsideProps = ref<FormProps>({})
|
||||
|
||||
const mergeProps = ref<FormProps>({})
|
||||
|
||||
const getProps = computed(() => {
|
||||
const propsObj = { ...props }
|
||||
Object.assign(propsObj, unref(mergeProps))
|
||||
return propsObj
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const formModel = ref<Recordable>({})
|
||||
|
||||
onMounted(() => {
|
||||
emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
|
||||
})
|
||||
|
||||
// 对表单赋值
|
||||
const setValues = (data: Recordable = {}) => {
|
||||
formModel.value = Object.assign(unref(formModel), data)
|
||||
}
|
||||
|
||||
const setProps = (props: FormProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
outsideProps.value = props
|
||||
}
|
||||
|
||||
const delSchema = (field: string) => {
|
||||
const { schema } = unref(getProps)
|
||||
|
||||
const index = findIndex(schema, (v: FormSchema) => v.field === field)
|
||||
if (index > -1) {
|
||||
schema.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const addSchema = (formSchema: FormSchema, index?: number) => {
|
||||
const { schema } = unref(getProps)
|
||||
if (index !== void 0) {
|
||||
schema.splice(index, 0, formSchema)
|
||||
return
|
||||
}
|
||||
schema.push(formSchema)
|
||||
}
|
||||
|
||||
const setSchema = (schemaProps: FormSetPropsType[]) => {
|
||||
const { schema } = unref(getProps)
|
||||
for (const v of schema) {
|
||||
for (const item of schemaProps) {
|
||||
if (v.field === item.field) {
|
||||
set(v, item.path, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getElFormRef = (): ComponentRef<typeof ElForm> => {
|
||||
return unref(elFormRef) as ComponentRef<typeof ElForm>
|
||||
}
|
||||
|
||||
expose({
|
||||
setValues,
|
||||
formModel,
|
||||
setProps,
|
||||
delSchema,
|
||||
addSchema,
|
||||
setSchema,
|
||||
getElFormRef
|
||||
})
|
||||
|
||||
// 监听表单结构化数组,重新生成formModel
|
||||
watch(
|
||||
() => unref(getProps).schema,
|
||||
(schema = []) => {
|
||||
formModel.value = initModel(schema, unref(formModel))
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
// 渲染包裹标签,是否使用栅格布局
|
||||
const renderWrap = () => {
|
||||
const { isCol } = unref(getProps)
|
||||
const content = isCol ? (
|
||||
<ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
|
||||
) : (
|
||||
renderFormItemWrap()
|
||||
)
|
||||
return content
|
||||
}
|
||||
|
||||
// 是否要渲染el-col
|
||||
const renderFormItemWrap = () => {
|
||||
// hidden属性表示隐藏,不做渲染
|
||||
const { schema = [], isCol } = unref(getProps)
|
||||
|
||||
return schema
|
||||
.filter((v) => !v.hidden)
|
||||
.map((item) => {
|
||||
// 如果是 Divider 组件,需要自己占用一行
|
||||
const isDivider = item.component === 'Divider'
|
||||
const Com = componentMap['Divider'] as ReturnType<typeof defineComponent>
|
||||
return isDivider ? (
|
||||
<Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
|
||||
) : isCol ? (
|
||||
// 如果需要栅格,需要包裹 ElCol
|
||||
<ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
|
||||
) : (
|
||||
renderFormItem(item)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染formItem
|
||||
const renderFormItem = (item: FormSchema) => {
|
||||
// 单独给只有options属性的组件做判断
|
||||
const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
|
||||
const componentSlots = (item?.componentProps as any)?.slots || {}
|
||||
const slotsMap: Recordable = {
|
||||
...setItemComponentSlots(unref(formModel), componentSlots)
|
||||
}
|
||||
if (
|
||||
item?.component !== 'SelectV2' &&
|
||||
item?.component !== 'Cascader' &&
|
||||
item?.componentProps?.options
|
||||
) {
|
||||
slotsMap.default = () => renderOptions(item)
|
||||
}
|
||||
|
||||
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
|
||||
// 如果有 labelMessage,自动使用插槽渲染
|
||||
if (item?.labelMessage) {
|
||||
formItemSlots.label = () => {
|
||||
return (
|
||||
<>
|
||||
<span>{item.label}</span>
|
||||
<ElTooltip placement="right" raw-content>
|
||||
{{
|
||||
content: () => <span v-html={item.labelMessage}></span>,
|
||||
default: () => (
|
||||
<Icon
|
||||
icon="ep:warning"
|
||||
size={16}
|
||||
color="var(--el-color-primary)"
|
||||
class="ml-2px relative top-1px"
|
||||
></Icon>
|
||||
)
|
||||
}}
|
||||
</ElTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
|
||||
{{
|
||||
...formItemSlots,
|
||||
default: () => {
|
||||
const Com = componentMap[item.component as string] as ReturnType<
|
||||
typeof defineComponent
|
||||
>
|
||||
|
||||
const { autoSetPlaceholder } = unref(getProps)
|
||||
|
||||
return slots[item.field] ? (
|
||||
getSlot(slots, item.field, formModel.value)
|
||||
) : (
|
||||
<Com
|
||||
vModel={formModel.value[item.field]}
|
||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={item.componentProps?.style}
|
||||
{...(notRenderOptions.includes(item?.component as string) &&
|
||||
item?.componentProps?.options
|
||||
? { options: item?.componentProps?.options || [] }
|
||||
: {})}
|
||||
>
|
||||
{{ ...slotsMap }}
|
||||
</Com>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
|
||||
// 渲染options
|
||||
const renderOptions = (item: FormSchema) => {
|
||||
switch (item.component) {
|
||||
case 'Select':
|
||||
const { renderSelectOptions } = useRenderSelect(slots)
|
||||
return renderSelectOptions(item)
|
||||
case 'Radio':
|
||||
case 'RadioButton':
|
||||
const { renderRadioOptions } = useRenderRadio()
|
||||
return renderRadioOptions(item)
|
||||
case 'Checkbox':
|
||||
case 'CheckboxButton':
|
||||
const { renderCheckboxOptions } = useRenderCheckbox()
|
||||
return renderCheckboxOptions(item)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤传入Form组件的属性
|
||||
const getFormBindValue = () => {
|
||||
// 避免在标签上出现多余的属性
|
||||
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
|
||||
const props = { ...unref(getProps) }
|
||||
for (const key in props) {
|
||||
if (delKeys.indexOf(key) !== -1) {
|
||||
delete props[key]
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
return () => (
|
||||
<ElForm
|
||||
ref={elFormRef}
|
||||
{...getFormBindValue()}
|
||||
model={props.isCustom ? props.model : formModel}
|
||||
class={prefixCls}
|
||||
>
|
||||
{{
|
||||
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
|
||||
default: () => {
|
||||
const { isCustom } = unref(getProps)
|
||||
return isCustom ? getSlot(slots, 'default') : renderWrap()
|
||||
}
|
||||
}}
|
||||
</ElForm>
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.@{elNamespace}-form.@{namespace}-form .@{elNamespace}-row {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
</style>
|
|
@ -21,6 +21,7 @@ import { set } from 'lodash-es'
|
|||
import { FormProps } from './types'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
import { ComponentNameEnum } from '@/types/components.d'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -172,46 +173,55 @@ export default defineComponent({
|
|||
// 渲染formItem
|
||||
const renderFormItem = (item: FormSchema) => {
|
||||
// 单独给只有options属性的组件做判断
|
||||
const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
|
||||
// const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
|
||||
const componentSlots = (item?.componentProps as any)?.slots || {}
|
||||
const slotsMap: Recordable = {
|
||||
...setItemComponentSlots(unref(formModel), item?.componentProps?.slots)
|
||||
...setItemComponentSlots(unref(formModel), componentSlots)
|
||||
}
|
||||
if (
|
||||
item?.component !== 'SelectV2' &&
|
||||
item?.component !== 'Cascader' &&
|
||||
item?.componentProps?.options
|
||||
) {
|
||||
slotsMap.default = () => renderOptions(item)
|
||||
// 如果是select组件,并且没有自定义模板,自动渲染options
|
||||
if (item.component === ComponentNameEnum.SELECT) {
|
||||
slotsMap.default = !componentSlots.default
|
||||
? () => renderOptions(item)
|
||||
: (option: any) => {
|
||||
console.log(option)
|
||||
return componentSlots.default(option)
|
||||
}
|
||||
}
|
||||
// if (
|
||||
// item?.component !== 'SelectV2' &&
|
||||
// item?.component !== 'Cascader' &&
|
||||
// item?.componentProps?.options
|
||||
// ) {
|
||||
// slotsMap.default = () => renderOptions(item)
|
||||
// }
|
||||
|
||||
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
|
||||
// const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
|
||||
// 如果有 labelMessage,自动使用插槽渲染
|
||||
if (item?.labelMessage) {
|
||||
formItemSlots.label = () => {
|
||||
return (
|
||||
<>
|
||||
<span>{item.label}</span>
|
||||
<ElTooltip placement="right" raw-content>
|
||||
{{
|
||||
content: () => <span v-html={item.labelMessage}></span>,
|
||||
default: () => (
|
||||
<Icon
|
||||
icon="ep:warning"
|
||||
size={16}
|
||||
color="var(--el-color-primary)"
|
||||
class="ml-2px relative top-1px"
|
||||
></Icon>
|
||||
)
|
||||
}}
|
||||
</ElTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
// if (item?.labelMessage) {
|
||||
// formItemSlots.label = () => {
|
||||
// return (
|
||||
// <>
|
||||
// <span>{item.label}</span>
|
||||
// <ElTooltip placement="right" raw-content>
|
||||
// {{
|
||||
// content: () => <span v-html={item.labelMessage}></span>,
|
||||
// default: () => (
|
||||
// <Icon
|
||||
// icon="ep:warning"
|
||||
// size={16}
|
||||
// color="var(--el-color-primary)"
|
||||
// class="ml-2px relative top-1px"
|
||||
// ></Icon>
|
||||
// )
|
||||
// }}
|
||||
// </ElTooltip>
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
return (
|
||||
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
|
||||
{{
|
||||
...formItemSlots,
|
||||
default: () => {
|
||||
const Com = componentMap[item.component as string] as ReturnType<
|
||||
typeof defineComponent
|
||||
|
@ -227,10 +237,6 @@ export default defineComponent({
|
|||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={item.componentProps?.style}
|
||||
{...(notRenderOptions.includes(item?.component as string) &&
|
||||
item?.componentProps?.options
|
||||
? { options: item?.componentProps?.options || [] }
|
||||
: {})}
|
||||
>
|
||||
{{ ...slotsMap }}
|
||||
</Com>
|
||||
|
@ -244,7 +250,7 @@ export default defineComponent({
|
|||
// 渲染options
|
||||
const renderOptions = (item: FormSchema) => {
|
||||
switch (item.component) {
|
||||
case 'Select':
|
||||
case ComponentNameEnum.SELECT:
|
||||
const { renderSelectOptions } = useRenderSelect(slots)
|
||||
return renderSelectOptions(item)
|
||||
case 'Radio':
|
||||
|
|
|
@ -2,14 +2,15 @@ import { ElOption, ElOptionGroup } from 'element-plus'
|
|||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import { Slots } from 'vue'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { ComponentOptions } from '@/types/components'
|
||||
import { SelectComponentProps, SelectOption } from '@/types/components'
|
||||
|
||||
export const useRenderSelect = (slots: Slots) => {
|
||||
// 渲染 select options
|
||||
const renderSelectOptions = (item: FormSchema) => {
|
||||
const componentsProps = item.componentProps as SelectComponentProps
|
||||
// 如果有别名,就取别名
|
||||
const labelAlias = item?.componentProps?.optionsAlias?.labelField
|
||||
return item?.componentProps?.options?.map((option) => {
|
||||
const labelAlias = componentsProps?.labelAlias
|
||||
return componentsProps?.options?.map((option) => {
|
||||
if (option?.options?.length) {
|
||||
return (
|
||||
<ElOptionGroup label={option[labelAlias || 'label']}>
|
||||
|
@ -27,10 +28,11 @@ export const useRenderSelect = (slots: Slots) => {
|
|||
}
|
||||
|
||||
// 渲染 select option item
|
||||
const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => {
|
||||
const renderSelectOptionItem = (item: FormSchema, option: SelectOption) => {
|
||||
// 如果有别名,就取别名
|
||||
const labelAlias = item?.componentProps?.optionsAlias?.labelField
|
||||
const valueAlias = item?.componentProps?.optionsAlias?.valueField
|
||||
const componentsProps = item.componentProps as SelectComponentProps
|
||||
const labelAlias = componentsProps?.labelAlias
|
||||
const valueAlias = componentsProps?.valueAlias
|
||||
|
||||
const { label, value, ...other } = option
|
||||
|
||||
|
@ -43,7 +45,7 @@ export const useRenderSelect = (slots: Slots) => {
|
|||
{{
|
||||
default: () =>
|
||||
// option 插槽名规则,{field}-option
|
||||
item?.componentProps?.optionsSlot
|
||||
componentsProps?.optionsSlot
|
||||
? getSlot(slots, `${item.field}-option`, { item: option })
|
||||
: undefined
|
||||
}}
|
||||
|
|
|
@ -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,167 @@ 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)
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
interface SelectOption {
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
value?: any
|
||||
key?: string | number
|
||||
options?: SelectOption[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface SelectComponentProps {
|
||||
value?: Array | string | number | boolean | Object
|
||||
multiple?: boolean
|
||||
disabled?: boolean
|
||||
valueKey?: string
|
||||
size?: InputNumberProps['size']
|
||||
clearable?: boolean
|
||||
collapseTags?: boolean
|
||||
collapseTagsTooltip?: number
|
||||
multipleLimit?: number
|
||||
name?: string
|
||||
effect?: string
|
||||
autocomplete?: string
|
||||
placeholder?: string
|
||||
filterable?: boolean
|
||||
allowCreate?: boolean
|
||||
filterMethod?: (query: string, item: any) => boolean
|
||||
remote?: boolean
|
||||
remoteMethod?: (query: string) => void
|
||||
remoteShowSuffix?: boolean
|
||||
loading?: boolean
|
||||
loadingText?: string
|
||||
noMatchText?: string
|
||||
noDataText?: string
|
||||
popperClass?: string
|
||||
reserveKeyword?: boolean
|
||||
defaultFirstOption?: boolean
|
||||
popperAppendToBody?: boolean
|
||||
teleported?: boolean
|
||||
persistent?: boolean
|
||||
automaticDropdown?: boolean
|
||||
clearIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
|
||||
fitInputWidth?: boolean
|
||||
suffixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
|
||||
tagType?: 'success' | 'info' | 'warning' | 'danger'
|
||||
validateEvent?: boolean
|
||||
placement?:
|
||||
| 'top'
|
||||
| 'top-start'
|
||||
| 'top-end'
|
||||
| 'bottom'
|
||||
| 'bottom-start'
|
||||
| 'bottom-end'
|
||||
| 'left'
|
||||
| 'left-start'
|
||||
| 'left-end'
|
||||
| 'right'
|
||||
| 'right-start'
|
||||
| 'right-end'
|
||||
maxCollapseTags?: number
|
||||
/**
|
||||
* label别名
|
||||
*/
|
||||
labelAlias?: string
|
||||
|
||||
/**
|
||||
* value别名
|
||||
*/
|
||||
valueAlias?: string
|
||||
|
||||
/**
|
||||
* key别名
|
||||
*/
|
||||
keyAlias?: string
|
||||
|
||||
/**
|
||||
* option是否禁用的统一拦截
|
||||
*/
|
||||
optionDisabled?: (optin: any, data: any) => boolean
|
||||
on?: {
|
||||
change?: (value: string | number | boolean | Object) => void
|
||||
visibleChange?: (visible: boolean) => void
|
||||
removeTag?: (tag: any) => void
|
||||
clear?: () => void
|
||||
blur?: (event: FocusEvent) => void
|
||||
focus?: (event: FocusEvent) => void
|
||||
}
|
||||
slots?: {
|
||||
default?: (item: any) => JSX.Element
|
||||
prefix?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
empty?: JSX.Element | ((item: any, data: any) => JSX.Element)
|
||||
}
|
||||
options?: SelectOption[]
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export interface ColProps {
|
||||
span?: number
|
||||
|
@ -92,7 +247,7 @@ export interface ColProps {
|
|||
|
||||
export interface ComponentOptions extends Recordable {
|
||||
label?: string
|
||||
value?: FormValueType
|
||||
value?: any
|
||||
disabled?: boolean
|
||||
key?: string | number
|
||||
children?: ComponentOptions[]
|
||||
|
|
|
@ -3,8 +3,10 @@ import {
|
|||
ColProps,
|
||||
ComponentProps,
|
||||
ComponentName,
|
||||
ComponentNameEnum,
|
||||
InputComponentProps
|
||||
InputComponentProps,
|
||||
AutocompleteComponentProps,
|
||||
InputNumberComponentProps,
|
||||
SelectComponentProps
|
||||
} from '@/types/components'
|
||||
import { FormValueType, FormValueType } from '@/types/form'
|
||||
import type { AxiosPromise } from 'axios'
|
||||
|
@ -28,29 +30,57 @@ 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
|
||||
| SelectComponentProps
|
||||
|
||||
/**
|
||||
* 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,95 +431,133 @@ 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: 'field13',
|
||||
label: t('formDemo.select'),
|
||||
component: 'Divider'
|
||||
},
|
||||
{
|
||||
field: 'field14',
|
||||
label: t('formDemo.default'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
optionDisabled: (item: any, data: any) => {
|
||||
console.log(item, data)
|
||||
return false
|
||||
},
|
||||
options: [
|
||||
{
|
||||
disabled: true,
|
||||
label: 'option1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option2',
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field15',
|
||||
label: t('formDemo.slot'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option2',
|
||||
value: '2'
|
||||
}
|
||||
],
|
||||
slots: {
|
||||
default: (item) => {
|
||||
console.log(item)
|
||||
return (
|
||||
<>
|
||||
<span style="float: left">{item.label}</span>
|
||||
<span style=" float: right; color: var(--el-text-color-secondary); font-size: 13px;">
|
||||
{item.value}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// {
|
||||
// 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'),
|
||||
// component: 'Divider'
|
||||
// },
|
||||
// {
|
||||
// field: 'field14',
|
||||
// label: t('formDemo.default'),
|
||||
// component: 'Select',
|
||||
// componentProps: {
|
||||
// options: [
|
||||
// {
|
||||
// disabled: true,
|
||||
// label: 'option1',
|
||||
// value: '1'
|
||||
// },
|
||||
// {
|
||||
// label: 'option2',
|
||||
// value: '2'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'field15',
|
||||
// label: t('formDemo.slot'),
|
||||
// component: 'Select',
|
||||
// componentProps: {
|
||||
// options: [
|
||||
// {
|
||||
// label: 'option1',
|
||||
// value: '1'
|
||||
// },
|
||||
// {
|
||||
// label: 'option2',
|
||||
// value: '2'
|
||||
// }
|
||||
// ],
|
||||
// optionsSlot: true
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'field16',
|
||||
// label: t('formDemo.selectGroup'),
|
||||
// component: 'Select',
|
||||
|
|
Loading…
Reference in New Issue