效果图
一、第一个文件index.vue,也就是表单生成器的根页面
<script setup>
import axios from 'axios'
import { ref, reactive } from 'vue'
import formRender from './modules/formRender.jsx'
import formCreator from './modules/formCreator.vue'
import { ElNotification } from 'element-plus'
// 表单配置结构
let config = reactive({})
const flag = ref(false)
// 获取表单配置结构
const getFormConfig = async () => {
const { data } = await axios.get('http://localhost:3000/formConfig')
config = reactive(data)
flag.value = true
console.log('点击了生成, 重新请求了页面配置', config);
}
getFormConfig()
// 刷新配置
const refresh = () => {
flag.value = false
getFormConfig()
}
// 表单初始数据
const data = {}
// 提交表单
const submitForm = (data) => {
console.log('submit', data)
ElNotification({
title: 'Success',
message: data,
type: 'success',
})
}
</script>
<template>
<Suspense v-if="flag">
<el-main class="main">
<div class="configBox">
<formCreator @refresh="refresh"></formCreator>
</div>
<div class="formBox">
<formRender :config="config" :init="data" @submitForm="submitForm"></formRender>
</div>
</el-main>
</Suspense>
</template>
<style scoped lang="scss">
.main {
display: flex;
.configBox {
flex: 1;
margin-right: 10px;
padding: 20px 10px;
border-radius: 20px;
background-color: #8EC5FC;
background-image: linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%);
}
.formBox {
flex: 1;
padding: 20px 10px;
border-radius: 20px;
background-color: #FFDEE9;
background-image: linear-gradient(0deg, #FFDEE9 0%, #B5FFFC 100%);
}
}
</style>
-
第12行,http://localhost:3000/formConfig,这个是使用json–server生成的接口,数据存在目录下的一个json文件中,如果不了解的小伙伴儿可以看一下这篇文章https://blog.csdn.net/ligonglanyuan/article/details/120665684
二、json–server存放的页面渲染的数据结构
重点看一下下面的数据结构
{
"formConfig": {
"title": "表单标题",
"formItems": [
[
{
"colspan": 12,
"options": [],
"label": "姓名",
"type": "input",
"key": "username",
"placeholder": "请输入姓名",
"inputType": "text"
},
{
"colspan": 12,
"options": [],
"label": "账号",
"type": "number",
"key": "account",
"placeholder": "请输入年龄",
"inputType": "number"
}
],
[
{
"colspan": 12,
"options": [
{
"label": "男",
"value": "1"
},
{
"label": "女",
"value": "2"
}
],
"label": "性别",
"type": "select",
"key": "sadf"
},
{
"colspan": 12,
"options": [
{
"label": "西瓜",
"value": "1"
},
{
"label": "橡胶",
"value": "2"
},
{
"label": "巴啦啦",
"value": "3"
}
],
"label": "喜欢的",
"type": "radio",
"key": "ddd"
}
]
]
}
}
-
label:表单标题
-
placeholder:提示语
三、formCreator.vue,表单生成器组件
<script setup lang='ts'>
import axios from 'axios'
import { ref, reactive, defineEmits } from 'vue'
import { Plus, Lightning } from '@element-plus/icons-vue'
import rowMenu from './menu.vue'
import formOption from './formOption.vue'
// 菜单类型接口
interface menuPosition {
x: number,
y: number,
rowIndex: number,
colIndex: number
}
// 列数据类型接口
interface colData {
label: string,
type: string,
colspan: number,
key: string,
inputType: string,
placeholder: string,
options: Array<object>
}
const emit = defineEmits(['refresh'])
// 表单结构
let formConstruction: Array<Array<object>> = reactive([])
let flag = ref(false)
// 获取表单配置结构
axios.get('http://localhost:3000/formConfig').then(res => {
formConstruction = reactive(res.data.formItems)
flag.value = true
})
// 新增行
const addRow = () => {
formConstruction.push([{}])
}
// 删除行
const delRow = ({ rowIndex }: menuPosition) => {
formConstruction.splice(rowIndex, 1)
}
// 添加列
const addCol = ({ rowIndex }: menuPosition) => {
formConstruction[rowIndex].push([])
}
// 删除列
const delCol = ({ rowIndex, colIndex }: menuPosition) => {
if (formConstruction[rowIndex].length > 1) {
formConstruction[rowIndex].splice(colIndex, 1)
}
}
// 表单配置显隐
let formConfigDialogVisible = ref(false)
const cancelFormConfigDialog = () => {
formConfigDialogVisible.value = false
}
// 该列位置
let colPosition = reactive({})
// 该列数据
let colData = reactive({})
// 设置该列表单
const setColForm = (menuPosition: menuPosition) => {
formConfigDialogVisible.value = true
colPosition = menuPosition
colData = formConstruction[menuPosition.rowIndex][menuPosition.colIndex]
}
// 保存该列表单
const saveFormConfig = (formConfigObj: colData) => {
const { rowIndex, colIndex } = <menuPosition>colPosition
formConstruction[rowIndex][colIndex] = { ...formConfigObj }
}
// 生成表单
const createFormConfig = async () => {
const formConfig = { title: '表单标题', formItems: formConstruction }
await axios.post('http://localhost:3000/formConfig', formConfig)
emit('refresh')
}
// 菜单显隐
let showMenu = ref(false)
// 菜单位置
const menuPosition = reactive({
x: 0,
y: 0,
rowIndex: 0,
colIndex: 0
})
// 右键菜单
const handleContextmenu = (e: any, rowIndex: number, colIndex: number) => {
showMenu.value = true
menuPosition.x = e.clientX
menuPosition.y = e.clientY
menuPosition.rowIndex = rowIndex
menuPosition.colIndex = colIndex
}
window.onclick = () => {
showMenu.value = false
}
window.oncontextmenu = () => {
showMenu.value = false
}
</script>
<template>
<el-button style="margin-bottom: 20px;" type="primary" @click="addRow">
新增行<el-icon class="el-icon--right">
<Plus />
</el-icon>
</el-button>
<el-button style="margin-bottom: 20px;" type="primary" @click="createFormConfig">
生成<el-icon class="el-icon--right">
<Lightning />
</el-icon>
</el-button>
<!-- 操作菜单 -->
<rowMenu v-if="showMenu" :menuPosition="menuPosition" @delRow="delRow" @addCol="addCol" @delCol="delCol"
@setColForm="setColForm"></rowMenu>
<!-- 表单结构 -->
<Suspense v-if="flag">
<div class="row" v-for="(row, rowIndex) in formConstruction" :key="rowIndex">
<div class="col" v-for="(col, colIndex) in row" :key="colIndex"
@contextmenu.prevent.stop="handleContextmenu($event, rowIndex, colIndex)">
<el-tag v-if="col.type" style="height: 100%;" type="success">已设置</el-tag>
</div>
</div>
</Suspense>
<!-- 表单配置选择 -->
<el-dialog v-model="formConfigDialogVisible" title="form config" width="30%" destroy-on-close>
<formOption :colData="colData" @cancel="cancelFormConfigDialog" @save="saveFormConfig"></formOption>
</el-dialog>
</template>
<style scoped lang="scss">
.row {
display: flex;
height: 40px;
min-height: 40px;
margin-bottom: 20px;
padding: 2px;
border: 1px solid #fff;
border-radius: 5px;
cursor: pointer;
.col {
position: relative;
flex: 1;
height: 100%;
border-radius: 5px;
border-right: 1px solid #fff;
background-color: transparent;
transition: all 0.5s;
&:last-child {
border-right: none;
}
&:hover {
background-color: #f0f0f0;
}
}
}
</style>
四、menu.vue,操作菜组件
<script setup lang='ts'>
const { menuPosition } = defineProps({
menuPosition: {
type: Object,
required: true,
default: () => ({})
}
})
const emit = defineEmits(['delRow', 'addCol', 'delCol', 'setColForm'])
// 删除行
const delRow = () => {
emit('delRow', menuPosition)
}
// 添加列
const addCol = () => {
emit('addCol', menuPosition)
}
// 删除列
const delCol = () => {
emit('delCol', menuPosition)
}
// 设置该列表单
const setColForm = () => {
emit('setColForm', menuPosition)
}
</script>
<template>
<div class="menuBox" :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }">
<div class="btn" @click="delRow">删除当前行</div>
<div class="btn" @click="addCol">添加列</div>
<div class="btn" @click="delCol">删除当前列</div>
<div class="btn" @click="setColForm">配置该列表单</div>
</div>
</template>
<style scoped lang='scss'>
.menuBox {
position: fixed;
width: 200px;
background-color: #409eff;
color: #fff;
z-index: 1;
cursor: pointer;
border-radius: 10px;
.btn {
padding: 5px 0;
text-align: center;
&:hover {
background-color: #304156;
}
&:first-child {
border-radius: 10px 10px 0 0;
}
&:last-child {
border-radius: 0 0 10px 10px;
}
}
}
</style>
五、formOption.vue,表单配置组件
<script setup lang='ts'>
import { reactive, defineProps, defineEmits, computed } from 'vue'
const { colData } = defineProps({
colData: {
type: Object,
required: true,
default: () => ({})
}
})
const emit = defineEmits(['cancel', 'save'])
// 表单配置对象
let formConfigObj = reactive({ ...colData })
// 反转换列宽百分比
formConfigObj.colspan = formConfigObj.colspan ? Math.floor(formConfigObj.colspan / 0.24) : 1
// 默认options
formConfigObj.options = formConfigObj.options ? formConfigObj.options : []
// 表单类型
const options = [
{
value: 'input',
label: '文本输入框',
},
{
value: 'number',
label: '数字框',
},
{
value: 'password',
label: '密码框',
},
{
value: 'select',
label: '下拉选择框',
},
{
value: 'radio',
label: '单选框',
},
{
value: 'checkbox',
label: '多选框',
},
{
value: 'date',
label: '时间选择器',
},
]
// 显隐选项填写表单
let showOption = computed(() => formConfigObj.type === 'select' || formConfigObj.type === 'radio' || formConfigObj.type === 'checkbox')
// 添加选项
const addOptions = () => {
formConfigObj.options.push({})
}
// 取消
const cancel = () => {
emit('cancel')
}
// 保存
const save = () => {
const { colspan, type } = formConfigObj
// 列宽百分比转换
if (colspan === 1) {
formConfigObj.colspan = 24
} else if (colspan > 100) {
formConfigObj.colspan = 24
} else {
formConfigObj.colspan = Math.ceil(colspan * 0.24) || 1
}
//表单类型处理
if (type === 'input') {
formConfigObj.inputType = 'text'
} else if (type === 'number') {
formConfigObj.inputType = 'number'
} else if (type === 'password') {
formConfigObj.type = 'input'
formConfigObj.inputType = 'password'
}
emit('save', formConfigObj)
cancel()
}
</script>
<template>
<el-form ref="formConfig" :model="formConfigObj">
<el-form-item label="标题">
<el-input v-model="formConfigObj.label" placeholder="请输入标题"></el-input>
</el-form-item>
<el-form-item label="类型">
<el-select style="width: 100%;" v-model="formConfigObj.type" placeholder="选择表单类型">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="选择性表单选项" v-if="showOption">
<div>
<el-button type="primary" size="small" @click="addOptions">添加</el-button>
<div class="optionRow" v-for="(item, index) in formConfigObj.options" :key="index">
<el-input v-model="item.label" placeholder="请输入选项名"></el-input>
<el-input v-model="item.value" placeholder="请输入选项名值"></el-input>
</div>
</div>
</el-form-item>
<el-form-item label="字段">
<el-input v-model="formConfigObj.key" placeholder="请输入表单绑定字段"></el-input>
</el-form-item>
<el-form-item label="提示语">
<el-input v-model="formConfigObj.placeholder" placeholder="请输入表单内提示语"></el-input>
</el-form-item>
<el-form-item label="该列宽度占百分比">
<el-input type="number" :min="1" :max="100" v-model.number="formConfigObj.colspan"></el-input>
</el-form-item>
</el-form>
<div style="display: flex;justify-content: flex-end;">
<span class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</span>
</div>
</template>
<style scoped lang='scss'>
.optionRow {
margin-bottom: 10px;
}
</style>
六、formRender.jsx,渲染器组件, 该组件是jsx文件,vue3是完全支持jsx的,非常的友好,如果不了解的小伙伴可以看一下这篇文章https://blog.csdn.net/lwf3115841/article/details/128259536
import { reactive } from 'vue'
export default {
props: {
config: {
type: Object
},
init: {
type: Object
}
},
emits: ['submitForm'],
setup(props, context) {
// 表单配置
const { title, formItems } = props.config
// 表单回显
let formData = reactive(props.init)
// 渲染表单元素
const renderEle = (item) => {
switch (item.type) {
case 'input':
return <el-input type={item.inputType} placeholder={item.placeholder} v-model={formData[item.key]}></el-input>
case 'select':
return <el-select style="width: 100%;" v-model={formData[item.key]}>
{item.options.map(opt => (
<el-option label={opt.label} value={opt.value}></el-option>
))}
</el-select>
case 'number':
return <el-input min={0} type={item.inputType} v-model={formData[item.key]}></el-input>
case 'radio':
return <el-radio-group v-model={formData[item.key]}>
{item.options.map(opt => (
<el-radio label={opt.value}>{opt.label}</el-radio>
))}
</el-radio-group>
case 'checkbox':
return <el-checkbox-group v-model={formData[item.key]}>
{item.options.map(opt => (
<el-checkbox label={opt.value}>{opt.label}</el-checkbox>
))}
</el-checkbox-group>
case 'date':
return <el-date-picker style="width: 100%;" v-model={formData[item.key]}
type="date"
placeholder="请选择时间"
size={formData[item.size]} />
}
}
// 渲染列
const renderColumn = (cols) => {
return cols.map(col => (
<el-col span={col.colspan}>
<el-form-item label={col.label}>
{renderEle(col)}
</el-form-item>
</el-col>
))
}
// 渲染行
const renderRows = (rows) => {
return rows.map(row => (
<el-row>
{renderColumn(row)}
</el-row>
))
}
// 提交
const submit = () => {
context.emit('submitForm', formData)
}
return () => (<>
<el-button type="primary" style="margin: 0 0 20px 40px;" onClick={submit}>提交</el-button>
<el-form label-width="80px">
{formItems && renderRows(formItems)}
</el-form>
</>)
}
}
该文件负责了整个表单的渲染,如果我使用vue文件来做渲染,那整体页面结构会非常混乱,映入眼帘的是数不清的v-for,难以维护,使用jsx就完美的解决了这个问题
以上就是全部的内容了,该组件只是初步实现了动态表单的功能,后续的功能完善交给你们啦,不明白可以随时私信我哈,谢谢大家~
原文地址:https://blog.csdn.net/jsmeng626/article/details/129261090
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_6985.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。