mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +08:00
292 lines
7.9 KiB
Vue
292 lines
7.9 KiB
Vue
<template>
|
||
<div
|
||
v-if="config"
|
||
:style="config.tip ? 'display: flex;align-items: baseline;' : ''"
|
||
:class="config.className"
|
||
class="m-form-container"
|
||
>
|
||
<m-fields-hidden
|
||
v-if="type === 'hidden'"
|
||
:model="model"
|
||
:config="config"
|
||
:name="config.name"
|
||
:disabled="disabled"
|
||
:prop="itemProp"
|
||
></m-fields-hidden>
|
||
|
||
<component
|
||
v-else-if="items && !config.text && type && display"
|
||
:key="key(config)"
|
||
:size="size"
|
||
:is="tagName"
|
||
:model="model"
|
||
:config="config"
|
||
:name="name"
|
||
:prop="itemProp"
|
||
:step-active="stepActive"
|
||
:expand-more="expand"
|
||
:label-width="itemLabelWidth"
|
||
@change="onChangeHandler"
|
||
></component>
|
||
|
||
<template v-else-if="type && display">
|
||
<el-form-item
|
||
:style="config.tip ? 'flex: 1' : ''"
|
||
:class="{ hidden: `${itemLabelWidth}` === '0' || !config.text }"
|
||
:prop="itemProp"
|
||
:label-width="itemLabelWidth"
|
||
:rules="rule"
|
||
>
|
||
<template #label><span v-html="type === 'checkbox' ? '' : config.text"></span></template>
|
||
<el-tooltip v-if="tooltip">
|
||
<component
|
||
:key="key(config)"
|
||
:size="size"
|
||
:is="tagName"
|
||
:model="model"
|
||
:config="config"
|
||
:name="name"
|
||
:disabled="disabled"
|
||
:prop="itemProp"
|
||
@change="onChangeHandler"
|
||
></component>
|
||
<template #content>
|
||
<div v-html="tooltip"></div>
|
||
</template>
|
||
</el-tooltip>
|
||
|
||
<component
|
||
v-else
|
||
:key="key(config)"
|
||
:size="size"
|
||
:is="tagName"
|
||
:model="model"
|
||
:config="config"
|
||
:name="name"
|
||
:disabled="disabled"
|
||
:prop="itemProp"
|
||
@change="onChangeHandler"
|
||
></component>
|
||
|
||
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
|
||
</el-form-item>
|
||
|
||
<el-tooltip v-if="config.tip" placement="left">
|
||
<el-icon style="line-height: 40px; margin-left: 5px"><warning-filled /></el-icon>
|
||
<template #content>
|
||
<div v-html="config.tip"></div>
|
||
</template>
|
||
</el-tooltip>
|
||
</template>
|
||
|
||
<template v-else-if="items && display">
|
||
<template v-if="name || name === 0 ? model[name] : model">
|
||
<m-form-container
|
||
v-for="item in items"
|
||
:key="key(item)"
|
||
:model="name || name === 0 ? model[name] : model"
|
||
:config="item"
|
||
:size="size"
|
||
:step-active="stepActive"
|
||
:expand-more="expand"
|
||
:label-width="itemLabelWidth"
|
||
:prop="itemProp"
|
||
@change="onChangeHandler"
|
||
></m-form-container>
|
||
</template>
|
||
</template>
|
||
|
||
<div style="text-align: center" v-if="config.expand && type !== 'fieldset'">
|
||
<el-button text @click="expandHandler">{{ expand ? '收起配置' : '展开更多配置' }}</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { computed, defineComponent, inject, PropType, ref, resolveComponent, watchEffect } from 'vue';
|
||
import { WarningFilled } from '@element-plus/icons';
|
||
|
||
import { ChildConfig, ContainerCommonConfig, FormState, FormValue } from '../schema';
|
||
import { display as displayFunction, filterFunction, getRules } from '../utils/form';
|
||
|
||
export default defineComponent({
|
||
name: 'm-form-container',
|
||
|
||
components: { WarningFilled },
|
||
|
||
props: {
|
||
labelWidth: String,
|
||
expandMore: Boolean,
|
||
|
||
model: {
|
||
type: [Object, Array] as PropType<FormValue>,
|
||
required: true,
|
||
},
|
||
|
||
config: {
|
||
type: Object as PropType<ChildConfig>,
|
||
required: true,
|
||
},
|
||
|
||
prop: {
|
||
type: String,
|
||
default: () => '',
|
||
},
|
||
|
||
stepActive: {
|
||
type: [String, Number],
|
||
},
|
||
|
||
size: {
|
||
type: String,
|
||
default: 'small',
|
||
},
|
||
},
|
||
|
||
emits: ['change'],
|
||
|
||
setup(props, { emit }) {
|
||
const mForm = inject<FormState | undefined>('mForm');
|
||
|
||
const expand = ref(false);
|
||
|
||
const name = computed(() => props.config.name || '');
|
||
|
||
const items = computed(() => (props.config as ContainerCommonConfig).items);
|
||
|
||
const itemProp = computed(() => {
|
||
let n: string | number = '';
|
||
const { names } = props.config as any;
|
||
if (names?.[0]) {
|
||
[n] = names;
|
||
} else if (name.value) {
|
||
n = name.value;
|
||
} else {
|
||
return props.prop;
|
||
}
|
||
return `${props.prop}${props.prop ? '.' : ''}${n}`;
|
||
});
|
||
|
||
const tagName = computed(() => {
|
||
const component = resolveComponent(`m-${items.value ? 'form' : 'fields'}-${type.value}`);
|
||
if (typeof component !== 'string') return component;
|
||
return 'm-fields-text';
|
||
});
|
||
|
||
const disabled = computed(() => filterFunction(mForm, props.config.disabled, props));
|
||
|
||
const tooltip = computed(() => filterFunction(mForm, props.config.tooltip, props));
|
||
|
||
const extra = computed(() => filterFunction(mForm, props.config.extra, props));
|
||
|
||
const rule = computed(() => getRules(mForm, props.config.rules, props));
|
||
|
||
const type = computed((): string => {
|
||
let { type } = props.config;
|
||
if (typeof type === 'function') {
|
||
type = type(mForm, {
|
||
model: props.model,
|
||
});
|
||
}
|
||
if (type === 'form') return '';
|
||
return type?.replace(/([A-Z])/g, '-$1').toLowerCase() || (items.value ? '' : 'text');
|
||
});
|
||
|
||
const display = computed((): boolean => {
|
||
if (props.config.display === 'expand') {
|
||
return expand.value;
|
||
}
|
||
|
||
return displayFunction(mForm, props.config.display, props);
|
||
});
|
||
|
||
const itemLabelWidth = computed(() => props.config.labelWidth || props.labelWidth);
|
||
|
||
watchEffect(() => {
|
||
expand.value = props.expandMore;
|
||
});
|
||
|
||
const expandHandler = () => (expand.value = !expand.value);
|
||
|
||
const key = (config: any) => config[mForm?.keyProps];
|
||
|
||
const filterHandler = (filter: any, value: FormValue | number | string) => {
|
||
if (typeof filter === 'function') {
|
||
return filter(mForm, value, {
|
||
model: props.model,
|
||
values: mForm?.initValues,
|
||
formValue: mForm?.values,
|
||
prop: itemProp.value,
|
||
});
|
||
}
|
||
|
||
if (filter === 'number') {
|
||
return +value;
|
||
}
|
||
|
||
return value;
|
||
};
|
||
|
||
const changeHandler = (onChange: any, value: FormValue | number | string) => {
|
||
if (typeof onChange === 'function') {
|
||
return onChange(mForm, value, {
|
||
model: props.model,
|
||
values: mForm?.initValues,
|
||
formValue: mForm?.values,
|
||
prop: itemProp.value,
|
||
});
|
||
}
|
||
};
|
||
|
||
const trimHandler = (trim: any, value: FormValue | number | string) => {
|
||
if (typeof value === 'string' && trim) {
|
||
return value.replace(/^\s*/, '').replace(/\s*$/, '');
|
||
}
|
||
};
|
||
|
||
const onChangeHandler = async function (v: FormValue, key?: string) {
|
||
const { filter, onChange, trim, name, dynamicKey } = props.config as any;
|
||
let value: FormValue | number | string = v;
|
||
|
||
try {
|
||
value = filterHandler(filter, v);
|
||
value = (await changeHandler(onChange, value)) ?? value;
|
||
value = trimHandler(trim, value) ?? value;
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
|
||
// field内容下包含field-link时,model===value, 这里避免循环引用
|
||
if ((name || name === 0) && props.model !== value && (v !== value || props.model[name] !== value)) {
|
||
// eslint-disable-next-line vue/no-mutating-props
|
||
props.model[name] = value;
|
||
}
|
||
// 动态表单类型,根据value和key参数,直接修改model
|
||
if (key !== undefined && dynamicKey) {
|
||
// eslint-disable-next-line vue/no-mutating-props
|
||
props.model[key] = value;
|
||
}
|
||
emit('change', props.model);
|
||
};
|
||
|
||
return {
|
||
expand,
|
||
name,
|
||
type,
|
||
disabled,
|
||
itemProp,
|
||
items,
|
||
display,
|
||
itemLabelWidth,
|
||
tagName,
|
||
rule,
|
||
tooltip,
|
||
extra,
|
||
key,
|
||
onChangeHandler,
|
||
expandHandler,
|
||
};
|
||
},
|
||
});
|
||
</script>
|