<!--
|
* @Description:
|
-->
|
<template>
|
<Col v-bind="colPropsComputed">
|
<FormItem v-bind="{ ...formItemProps }">
|
<template #label v-if="!formItemProps.hiddenLabel && schema.component !== 'Divider'">
|
<Tooltip>
|
<span>{{ schema.label }}</span>
|
<template #title v-if="schema.helpMessage"
|
><span>{{ schema.helpMessage }}</span></template
|
>
|
<Icon v-if="schema.helpMessage" class="ml-5" icon="ant-design:question-circle-outlined" />
|
</Tooltip>
|
</template>
|
|
<slot
|
v-if="schema.componentProps && schema.componentProps?.slotName"
|
:name="schema.componentProps.slotName"
|
v-bind="schema"
|
></slot>
|
<Divider
|
v-else-if="schema.component == 'Divider' && schema.label && !formItemProps.hiddenLabel"
|
>{{ schema.label }}</Divider
|
>
|
<!-- 部分控件需要一个空div -->
|
<div
|
><component
|
class="v-form-item-wrapper"
|
:is="componentItem"
|
v-bind="{ ...cmpProps, ...asyncProps }"
|
:schema="schema"
|
:style="schema.width ? { width: schema.width } : {}"
|
@change="handleChange"
|
@click="handleClick(schema)"
|
/></div>
|
|
<span v-if="['Button'].includes(schema.component)">{{ schema.label }}</span>
|
</FormItem>
|
</Col>
|
</template>
|
<script lang="ts">
|
import { type Recordable } from '@vben/types';
|
import { defineComponent, reactive, toRefs, computed, PropType, unref } from 'vue';
|
import { componentMap } from '../../core/formItemConfig';
|
import { IVFormComponent, IFormConfig } from '../../typings/v-form-component';
|
import { asyncComputed } from '@vueuse/core';
|
import { handleAsyncOptions } from '../../utils';
|
import { omit } from 'lodash-es';
|
import { Tooltip, FormItem, Divider, Col } from 'ant-design-vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
import { useFormModelState } from '../../hooks/useFormDesignState';
|
|
export default defineComponent({
|
name: 'VFormItem',
|
components: {
|
Tooltip,
|
Icon,
|
FormItem,
|
Divider,
|
Col,
|
},
|
|
props: {
|
formData: {
|
type: Object,
|
default: () => ({}),
|
},
|
schema: {
|
type: Object as PropType<IVFormComponent>,
|
required: true,
|
},
|
formConfig: {
|
type: Object as PropType<IFormConfig>,
|
required: true,
|
},
|
},
|
emits: ['update:form-data', 'change'],
|
setup(props, { emit }) {
|
const state = reactive({
|
componentMap,
|
});
|
|
const { formModel: formData1, setFormModel } = useFormModelState();
|
const colPropsComputed = computed(() => {
|
const { colProps = {} } = props.schema;
|
return colProps;
|
});
|
const formItemProps = computed(() => {
|
const { formConfig } = unref(props);
|
let { field, required, rules, labelCol, wrapperCol } = unref(props.schema);
|
const { colon } = props.formConfig;
|
|
const { itemProps } = unref(props.schema);
|
|
//<editor-fold desc="布局属性">
|
labelCol = labelCol
|
? labelCol
|
: formConfig.layout === 'horizontal'
|
? formConfig.labelLayout === 'flex'
|
? { style: `width:${formConfig.labelWidth}px` }
|
: formConfig.labelCol
|
: {};
|
|
wrapperCol = wrapperCol
|
? wrapperCol
|
: formConfig.layout === 'horizontal'
|
? formConfig.labelLayout === 'flex'
|
? { style: 'width:auto;flex:1' }
|
: formConfig.wrapperCol
|
: {};
|
|
const style =
|
formConfig.layout === 'horizontal' && formConfig.labelLayout === 'flex'
|
? { display: 'flex' }
|
: {};
|
|
/**
|
* 将字符串正则格式化成正则表达式
|
*/
|
|
const newConfig = Object.assign(
|
{},
|
{
|
name: field,
|
style: { ...style },
|
colon,
|
required,
|
rules,
|
labelCol,
|
wrapperCol,
|
},
|
itemProps,
|
);
|
if (!itemProps?.labelCol?.span) {
|
newConfig.labelCol = labelCol;
|
}
|
if (!itemProps?.wrapperCol?.span) {
|
newConfig.wrapperCol = wrapperCol;
|
}
|
if (!itemProps?.rules) {
|
newConfig.rules = rules;
|
}
|
return newConfig;
|
}) as Recordable<any>;
|
|
const componentItem = computed(() => componentMap.get(props.schema.component as string));
|
|
// console.log('component change:', props.schema.component, componentItem.value);
|
const handleClick = (schema: IVFormComponent) => {
|
if (schema.component === 'Button' && schema.componentProps?.handle)
|
emit(schema.componentProps?.handle);
|
};
|
/**
|
* 处理异步属性,异步属性会导致一些属性渲染错误,如defaultValue异步加载会导致渲染不出来,故而此处只处理options,treeData,同步属性在cmpProps中处理
|
*/
|
const asyncProps = asyncComputed(async () => {
|
let { options, treeData } = props.schema.componentProps ?? {};
|
if (options) options = await handleAsyncOptions(options);
|
if (treeData) treeData = await handleAsyncOptions(treeData);
|
return {
|
options,
|
treeData,
|
};
|
});
|
|
/**
|
* 处理同步属性
|
*/
|
const cmpProps = computed(() => {
|
const isCheck =
|
props.schema && ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component);
|
let { field } = props.schema;
|
|
let { disabled, ...attrs } =
|
omit(props.schema.componentProps, ['options', 'treeData']) ?? {};
|
|
disabled = props.formConfig.disabled || disabled;
|
|
return {
|
...attrs,
|
disabled,
|
[isCheck ? 'checked' : 'value']: formData1.value[field!],
|
};
|
});
|
|
const handleChange = function (e) {
|
const isCheck = ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component);
|
const target = e ? e.target : null;
|
const value = target ? (isCheck ? target.checked : target.value) : e;
|
setFormModel(props.schema.field!, value);
|
emit('change', value);
|
};
|
return {
|
...toRefs(state),
|
componentItem,
|
formItemProps,
|
handleClick,
|
asyncProps,
|
cmpProps,
|
handleChange,
|
colPropsComputed,
|
};
|
},
|
});
|
</script>
|
|
<style lang="less" scoped>
|
.ml-5 {
|
margin-left: 5px;
|
}
|
|
// form字段中的标签有ant-col,不能使用width:100%
|
:deep(.ant-col) {
|
width: auto;
|
}
|
|
.ant-form-item:not(.ant-form-item-with-help) {
|
margin-bottom: 20px;
|
}
|
|
// .w-full {
|
// width: 100% !important;
|
// }
|
</style>
|