iot-ui-vue/src/components/Form/FormBuilder.vue

254 lines
6.8 KiB
Vue

<template>
<div class='JForm-content'>
<a-form
ref='form'
v-bind='props'
:model='formData.data'
layout='vertical'
>
<a-row :type='rowType'>
<a-col v-for='item in formOptions.data' :key='item.key' :span='item.span'>
<a-form-item
:name='item.name'
:required='item.required'
:rules='item.rules'
:noStyle='item.noStyle'
>
<template #label>
<span>{{ item.title }}</span>
<a-tooltip :title='item.tooltip'>
<QuestionCircleOutlined v-if='!!item.tooltip' style='margin-left: 4px; color: rgba(0,0,0,.45) ' />
</a-tooltip>
</template>
<a-input
v-if='item.component === componentType.input'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
<a-select
v-else-if='item.component === componentType.select'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
:options='item.options'
/>
<a-inputnumber
v-else-if='item.component === componentType.inputNumber'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
<a-input-password
v-else-if='item.component === componentType.password'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
<a-switch
v-else-if='item.component === componentType.switch'
v-bind='item.componentProps'
v-model:checked='formData.data[item.name]'
/>
<a-radio-group
v-else-if='item.component === componentType.radio'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
<a-checkbox-group
v-else-if='item.component === componentType.checkbox'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
:options='item.options'
/>
<a-time-picker
v-else-if='item.component === componentType.time'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
<a-date-picker
v-else-if='item.component === componentType.date'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
<a-tree-select
v-else-if='item.component === componentType.tree'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
:tree-data='item.options'
/>
<a-upload
v-else-if='item.component === componentType.upload'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
<component
v-else
:is='item.component'
v-bind='item.componentProps'
v-model:value='formData.data[item.name]'
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
</template>
<script setup lang='ts' name='FormBuilder'>
import type { Options, OptionsItem, OptionsComponent } from './index.modules'
import { PropType } from 'vue'
import { get, isArray, isString, pick, set } from 'lodash-es'
import { formProps } from 'ant-design-vue/es/form'
import componentType from './util'
import {
QuestionCircleOutlined
} from '@ant-design/icons-vue';
const form = ref()
const props = defineProps({
...formProps,
options: {
type: Object as PropType<Options>,
default: () => []
},
initValue: {
type: Object,
default: () => ({})
},
column: {
type: Number,
default: 1
}
})
// 表单数据
const formData = reactive({
data: {}
})
const formOptions = reactive<{ data: OptionsComponent[]}>({
data: []
}) // 表单Item
const rowType = ref<string | undefined>(undefined)
const calculateItemSpan = (span?: number | string) => {
const itemSpan = 24 / props.column
if (!span) return itemSpan
if (isString(span) && span.includes('px')) {
rowType.value = 'flex'
} else {
return span
}
}
/**
* 根据传入的表单options生成表单
* @param data {Options}
* @param parentKey
*/
const handleFormData = (data: Options, parentKey: Array<string> = []): any => {
const cacheModel: any = {}
Object.keys(data).forEach(async (key) => {
const optionItem = data[key]
const _key = [...parentKey, key]
if ('type' in optionItem && optionItem.type === 'Object') {
const dataModel = handleFormData(optionItem.properties, _key)
cacheModel[key] = dataModel
} else if (!('visible' in optionItem) || ('visible' in optionItem && optionItem.visible !== true)){
// 处理默认值以及原始值
const keyValue = get(formData.data, _key)
let _options: any[] = []
if (keyValue) { // 当前值在formModel中
cacheModel[key] = keyValue
} else {
cacheModel[key] = (optionItem as OptionsItem).default
}
// 处理options
if ('options' in optionItem) {
_options = optionItem.options!
}
// 处理 onSearch 请求
if ('onSearch' in optionItem) {
const data = await optionItem.onSearch!()
if (data) {
_options = data
}
}
const optionsItemProps = pick(optionItem, ['componentProps', 'title', 'component', 'rules', 'required', 'hidden', 'tooltip', 'noStyle'])
//
formOptions.data.push({
...optionsItemProps,
name: _key,
options: _options,
key: isArray(_key) ? _key.toString() : _key,
span: calculateItemSpan((optionItem as OptionsItem).span)
})
}
})
return cacheModel
}
/**
* 重置表单
*/
const resetModel = () => {
form.value.resetFields()
}
/**
* 验证并提交表单
*/
const formValidate = () => {
return new Promise((res, rej) => {
form.value.validate().then(() => {
res(formData.data)
}).catch((err: any) => {
rej(err)
})
})
}
/**
* 改变单个值
*/
const setItemValue = (key: string | (string | number)[], value: any) => {
set(formData.data, key, value)
}
/**
* 修改整个表单值
* @param data
*/
const setData = (data: any) => {
formData.data = data
}
if (props.initValue) {
formData.data = props.initValue
}
formData.data = handleFormData(props.options)
watch(props.options, (newValue: any) => {
formOptions.data = []
formData.data = handleFormData(newValue)
})
watch(props.initValue, (newValue: any) => {
formData.data = newValue
})
defineExpose({
reset: resetModel,
formValidate,
setItemValue,
setData
})
</script>
<style scoped>
</style>