大致思路
页面布局分为左中右,左边是组件列表,中间是视图列表,右边是组件内容。
父组件app.vue
<template>
<div class="content">
<div class="left">
<left />
</div>
<div class="center">
<center :value="data" :current="current"></center>
</div>
<div class="right">
<right :current="current" @update="update" />
</div>
</div>
</template>
<script setup>
import $bus from "./mitt";
import left from "./left.vue";
import center from "./center.vue";
import right from "./right.vue";
import { useStore } from "./store"
import { reactive, ref, watch } from "vue"
const store = useStore();
const data = ref([])
//当前选中的组件
const current = ref({
id: 0,
value: {}
})
watch(data, (val) => {
console.log(val);
})
const update = (e) => {
let index = data.value.findIndex(item => item.id == e.id)
data.value[index] = e
}
// 监听左侧组件点击事件 添加组件到中间组件
$bus.on("change", (val) => {
data.value.push(val)
current.value = val
});
// 监听中间组件点击事件 高亮当前选中的组件
$bus.on("changeCurent", (val) => {
current.value = val
});
// 监听右侧组件上传图片事件 修改中间组件数据
$bus.on("changeImg", (val) => {
let index = data.findIndex(item => item.id == val.id)
data.value[index].value.src = val.value
});
</script>
<style scoped>
* {
padding: 0;
margin: 0;
}
.content {
display: flex;
}
.left {
width: 400px;
}
.center {
width: 400px;
}
.right {
flex: 1;
max-width: calc(100% - 800px);
overflow: hidden;
}
</style>
左边left.vue
<template>
<div class="container">
<el-button v-for="i in list" :key="i.id" @click="handitem(i)">{{ i.title }}</el-button>
</div>
</template>
<script setup>
import $bus from "./mitt";
import { ref } from "vue";
const list = [
{
id: 1,
name: 'tlp1',
title: "宣传工具"
}, {
id: 2,
name: 'tlp2',
title: "宫格工具"
}, {
id: 3,
name: 'tlp3',
title: "图片工具"
}, {
id: 4,
name: 'tlp4',
title: "轮播工具"
}, {
id: 5,
name: 'tlp5',
title: "通知工具"
},
]
const handitem = (item) => {
// 根据不同组件类型,生成大致相同数据结构的数据
$bus.emit("change", (() => {
switch (item.name) {
case 'tlp1':
return {
name: item.name,
value: {
src: '',
type: "link",
src: "https://img.yzcdn.cn/vant/cat.jpeg",
},
id: new Date().getTime()
}
case 'tlp2':
return {
name: item.name,
value: {
columns: 4,
list: [
{
type: "link",
src: "https://img.yzcdn.cn/vant/cat.jpeg",
},
{
type: "link",
src: "https://img.yzcdn.cn/vant/cat.jpeg",
},
{
type: "link",
src: "https://img.yzcdn.cn/vant/cat.jpeg",
},
{
type: "link",
src: "https://img.yzcdn.cn/vant/cat.jpeg",
},
]
},
id: new Date().getTime()
}
}
})());
}
</script>
<style scoped>
.container {
padding: 30px;
}
.el-button {
margin: 10px;
}
</style>
中间center.vue
<template>
<div class="container">
<div class="mobile">
<transition-group name="drag" class="list" tag="ul">
<li v-for="item in props.value" @dragstart="dragstart(item)" :key="item.id"
@dragenter="dragenter($event, item.id)" @dragover.prevent draggable>
<component :style="{ border: current.id === item.id || currentDrag == item.id ? 'dotted' : '' }"
:is="components[item.name]" :value="item.value" @click="handCmp(item)" />
</li>
</transition-group>
</div>
</div>
</template>
<script setup>
import $bus from "./mitt";
import tlp1 from "./components/tpl1.vue";
import tlp2 from "./components/tpl2.vue";
import tlp3 from "./components/tpl3.vue";
import tlp4 from "./components/tpl4.vue";
import tlp5 from "./components/tpl5.vue";
const components = {
tlp1,
tlp2,
tlp3,
tlp4,
tlp5
};
import { ref, defineProps } from 'vue';
const props = defineProps({
value: {
type: Array,
default: () => []
},
current: {
type: Object,
}
});
/**
* 当前选中的索引
*/
const currentDrag = ref(0)
/**
* 处理组件点击事件
* @param {Object} item - 被点击的组件对象
*/
const handCmp = (item) => {
$bus.emit('changeCurent', item)
currentDrag.value = 0
}
/**
* 拖拽开始事件处理函数
* @param {number} id - 被拖拽的组件的id
*/
const dragstart = (item) => {
console.log(item, 'dragstart');
$bus.emit('changeCurent', item)
currentDrag.value = item.id
}
/**
* 拖拽进入事件处理函数
* @param {Event} e - 拖拽事件对象
* @param {number} id - 当前拖拽进入的组件的id
*/
const dragenter = (e, id) => {
console.log(id, 'dragenter');
e.dataTransfer.dropEffect = "move";
e.preventDefault();
if (currentDrag.value !== id) {
let index1 = props.value.findIndex(item => item.id == currentDrag.value)
let index2 = props.value.findIndex(item => item.id == id)
let temp = props.value[index1]
props.value[index1] = props.value[index2]
props.value[index2] = temp
}
}
</script>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* 问题图片不可选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.mobile {
width: 375px;
height: 667px;
border: solid;
border-radius: 10px;
overflow-y: auto;
}
</style>
右边
<template>
<div class="container">
{{ props.current }}
<div v-if="props.current.name == 'tlp1'">
<input type="file" @change="uploadImg">
<img :src="props.current.value.src" v-if="props.current.value.src">
<el-radio-group v-model="props.current.value.type" class="ml-4">
<el-radio label="link" size="large">页面跳转</el-radio>
<el-radio label="waibu" size="large">外部链接</el-radio>
</el-radio-group>
</div>
<div v-else-if="props.current.name == 'tlp2'">
<div class="flex-row">
<div>列数</div>
<el-input-number v-model="props.current.value.columns" :min="1" :max="6" :step="1" />
</div>
<div v-for="item in props.current.value.list">
<el-avatar :size="30" :src="item.src" />
</div>
<el-button @click="pushGrid">添加</el-button>
</div>
</div>
</template>
<script setup>
import { defineProps, watch, defineEmits } from 'vue';
const props = defineProps({
current: {
type: Object,
default: () => { }
}
});
const emit = defineEmits(['update'])
const uploadImg = (e) => {
// 获取文件对象
let file = e.target.files[0];
// 创建文件读取对象
let reader = new FileReader();
// 读取文件
reader.readAsDataURL(file);
// 文件读取完毕后触发
reader.onload = function (e) {
// 获取文件读取完毕时的结果
let img = e.target.result;
emit('update', props.current)
}
}
const pushGrid = () => {
// 将链接信息添加到列表中
props.current.value.list.push({
type: "link",
src: "https://img.yzcdn.cn/vant/cat.jpeg",
})
emit('update', props.current)
}
</script>
<style scoped>
img {
width: 200px;
height: 150px;
}
</style>
总结。始终是要围着app.vue父组件进行的。必须要定义当前点击的组件,不然右侧无法渲染哪个组件的内容。右侧组件无论改了什么,都应该更新值到父组件。父组件通过id来判断哪个组件改变了来更新值。