Vue2之组件在线编辑器
- 通过props、events 实现父子组件通信
- 通过ref属性获取组件实例
src
├─ components
│ ├─ edit.vue
│ └─ show.vue
├─ App.vue
实现效果
![run](https://img-blog.csdnimg.cn/img_convert/7303b6fee49f904ccf57136c512bbe08.gif)
App.vue
<template>
<div id="app">
<Edit @input="handleInput" @run="handleRun"></Edit>
<Show :code="code" ref="show"></Show>
</div>
</template>
<script>
import Edit from "./components/edit.vue";
import Show from "./components/show.vue";
export default {
data() {
return {
code: "",
};
},
components: {
Edit,
Show,
},
created() {},
mounted() {},
methods: {
handleInput(value) {
this.code = value;
},
handleRun() {
this.$refs.show.run();
},
},
};
</script>
<style lang="css">
* {
margin: 0;
padding: 0;
}
html,
body,
#app {
height: 100vh;
width: 100%;
margin: 0;
}
#app {
display: flex;
justify-content: space-around;
}
</style>
edit.vue
<template>
<div class="edit">
<div class="edit-list">
<button @click="$emit('run')">运行</button>
<button>清空</button>
</div>
<div class="edit-box">
<textarea :value="code" @input="handleInput"></textarea>
<!--对于原生标签 v-model 并不是在任何时候都等于:value +@input ,内部会对输入法做处理 -->
<!-- <textarea v-model="code"></textarea> -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
code: "",
};
},
created() {},
mounted() {},
methods: {
handleInput(e) {
this.code = e.target.value;
this.$emit("input", this.code);
},
},
};
</script>
<style scoped lang="css">
.edit {
width: 45%;
}
.edit .edit-list {
padding: 20px;
position: relative;
}
.edit .edit-list button {
width: 150px;
height: 40px;
margin-right: 10px;
background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
border-radius: 30px;
border: 1px solid #ccc;
outline: none;
color: #fff;
cursor: pointer;
background-size: 400%;
}
button:hover {
animation: animate 8s linear infinite;
}
button::before {
content: "";
inset: -5px;
z-index: -1;
background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
border-radius: 40px;
background-size: 400%;
opacity: 0;
}
button:hover::before {
filter: blur(20px);
opacity: 1;
animation: animate 8s linear infinite;
}
.edit-box textarea {
border: 1px solid #ccc;
outline: none;
width: 100%;
height: calc(100vh - 200px);
}
@keyframes animate {
form {
background-position: 0%;
}
to {
background-position: 400%;
}
}
</style>
show.vue
<template>
<div class="show">
<h2>代码运行结果</h2>
<div class="result-box" ref="show">
</div>
</div>
</template>
<script>
export default {
data() {
return {};
},
props: {
code: {
type: String,
default: "",
},
},
created() {},
mounted() {},
methods: {
/** 获取模板内容 */
getSource(type) {
// 开始标签
let reg = new RegExp(`<${type}[^>]*>`);
let code = this.code;
let matched = code.match(reg);
if (matched) {
let tag = matched[0];
return code.slice(
code.indexOf(tag) + tag.length,
code.lastIndexOf(`</${type}>`)
);
}
return "";
},
run() {
// 获取模板中的内容 js css
const template = this.getSource("template");
const script = this.getSource("script").replace(
/export default/,
"return"
);
const style = this.getSource("style");
// 动态加载组件
let component = {};
if (script) {
component = new Function(script)();
}
if (template) {
component.template = template;
// $mount进行挂载 this.$options._base 就是大Vue
// 解析出对应的内容,采用Vue.extend构造Vue组件,手动挂载到对应的元素上.当ref属性指定在DOM身上时,代表的是真实的DOM元素
let instance = new (this.$options._base.extend(component))();
// instance.$mount().$el在内存中进行挂载,挂载的结果放到$el上
this.$refs.show.appendChild(instance.$mount().$el);
}
if (style) {
let element = document.createElement("style");
element.type = "text/css";
element.innerText = style;
document.body.appendChild(element);
}
},
},
};
</script>
<style scoped lang="css">
.show {
width: 45%;
}
.show h2 {
padding: 20px;
height: 80px;
box-sizing: border-box;
}
.result-box {
width: 100%;
height: calc(100vh - 200px);
border: 1px solid #ccc;
padding: 20px;
box-sizing: border-box;
}
</style>