文章目录
- 前言
- 一、配置化的前提
- 二、配置的相关组件
- 1、新建form.vue组件
- 2、新建input.vue组件
- 3、新建select.vue组件
- 4、新建v-html.vue组件
- 5、新建upload.vue组件
- 6、新建switch.vue组件
- 7、新建radio.vue组件
- 8、新建checkbox.vue组件
- 9、新建date.vue组件
- 10、新建time-picker.vue组件
- 11、新建cascader.vue组件
- 12、新建/ueditor/组件
- 13、新建/kind-editor组件
- 二、配置的表单及使用
前言
网站基本离不开表单提交,结合之前保险项目及各种检验平台,都是需要填写大量数据,均涉及多个不同表单提交。
所以表单提交配置化是很有必要的 – 基于面向对象
本文以vue3+element-plus
为例,由于时间有限,部分配置可结合项目实际扩展
先展示实际效果图
一、配置化的前提
表单内一般包含
- 需要什么字段
- 字段填写形式(输入框、下拉框、多级联动、开关、日期、附件上传、富文本编辑器等等)
- 字段校验(是否必填、存在特殊校验,例如邮箱格式等)
- 字段排序及各种属性配置
- …
二、配置的相关组件
以下文件均建立在 @/componentsform-configuration
目录下
1、新建form.vue组件
用于接收配置信息
<template>
<el-form
class="customForm"
:model="fields"
:rules="rules"
ref="formData"
:size="size"
:disabled="disabled"
:validate-on-rule-change="validateOnRuleChange"
:hide-required-asterisk="hideAsterisk"
:label-position="labelPosition"
:label-width="labelWidth">
<el-row :gutter="gutter">
<el-col v-for="(item, index) in formData" v-show="item.show" :key="item.field + '-' + index" :span="item.colSize">
<el-form-item :prop="item.field" :label="item.title" :class="item.class">
<component v-model:content="fields[item.field]" v-model="fields[item.field]" :property="{...item.property, name: item.field}" :is="item.type" @fieldChange="(val) => change(val, item.field)" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import Input from '@/components/form-configuration/input.vue'
import Select from '@/components/form-configuration/select.vue'
import Vhtml from '@/components/form-configuration/v-html.vue'
import Upload from '@/components/form-configuration/upload.vue'
import Switch from '@/components/form-configuration/switch.vue'
import Radio from '@/components/form-configuration/radio.vue'
import Checkbox from '@/components/form-configuration/checkbox.vue'
import Date from '@/components/form-configuration/date.vue'
import TimePicker from '@/components/form-configuration/time-picker.vue'
import Cascader from '@/components/form-configuration/cascader.vue'
import UEditor from '@/components/form-configuration/ueditor/index.vue'
import KindEditor from '@/components/form-configuration/kind-editor/index.vue'
import { defineComponent, reactive, ref, watch, computed, nextTick } from 'vue'
export default {
components: {
Input,
Select,
Vhtml,
Upload,
Switch,
Radio,
Checkbox,
Date,
TimePicker,
Cascader,
},
props: {
disabled: {
type: Boolean,
default: false //
},
size: {
type: String,
default: 'default' // "", "default", "small", "large"
},
gutter: {
type: Number,
default: 20
},
formData: {
type: Object,
default() {
return {}
}
},
hideAsterisk: {
type: Boolean,
default: false
},
validateOnRuleChange: {
type: Boolean,
default: false
},
fields: {}, // form相关字段值
rules: {}, // form校验
labelWidth: {
type: String,
default: '180px'
},
labelPosition: {
type: String,
default: 'right', // left/right/top
}
},
setup(props, { emit }) {
const change = (val, field) => {
emit('change', {
val, field
}) // 触发
}
return {
change
}
}
}
</script>
<style lang="less" scoped>
</style>
2、新建input.vue组件
<template>
<el-input v-model="input"
:type="fieldProperty.type"
:clearable="fieldProperty.clearable"
:placeholder="fieldProperty.placeholder"
:maxlength="fieldProperty.maxlength"
:readonly="fieldProperty.readonly"
@blur="blur"
@change="change"
autocomplete="on"
:name="fieldProperty.name"
:disabled="fieldProperty.disabled" />
</template>
<script lang="ts">
import { computed } from 'vue'
export default {
props: {
modelValue: [String, Number], // 组件绑定值
content: [String, Number], // 组件绑定值
property: {
type: Object,
default() {
return {
}
}
}
},
setup(props, { emit }) {
const fieldProperty = computed(() => {
return {
placeholder: '请输入', // 提示语
maxlength: -1, // 可输入最大长度
readonly: false, // 是否只读
disabled: false, // 是否可输入
clearable: false, // 是否可清空输入框
type: 'text', // 输入框类型 input | textarea | password | text
...props.property
}
})
const input = computed({
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val)
}
})
const change = (val: any) => {
emit('fieldChange', val) // 触发
}
const blur = () => {
emit('fieldBlur', input.value) // 触发
}
return {
input,
change,
blur,
fieldProperty
}
},
}
</script>
<style lang="less" scoped></style>
3、新建select.vue组件
<template>
<el-select v-model="val"
ref="selectRef"
:multiple="fieldProperty.multiple"
:filterable="fieldProperty.filterable"
:clearable="fieldProperty.clearable"
:disabled="fieldProperty.disabled"
@change="change"
:placeholder="fieldProperty.placeholder">
<el-option v-for="(item, index) in fieldProperty.data"
:key="item[fieldProperty.value] + item[fieldProperty.label] + '-' + index"
:label="item[fieldProperty.label]" :value="item[fieldProperty.value]">
<!-- {{ item[fieldProperty.label] }} -->
</el-option>
</el-select>
</template>
<script lang="ts">
import { computed } from 'vue'
export default {
props: {
modelValue : [String, Number, Array],
property: {
type: Object,
default() {
return {
}
}
}
},
setup(props, { emit }) {
const fieldProperty = computed(() => {
return {
placeholder: '请选择', // 提示语
data: [], // 下拉可选数据
label: 'label', // 选择框的文案字段
value: 'value', // 选择框的值字段
multiple: false, // 是否多选
filterable: true, // 是否可搜索
clearable: false, // 是否可以清空选项
disabled: false, // 是否禁用
...props.property
}
})
const val = computed({
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val) // 触发
}
})
const change = (val: any) => {
emit('fieldChange', val) // 触发
}
return {
val,
fieldProperty,
change
}
}
}
</script>
<style lang="less" scoped>
</style>
4、新建v-html.vue组件
<template>
<div v-html="val" class="pre-line" style="line-height: 26px"></div>
</template>
<script>
export default {
props: {
modelValue: [String, Number]
},
model: {
prop: 'modelValue', // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
event: 'change' // 指定要触发的事件名字,将被用于 $emit
},
data() {
return {
}
},
computed: {
val: {
// 这里的计算属性使用了 getter、setter,可以简化代码
// 可参见链接 https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E7%9A%84-setter
get() {
return this.modelValue
},
set(val) {
this.$emit('change', val) // 触发
}
}
},
methods: {}
}
</script>
<style lang="less" scoped>
</style>
5、新建upload.vue组件
<template>
<el-upload
ref="upload"
class="upload-demo"
:file-list="val"
:multiple="fieldProperty.multiple"
:limit="fieldProperty.limit"
:disabled="fieldProperty.disabled"
action="#"
:auto-upload="false"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:on-change="handleChange"
:accept="fieldProperty.accept"
:on-remove="handleRemove">
<el-button type="primary" :loading="fieldProperty.loading">{{ fieldProperty.btnName }}</el-button>
<div class="el-upload__tip">{{ fieldProperty.tips}}</div>
</el-upload>
</template>
<script>
import { watch, reactive, computed, ref } from 'vue'
import { ElMessage, genFileId } from 'element-plus'
import { DownloadFileAPI } from "@/server/Base"
import { formDataDownFile } from "@/utils"
export default {
props: {
modelValue: [String, Number, Array], // 组件绑定值
property: {
type: Object,
default() {
return {}
}
}
},
setup(props, { emit }) {
const upload = ref()
const fieldProperty = computed(() => {
return {
multiple: false, // 是否可多选
limit: 10, // 依次最大可上传
btnName: '点击上传', // 出发上传按钮文案
tips: '', // 上传文案提示
accept: '', // 文件上传类型
disabled: false,
maximum: 20, // 默认最大 20M ; 1M = 1024b * 1024KB
loading: false,
...props.property
}
})
const val = computed({
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val) // 触发
}
})
const handleChange = (file, fileList) => {
const fileSuffix = file.name.substring(file.name.lastIndexOf(".") + 1).toLocaleLowerCase();
const whiteList = fieldProperty.value.accept.toLocaleLowerCase().split(',')
if (whiteList.indexOf('.'+fileSuffix) === -1 && fieldProperty.value.accept) {
val.value = fileList.filter((fie) => fie.uid !== file.uid)
ElMessage.error('The file type can only be ' + fieldProperty.value.accept)
return false;
}
if (file.size > fieldProperty.value.maximum * 1024 * 1024) {
val.value = fileList.filter((fie) => fie.uid !== file.uid)
ElMessage.error('The maximum upload size is ' + fieldProperty.value.maximum + 'M')
return
}
val.value = fileList
emit('fieldChange', { file, index: fileList.length - 1, fileList}) // 触发当前上传附件
}
const handleRemove = (file, fileList) => {
val.value = fileList
emit('fieldChange', fileList) // 触发
}
const handlePreview = (file) => {
const params = {
fullName: file.url || file.filePath?.FullName,
oriFileName: file.name || file.filePath?.OriginalName
}
const action = DownloadFileAPI(params)
formDataDownFile(action, params)
}
const handleExceed = (files) => {
console.log('100000', fieldProperty.multiple)
if (!fieldProperty.multiple) {
upload.value.clearFiles()
const file = files[0]
file.uid = genFileId()
upload.value.handleStart(file)
}
}
return {
handleChange,
handleRemove,
handleExceed,
handlePreview,
fieldProperty,
upload,
val
}
}
}
</script>
<style lang="less" scoped>
</style>
6、新建switch.vue组件
<template>
<el-switch v-model="val"
:disabled="fieldProperty.disabled"
:active-color="fieldProperty.activeColor"
:inactive-color="fieldProperty.inactiveColor">
</el-switch>
</template>
<script>
import { computed, reactive} from 'vue'
export default {
name: 'SmSwitch',
props: {
modelvalue: [Boolean],
property: {
type: Object,
default() {
return {}
}
}
},
setup(props, { emit }) {
const fieldProperty = reactive({
activeColor: '#13ce66',
inactiveColor: '#DCDFE6',
disabled: false,
...props.property
})
const val = computed({
get() {
return props.modelvalue
},
set(val) {
emit('update:modelvalue', val) // 触发
}
})
return {
val,
fieldProperty
}
}
}
</script>
<style lang="less" scoped></style>
7、新建radio.vue组件
<template>
<el-radio-group v-model="val" class="ml-4" :disabled="fieldProperty.disabled" :size="fieldProperty.size">
<el-radio v-for="(item, index) in fieldProperty.data" :key="item + index + RandomNumber()" :label="item[fieldProperty.value]">{{ item[fieldProperty.label] }}</el-radio>
</el-radio-group>
</template>
<script lang="ts">
import { computed, reactive} from 'vue'
import { RandomNumber } from '@/utils'
export default {
name: 'Radio',
props: {
modelvalue: [Boolean],
property: {
type: Object,
default() {
return {}
}
}
},
setup(props, { emit }) {
const fieldProperty = reactive({
readonly: false,
disabled: false,
label: 'label', // 选择框的文案字段
value: 'value', // 选择框的值字段
data: [],
size: 'default', // 'large' | 'default' | 'small'
...props.property
})
const val = computed({
get() {
return props.modelvalue
},
set(val) {
emit('update:modelvalue', val) // 触发
}
})
return {
val,
fieldProperty,
RandomNumber
}
}
}
</script>
<style lang="less" scoped></style>
8、新建checkbox.vue组件
<template>
<el-checkbox-group
v-if="fieldProperty.data.length"
v-model="val"
:disabled="fieldProperty.disabled"
@change="handleCheckedChange"
>
<el-checkbox
v-for="(item, index) in fieldProperty.data"
:key="item.label + index + RandomNumber()"
:disabled="item.disabled"
:label="item.value"
>
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
<el-checkbox
v-else
v-model="val"
:disabled="fieldProperty.disabled"
:label="fieldProperty.label"
/>
</template>
<script lang="ts">
import { computed, reactive } from "vue";
import { RandomNumber } from "@/utils";
import type { CheckboxData } from "@/interface/form";
export default {
name: "Radio",
props: {
modelvalue: [Array, Boolean],
property: {
type: Object,
default() {
return {};
},
},
},
setup(props, { emit }) {
const fieldProperty = computed(() => {
return {
readonly: false,
disabled: false,
label: "label", // 单选框的文案字段
data: [] as Array<CheckboxData>,
size: "default", // 'large' | 'default' | 'small'
...props.property,
};
});
const handleCheckedChange = () => {};
const val = computed({
get() {
return props.modelvalue;
},
set(val) {
emit("update:modelvalue", val); // 触发
},
});
return {
val,
RandomNumber,
handleCheckedChange,
fieldProperty,
};
},
};
</script>
<style lang="less" scoped></style>
9、新建date.vue组件
<template>
<el-date-picker
style="width: 100%"
v-model="date"
:type="fieldProperty.type"
:disabled="fieldProperty.disabled"
:range-separator="fieldProperty.ngeSeparator"
:placeholder="fieldProperty.placeholder"
:start-placeholder="fieldProperty.startPlaceholder"
:disabled-date="fieldProperty.disabledDate"
:end-placeholder="fieldProperty.endPlaceholder"
@change="change"
/>
</template>
<script lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export default {
props: {
modelValue: [String, Number], // 组件绑定值
content: [String, Number], // 组件绑定值
property: {
type: Object,
default() {
return {}
}
}
},
setup(props, { emit }) {
const { t } = useI18n()
const fieldProperty = computed(() => {
return {
startPlaceholder: t('Startdate'), // 提示语
endPlaceholder: t('Enddate'), // 提示语
placeholder: t('Selectdate'), // 提示语
readonly: false, // 是否只读
disabled: false, // 是否可输入
ngeSeparator: t('To'),
type: 'date', // year / month / date / dates / datetime / week / datetimerange / daterange / monthrange
disabledDate: (time: Date) => {
const beginDateVal = props.property.EffectDate
if(beginDateVal){
return time.valueOf()<new Date(beginDateVal).valueOf()- 24*60*60*1000
}
const endDateVal = props.property.endDate
if(endDateVal){
return time.valueOf() > new Date(beginDateVal).valueOf()- 24*60*60*1000
}
},
...props.property
}
})
const date = computed({
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val)
}
})
const change = (val: any) => {
emit('fieldChange', val) // 触发
}
return {
date,
change,
fieldProperty
}
},
}
</script>
<style lang="less" scoped></style>
10、新建time-picker.vue组件
<template>
<el-time-picker
style="width: 100%"
v-model="date"
:is-range="fieldProperty.type"
:disabled="fieldProperty.disabled"
:range-separator="fieldProperty.ngeSeparator"
:placeholder="fieldProperty.placeholder"
:start-placeholder="fieldProperty.startPlaceholder"
:end-placeholder="fieldProperty.endPlaceholder"
/>
</template>
<script lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export default {
props: {
modelValue: [String, Number], // 组件绑定值
content: [String, Number], // 组件绑定值
property: {
type: Object,
default() {
return {}
}
}
},
setup(props, { emit }) {
const { t } = useI18n()
const fieldProperty = computed(() => {
return {
startPlaceholder: t('Startdate'), // 提示语
endPlaceholder: t('Enddate'), // 提示语
placeholder: t('Selectdate'), // 提示语
readonly: false, // 是否只读
disabled: false, // 是否可输入
ngeSeparator: t('To'),
type: true, // true / false
...props.property
}
})
const date = computed({
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val)
}
})
return {
date,
fieldProperty
}
},
}
</script>
<style lang="less" scoped></style>
11、新建cascader.vue组件
<template>
<el-cascader
v-model="value"
:filterable="fieldProperty.filterable"
:placeholder="fieldProperty.placeholder"
:disabled="fieldProperty.disabled"
:clearable="fieldProperty.clearable"
:options="fieldProperty.data"
:collapse-tags="true"
:collapse-tags-tooltip="true"
@change="handleChange"
/>
</template>
<script lang="ts">
import { computed, reactive, watch } from 'vue'
export default {
props: {
modelValue: Array, // 组件绑定值
content: Array, // 组件绑定值
property: {
type: Object,
default() {
return {}
}
}
},
setup(props, { emit }) {
const fieldProperty = computed(() => {
return {
placeholder: '请选择', // 提示语
disabled: false, // 是否可输入
clearable: true, // 是否支持清空选项
filterable: true, // 是否可搜索
data: [],
...props.property
}
})
const value = computed({
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val)
}
})
const handleChange = (val) => {
console.log('handleChange', val)
}
return {
value,
fieldProperty,
handleChange
}
},
}
</script>
<style lang="less" scoped></style>
12、新建/ueditor/组件
- index.vue
<template>
<vue-ueditor-wrap
v-model="content"
:config="VueUeditorWrapConfig"
:editor-id="editorId"
></vue-ueditor-wrap>
</template>
<script>
import { default as VueUeditorWrapConfig } from "./ueditor.js";
import { RandomNumber } from '@/utils'
export default {
props: {
value: [String],
contentDetail: {
type: String,
default: "",
},
property: {
type: Object,
default() {
return {};
},
},
},
model: {
prop: "value", // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
event: "change", // 指定要触发的事件名字,将被用于 $emit
},
data() {
return {
show: false,
VueUeditorWrapConfig,
isPosting: false,
edit_show: true,
index: 0,
editorId: 'editor' + RandomNumber()
};
},
computed: {
content: {
get() {
console.log('10000', this.value)
return this.value;
},
set(val) {
this.$emit("change", val);
},
},
},
};
</script>
<style lang="less" scoped>
/* 弹框输入框长度 */
.lengthWidth {
width: 80%;
}
.editor_if {
width: 100%;
height: 400px;
}
.edui1 {
z-index: 90 !important;
}
</style>
- ueditor.js
// let env = process.env.NODE_ENV || ''
let ueditorPath = ''
// if (env === 'development') {
// ueditorPath = '/static/UEditor/'
// } else if (env === 'production') {
// ueditorPath = '/static/UEditor/'
// } else {
// ueditorPath = '/static/UEditor/'
// }
ueditorPath = '/ueditor/'
export default {
UEDITOR_HOME_URL: ueditorPath,
maximumWords: 1000 * 100,
// toolbars: [
// [
// 'source', '|', 'undo', 'redo', '|',
// 'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',
// 'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',
// 'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',
// 'directionalityltr', 'directionalityrtl', 'indent', '|',
// 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|',
// 'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',
// 'horizontal', 'date', 'time',
// 'inserttable', 'simpleupload', 'preview',
// ]
// ],
toolbars: [
[
'source', '|', 'undo', 'redo', '|',
'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',
'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',
'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',
'directionalityltr', 'directionalityrtl', 'indent', '|',
'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|',
'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',
'horizontal'
]
],
enableAutoSave: false,
elementPathEnabled: false,
disabledTableInTable: false,
serverUrl: '' // 暂无接口 APP_CONFIG.baseURL + '/file/upload'
}
13、新建/kind-editor组件
- index.vue
<template>
<vue3-kind-editor
:id="editorId"
:height="fieldProperty.height"
:width="fieldProperty.width"
v-model="content"
:items="fieldProperty.items"
:loadStyleMode="fieldProperty.loadStyleMode">
</vue3-kind-editor>
</template>
<script lang="ts" src="./index.js" />
<style lang="less" scoped>
/* 弹框输入框长度 */
.lengthWidth {
width: 80%;
}
.editor_if {
width: 100%;
height: 400px;
}
.edui1 {
z-index: 90 !important;
}
</style>
- index.js
// import Vue3KindEditor from '@zhj-target/vue3-kind-editor'
import { computed } from 'vue';
import Vue3KindEditor from './editor/index.vue'
import { RandomNumber } from '@/utils'
window._instances = []
export default {
components: {
Vue3KindEditor,
},
props: {
value: [String],
property: {
type: Object,
default() {
return {};
},
},
},
model: {
prop: "value", // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
event: "change", // 指定要触发的事件名字,将被用于 $emit
},
setup(props, { emit }) {
const fieldProperty = computed(() => {
return {
height: '150px',
width: '100%',
loadStyleMode: false,
items: [
'preview', 'template', '|', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold', 'italic', 'underline',
'removeformat', '|', 'justifyleft', 'justifycenter', 'justifyright', 'insertorderedlist',
'insertunorderedlist', '|', 'link'
],
...props.property
}
})
const content = computed({
get() {
return props.value
},
set(val) {
emit('update:value', val) // 触发
}
})
const editorId = computed(() => {
return 'editor' + RandomNumber()
})
return {
editorId,
content,
fieldProperty
}
}
};
- editor/index.vue
<template>
<div class="kindeditor">
<textarea :id="id" name="content">{{ state.outContent }}</textarea>
</div>
</template>
<script lang="ts" src="./index.ts" />
<style scoped>
.kindeditor {
width: 100%;
}
</style>
- editor/index.js
import { onMounted, reactive, watch } from "vue"
import '@public/kind-editor/themes/default/default.css'
import '@public/kind-editor/kindeditor-all.js'
import '@public/kind-editor/lang/zh-CN.js'
import '@public/kind-editor/lang/en.js'
export default {
name: 'vue3-kind-editor',
props: {
modelValue: {
type: String,
default: ''
},
id: {
type: String,
required: true
},
width: {
type: String
},
height: {
type: String
},
minWidth: {
type: Number,
default: 650
},
minHeight: {
type: Number,
default: 100
},
items: {
type: Array,
default: function () {
return [
'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'code', 'cut', 'copy', 'paste',
'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/',
'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold',
'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image',
'media', 'insertfile', 'table', 'hr', 'pagebreak',
'anchor', 'link', 'unlink',
]
}
},
noDisableItems: {
type: Array,
default: function () {
return ['source', 'fullscreen']
}
},
filterMode: {
type: Boolean,
default: true
},
htmlTags: {
type: Object,
default: function () {
return {
font: ['color', 'size', 'face', '.background-color'],
span: ['style'],
div: ['class', 'align', 'style'],
table: ['class', 'border', 'cellspacing', 'cellpadding', 'width', 'height', 'align', 'style'],
'td,th': ['class', 'align', 'valign', 'width', 'height', 'colspan', 'rowspan', 'bgcolor', 'style'],
a: ['class', 'href', 'target', 'name', 'style'],
embed: ['src', 'width', 'height', 'type', 'loop', 'autostart', 'quality',
'style', 'align', 'allowscriptaccess', '/'],
img: ['src', 'width', 'height', 'border', 'alt', 'title', 'align', 'style', '/'],
hr: ['class', '/'],
br: ['/'],
'p,ol,ul,li,blockquote,h1,h2,h3,h4,h5,h6': ['align', 'style'],
'tbody,tr,strong,b,sub,sup,em,i,u,strike': []
}
}
},
wellFormatMode: {
type: Boolean,
default: true
},
resizeType: {
type: Number,
default: 2
},
themeType: {
type: String,
default: 'default'
},
langType: {
type: String,
default: 'en'
},
designMode: {
type: Boolean,
default: true
},
fullscreenMode: {
type: Boolean,
default: false
},
basePath: {
type: String
},
themesPath: {
type: String
},
pluginsPath: {
type: String,
default: ''
},
langPath: {
type: String
},
minChangeSize: {
type: Number,
default: 5
},
loadStyleMode: {
type: Boolean,
default: true
},
urlType: {
type: String,
default: ''
},
newlineTag: {
type: String,
default: 'p'
},
pasteType: {
type: Number,
default: 2
},
dialogAlignType: {
type: String,
default: 'page'
},
shadowMode: {
type: Boolean,
default: true
},
zIndex: {
type: Number,
default: 811213
},
useContextmenu: {
type: Boolean,
default: true
},
syncType: {
type: String,
default: 'form'
},
indentChar: {
type: String,
default: '\t'
},
cssPath: {
type: [String, Array]
},
cssData: {
type: String
},
bodyClass: {
type: String,
default: 'ke-content'
},
colorTable: {
type: Array
},
afterCreate: {
type: Function
},
afterChange: {
type: Function
},
afterTab: {
type: Function
},
afterFocus: {
type: Function
},
afterBlur: {
type: Function
},
afterUpload: {
type: Function
},
uploadJson: {
type: String
},
fileManagerJson: {
type: Function
},
allowPreviewEmoticons: {
type: Boolean,
default: true
},
allowImageUpload: {
type: Boolean,
default: true
},
allowFlashUpload: {
type: Boolean,
default: true
},
allowMediaUpload: {
type: Boolean,
default: true
},
allowFileUpload: {
type: Boolean,
default: true
},
allowFileManager: {
type: Boolean,
default: false
},
fontSizeTable: {
type: Array,
default: function () {
return ['9px', '10px', '12px', '14px', '16px', '18px', '24px', '32px']
}
},
imageTabIndex: {
type: Number,
default: 0
},
formatUploadUrl: {
type: Boolean,
default: true
},
fullscreenShortcut: {
type: Boolean,
default: false
},
extraFileUploadParams: {
type: Array,
default: function () {
return []
}
},
filePostName: {
type: String,
default: 'imgFile'
},
fillDescAfterUploadImage: {
type: Boolean,
default: false
},
afterSelectFile: {
type: Function
},
pagebreakHtml: {
type: String,
default: '<hr style=”page-break-after: always;” class=”ke-pagebreak” />'
},
allowImageRemote: {
type: Boolean,
default: true
},
autoHeightMode: {
type: Boolean,
default: false
},
fixToolBar: {
type: Boolean,
default: false
},
tabIndex: {
type: Number
}
},
setup(props: any, { emit }: any) {
const state = reactive({
editor: {} as any,
outContent: props.modelValue
})
watch(() => props.modelValue, (val: any) => {
state.editor && val !== state.outContent && state.editor.html(val)
})
watch(() => state.outContent, (val: any) => {
emit('update:modelValue', val)
})
onMounted(async() => {
state.editor = (window as any).KindEditor.create('#' + props.id, {
width: props.width,
height: props.height,
minWidth: props.minWidth,
minHeight: props.minHeight,
items: props.items,
noDisableItems: props.noDisableItems,
filterMode: props.filterMode,
htmlTags: props.htmlTags,
wellFormatMode: props.wellFormatMode,
resizeType: props.resizeType,
themeType: props.themeType,
langType: props.langType,
designMode: props.designMode,
fullscreenMode: props.fullscreenMode,
basePath: props.basePath,
themesPath: props.cssPath,
pluginsPath: props.pluginsPath,
langPath: props.langPath,
minChangeSize: props.minChangeSize,
loadStyleMode: props.loadStyleMode,
urlType: props.urlType,
newlineTag: props.newlineTag,
pasteType: props.pasteType,
dialogAlignType: props.dialogAlignType,
shadowMode: props.shadowMode,
zIndex: props.zIndex,
useContextmenu: props.useContextmenu,
syncType: props.syncType,
indentChar: props.indentChar,
cssPath: props.cssPath,
cssData: props.cssData,
bodyClass: props.bodyClass,
colorTable: props.colorTable,
afterCreate: props.afterCreate,
afterChange: function () {
props.afterChange
state.outContent = this.html()
},
afterTab: props.afterTab,
afterFocus: props.afterFocus,
afterBlur: props.afterBlur,
afterUpload: props.afterUpload,
uploadJson: props.uploadJson,
fileManagerJson: props.fileManagerJson,
allowPreviewEmoticons: props.allowPreviewEmoticons,
allowImageUpload: props.allowImageUpload,
allowFlashUpload: props.allowFlashUpload,
allowMediaUpload: props.allowMediaUpload,
allowFileUpload: props.allowFileUpload,
allowFileManager: props.allowFileManager,
fontSizeTable: props.fontSizeTable,
imageTabIndex: props.imageTabIndex,
formatUploadUrl: props.formatUploadUrl,
fullscreenShortcut: props.fullscreenShortcut,
extraFileUploadParams: props.extraFileUploadParams,
filePostName: props.filePostName,
fillDescAfterUploadImage: props.fillDescAfterUploadImage,
afterSelectFile: props.afterSelectFile,
pagebreakHtml: props.pagebreakHtml,
allowImageRemote: props.allowImageRemote,
autoHeightMode: props.autoHeightMode,
fixToolBar: props.fixToolBar,
tabIndex: props.tabIndex
})
})
return {
state
}
}
}
二、配置的表单及使用
- 定义表单配置类
interface Rules {
[key: string]: Array<Rule>;
}
interface Rule {
required: boolean;
message?: string;
trigger: string;
validator?: Function;
}
interface unKnow {
[key: string]: any;
}
type DefaultFields = unKnow;
interface FormData {
[key: string]: FormDataInfo;
}
interface FormDataInfo {
title?: string;
field?: string;
colSize: number;
show: boolean;
type: string;
class?: Array<string>;
zIndex?: number;
property: FieldProperty;
}
type SelectList = unKnow;
interface FieldProperty {
disabled?: boolean;
readonly?: boolean;
placeholder?: string;
maxlength?: number;
label?: string;
value?: string;
filterable?: boolean;
clearable?: boolean;
data?: Array<SelectList>;
multiple?: boolean;
btnName?: string;
activeColor?: string;
inactiveColor?: string;
img?: string;
TitleT?: string;
time?: number;
type?: string | number;
search?: boolean;
del?: boolean;
sameAsSupplier?: boolean;
loading?: boolean;
limit?: number;
accept?: string;
}
class CancalBookingEntity {
public formRules: Rules = {};
public formFields: DefaultFields = {};
public formData: FormData = {};
constructor() {
this.formFields = {
Input: "",
Select: "",
Vhtml: "<div style='color: red'>color</div>",
Upload: "",
Switch: "",
Radio: "0",
Checkbox: true,
Date: "",
TimePicker: "",
Cascader: "",
UEditor: "",
KindEditor: "",
};
this.formData = {
Input: {
type: "Input",
colSize: 12,
show: true,
class: [],
title: "Input",
field: "Input",
property: {
type: "text",
placeholder: "text",
},
},
Reason: {
type: "Select",
colSize: 12,
show: true,
class: [],
title: "Select",
field: "Select",
property: {
data: [
{
label: "请选择",
value: "",
},
{
label: "Select",
value: "Select",
},
],
label: "label",
value: "value",
},
},
Vhtml: {
type: "Vhtml",
colSize: 12,
show: true,
class: [],
title: "Vhtml",
field: "Vhtml",
property: {},
},
upload: {
title: "upload:",
field: "upload",
type: "Upload",
colSize: 12,
show: true,
property: {
readonly: false,
multiple: true,
btnName: "Browse...",
},
},
Switch: {
title: "Switch:",
field: "Switch",
type: "Switch",
colSize: 12,
show: true,
property: {
readonly: false,
multiple: true,
btnName: "Browse...",
},
},
Radio: {
type: "Radio",
colSize: 12,
show: true,
title: "Radio",
field: "Radio",
class: [],
property: {
data: [
{ label: "男", value: "1" },
{ label: "女", value: "0" },
],
},
},
Checkbox: {
type: "Checkbox",
colSize: 12,
show: true,
class: [],
title: "Checkbox",
field: "Checkbox",
property: {
label: "",
},
},
Date: {
type: "Date",
colSize: 12,
show: true,
class: [],
title: "Date",
field: "Date",
property: {
placeholder: "Date",
type: "daterange",
},
},
TimePicker: {
type: "TimePicker",
colSize: 12,
show: true,
class: [],
title: "TimePicker",
field: "TimePicker",
property: {
placeholder: "TimePicker",
},
},
Cascader: {
type: "Cascader",
colSize: 12,
show: true,
class: [],
title: "Cascader",
field: "Cascader",
property: {
data: [
{
value: "CN",
label: "中国",
children: [
{
value: "CN",
label: "中国",
children: [
{
value: "CN",
label: "中国",
},
],
},
],
},
],
},
},
UEditor: {
type: 'UEditor',
colSize: 24,
show: true,
class: [],
title: 'UEditor',
field: 'UEditor',
property: {
placeholder: 'UEditor',
}
},
KindEditor: {
type: 'KindEditor',
colSize: 24,
show: true,
class: [],
title: 'KindEditor',
field: 'KindEditor',
property: {
placeholder: 'KindEditor',
}
},
};
}
}
export default CancalBookingEntity;
- 引入上传配置数据并应用form组件
<FormList
class="register-info-form"
ref="FormList"
:fields="businessInquiry.formFields"
:formData="businessInquiry.formData"
:rules="businessInquiry.formRules"
labelWidth="120px"
/>
export default {
setup() {
const FormListRef = ref()
const businessInquiry = reactive(new BusinessInquiry())
const validate = throttle(() => {
companyInfo.value.$refs.formData.validate((valid: boolean) => {
if (valid) ...
})
}, 1500)
return {
businessInquiry,
FormList,
validate
}
},
}
以上配置完即可显示配置好的表单啦~ ,更多详细细节后续有时间慢慢加上,然后再附上案例项目