wip(VForm): VForm component development

This commit is contained in:
陈凯龙 2021-12-16 17:15:03 +08:00
parent d71bc5d6f5
commit 69909e2832
12 changed files with 309 additions and 40 deletions

View File

@ -1,27 +1,58 @@
<script setup lang="ts">
import { reactive } from 'vue'
import { reactive, ref, onMounted } from 'vue'
import { ElConfigProvider, ElIcon } from 'element-plus'
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
// import en from 'element-plus/lib/locale/lang/en'
import { VFrom } from '@/components/Form'
import Calendar from '~icons/ep/calendar'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const restaurants = ref<Recordable[]>([])
const querySearch = (queryString: string, cb: Fn) => {
const results = queryString
? restaurants.value.filter(createFilter(queryString))
: restaurants.value
// call callback function to return suggestions
cb(results)
}
const createFilter = (queryString: string) => {
return (restaurant: Recordable) => {
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
}
}
const loadAll = () => {
return [
{ value: 'vue', link: 'https://github.com/vuejs/vue' },
{ value: 'element', link: 'https://github.com/ElemeFE/element' },
{ value: 'cooking', link: 'https://github.com/ElemeFE/cooking' },
{ value: 'mint-ui', link: 'https://github.com/ElemeFE/mint-ui' },
{ value: 'vuex', link: 'https://github.com/vuejs/vuex' },
{ value: 'vue-router', link: 'https://github.com/vuejs/vue-router' },
{ value: 'babel', link: 'https://github.com/babel/babel' }
]
}
const handleSelect = (item: Recordable) => {
console.log(item)
}
onMounted(() => {
restaurants.value = loadAll()
})
const schema = reactive<VFormSchema[]>([
{
field: 'field1',
component: 'Divider',
componentProps: {
text: 'Input'
}
label: t('formDemo.input'),
component: 'Divider'
},
{
field: 'field2',
label: 'default',
label: t('formDemo.default'),
component: 'Input'
},
{
field: 'field3',
label: 'input-icon1',
label: `${t('formDemo.icon')}1`,
component: 'Input',
componentProps: {
suffixIcon: Calendar,
@ -30,7 +61,7 @@ const schema = reactive<VFormSchema[]>([
},
{
field: 'field4',
label: 'input-icon2',
label: `${t('formDemo.icon')}2`,
component: 'Input',
componentProps: {
slots: {
@ -41,7 +72,7 @@ const schema = reactive<VFormSchema[]>([
},
{
field: 'field5',
label: 'input-mixed',
label: t('formDemo.mixed'),
component: 'Input',
componentProps: {
slots: {
@ -52,7 +83,7 @@ const schema = reactive<VFormSchema[]>([
},
{
field: 'field6',
label: 'textarea',
label: t('formDemo.textarea'),
component: 'Input',
componentProps: {
type: 'textarea',
@ -61,10 +92,152 @@ const schema = reactive<VFormSchema[]>([
},
{
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',
componentProps: {
text: 'Autocomplete'
text: t('formDemo.inputNumber')
}
},
{
field: 'field11',
label: t('formDemo.default'),
component: 'InputNumber'
},
{
field: 'field11',
label: t('formDemo.position'),
component: 'InputNumber',
componentProps: {
controlsPosition: 'right'
}
},
{
field: 'field12',
label: t('formDemo.select'),
component: 'Divider'
},
{
field: 'field13',
label: t('formDemo.default'),
component: 'Select',
options: [
{
label: '选项1',
value: '1'
},
{
label: '选项2',
value: '2'
}
]
},
{
field: 'field14',
label: t('formDemo.slot'),
component: 'Select',
options: [
{
label: '选项1',
value: '1'
},
{
label: '选项2',
value: '2'
}
],
optionsSlot: true
},
{
field: 'field15',
label: t('formDemo.group'),
component: 'Select',
options: [
{
label: '选项1',
options: [
{
label: '选项1-1',
value: '1-1'
},
{
label: '选项1-2',
value: '1-2'
}
]
},
{
label: '选项2',
options: [
{
label: '选项2-1',
value: '2-1'
},
{
label: '选项2-2',
value: '2-2'
}
]
}
]
},
{
field: 'field16',
label: `${t('formDemo.group')}${t('formDemo.slot')}`,
component: 'Select',
options: [
{
label: '选项1',
options: [
{
label: '选项1-1',
value: '1-1'
},
{
label: '选项1-2',
value: '1-2'
}
]
},
{
label: '选项2',
options: [
{
label: '选项2-1',
value: '2-1'
},
{
label: '选项2-2',
value: '2-2'
}
]
}
],
optionsSlot: true
}
])
</script>
@ -81,6 +254,25 @@ const schema = reactive<VFormSchema[]>([
<template #field5-prepend> Http:// </template>
<template #field5-append> .com </template>
<template #field9-default="{ item }">
<div class="value">{{ item.value }}</div>
<span class="link">{{ item.link }}</span>
</template>
<template #field14-option="item">
<span style="float: left">{{ item.label }}</span>
<span style="float: right; font-size: 13px; color: var(--el-text-color-secondary)">{{
item.value
}}</span>
</template>
<template #field16-option="item">
<span style="float: left">{{ item.label }}</span>
<span style="float: right; font-size: 13px; color: var(--el-text-color-secondary)">{{
item.value
}}</span>
</template>
</VFrom>
</ElConfigProvider>
</template>

View File

@ -1,7 +1,7 @@
<script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol } from 'element-plus'
import { COMPONENT_MAP } from './componentMap'
import { PropType, defineComponent, ref, computed, unref } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol, ElOption, ElOptionGroup } from 'element-plus'
import { componentMap } from './componentMap'
import { propTypes } from '@/utils/propTypes'
import { getSlot } from '@/utils/tsxHelper'
import { setTextPlaceholder, setGridProp, setComponentProps, setItemComponentSlots } from './helper'
@ -33,14 +33,6 @@ export default defineComponent({
const formRef = ref<ComponentRef<typeof ElForm>>()
const getProps = computed(() => props)
const { schema, isCol, isCustom, autoSetPlaceholder } = unref(getProps)
const test = ref('')
watch(
() => test.value,
(val) => {
console.log(val)
}
)
// 使
function renderWrap() {
@ -60,11 +52,9 @@ export default defineComponent({
.map((item) => {
// Divider
const isDivider = item.component === 'Divider'
const Com = COMPONENT_MAP['Divider'] as ReturnType<typeof defineComponent>
const Com = componentMap['Divider'] as ReturnType<typeof defineComponent>
return isDivider ? (
<Com {...{ contentPosition: 'left', ...item.componentProps }}>
{item?.componentProps?.text}
</Com>
<Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
) : isCol ? (
// ElCol
<ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
@ -79,17 +69,14 @@ export default defineComponent({
return (
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label}>
{() => {
const Com = COMPONENT_MAP[item.component as string] as ReturnType<
typeof defineComponent
>
const Com = componentMap[item.component as string] as ReturnType<typeof defineComponent>
return (
<Com
vModel={test.value}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item.componentProps)}
>
{{
default: () => (item.options ? renderOptions() : null),
default: () => (item.options ? renderOptions(item) : null),
...setItemComponentSlots(slots, item?.componentProps?.slots, item.field)
}}
</Com>
@ -100,9 +87,53 @@ export default defineComponent({
}
// options
function renderOptions() {
// const optionsMap = ['Radio', 'Checkbox', 'Select']
return <div>2222</div>
function renderOptions(item: VFormSchema) {
switch (item.component) {
case 'Select':
return renderSelectOptions(item)
default:
break
}
}
// select options
function renderSelectOptions(item: VFormSchema) {
//
const labelAlias = item.optionsField?.labelField
return item.options?.map((option) => {
if (option?.options?.length) {
return (
<ElOptionGroup label={labelAlias ? option[labelAlias] : option['label']}>
{() => {
return option?.options?.map((v) => {
return renderSelectOptionItem(item, v)
})
}}
</ElOptionGroup>
)
} else {
return renderSelectOptionItem(item, option)
}
})
}
// select option item
function renderSelectOptionItem(item: VFormSchema, option: FormOptions) {
//
const labelAlias = item.optionsField?.labelField
const valueAlias = item.optionsField?.valueField
return (
<ElOption
label={labelAlias ? option[labelAlias] : option['label']}
value={valueAlias ? option[valueAlias] : option['value']}
>
{{
default: () =>
// option {field}-option
item.optionsSlot ? getSlot(slots, `${item.field}-option`, option) : null
}}
</ElOption>
)
}
// Form
@ -129,5 +160,3 @@ export default defineComponent({
}
})
</script>
<style lang="less" scoped></style>

View File

@ -19,7 +19,7 @@ import {
ElDivider
} from 'element-plus'
const COMPONENT_MAP: Recordable<Component, ComponentName> = {
const componentMap: Recordable<Component, ComponentName> = {
Radio: ElRadioGroup,
Checkbox: ElCheckboxGroup,
Input: ElInput,
@ -39,4 +39,4 @@ const COMPONENT_MAP: Recordable<Component, ComponentName> = {
SelectV2: ElSelectV2
}
export { COMPONENT_MAP }
export { componentMap }

View File

@ -110,8 +110,8 @@ export function setItemComponentSlots(
for (const key in slotsProps) {
if (slotsProps[key]) {
// 由于组件有可能重复,需要有一个唯一的前缀
slotObj[key] = () => {
return getSlot(slots, `${field}-${key}`)
slotObj[key] = (data: Recordable) => {
return getSlot(slots, `${field}-${key}`, data)
}
}
}

View File

@ -4,5 +4,18 @@ export default {
selectText: 'Please select',
startTimeText: 'Start time',
endTimeText: 'End time'
},
formDemo: {
input: 'Input',
inputNumber: 'InputNumber',
default: 'Default',
icon: 'Icon',
mixed: 'Mixed',
textarea: 'Textarea',
slot: 'Slot',
position: 'Position',
autocomplete: 'Autocomplete',
select: 'Select',
group: 'Select Group'
}
}

View File

@ -4,5 +4,18 @@ export default {
selectText: '请选择',
startTimeText: '开始时间',
endTimeText: '结束时间'
},
formDemo: {
input: '输入框',
inputNumber: '数字输入框',
default: '默认',
icon: '图标',
mixed: '复合型',
textarea: '多行文本',
slot: '插槽',
position: '位置',
autocomplete: '自动补全',
select: '选择器',
group: '选项分组'
}
}

View File

@ -5,6 +5,10 @@ import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 引入element-plus
import { setupElementPlus } from '@/plugins/elementPlus'
setupElementPlus(app)
// 引入状态管理
import { setupStore } from '@/store'
setupStore(app)

View File

@ -0,0 +1,16 @@
import type { App } from 'vue'
// 需要全局引入一些组件如ElScrollbar不然一些下拉项样式有问题
import { ElLoading, ElScrollbar } from 'element-plus'
const plugins = [ElLoading]
const components = [ElScrollbar]
export function setupElementPlus(app: App) {
plugins.forEach((plugin) => {
app.use(plugin)
})
components.forEach((component) => {
app.component(component.name, component)
})
}

View File

@ -58,6 +58,7 @@ declare global {
disabled?: boolean
key?: string | number
children?: FormOptions[]
options?: FormOptions[]
[key: string]: any
}
@ -76,6 +77,7 @@ declare global {
value?: FormValueTypes
options?: FormOptions[]
optionsField?: FormOptionsAlias
optionsSlot?: boolean
hidden?: boolean
}