Vue实现代码编辑并实时预览
2023-12-13 12:41:09
实现代码编辑并实时预览,我使用的是codemirror,以下是代码示例,直接上代码:
vueMain.vue (主页面,左侧为代码编辑区域,右侧为代码预览区域)
<template>
<div :id="getRandomId" class="comp-home">
<div class="online-comp-editor">
<div class="demo-split-pane">
<!-- 代码 -->
<codemirror
class="left-box"
ref="myCodeMirror"
v-model="code"
:options="cmOptions"
@ready="onCmReady"
></codemirror>
</div>
</div>
<div slot="right" class="ol-right">
<!-- 展示 -->
<run-code
style="width: 100%;height: 100%;"
v-if="runCodeHash"
:code="code"
ref="runCodeComp"
></run-code>
</div>
</div>
</div>
</template>
<script>
import runCode from "../components/runCode/runCode";
//code模版
import {codeList} from "../components/codeList/index";
//vue-codemirror相关
import {codemirror} from "vue-codemirror";
import "../components/codeMirror/index.js";
import "../components/codeMirror/myCodeMirror.css";
// 主题 theme style
import "codemirror/theme/base16-light.css";
import "codemirror/theme/base16-dark.css";
import {getuuid} from "@/utils/mUtils";
import fontIcon from "../../../../../../davp-gis/src/space/fontIcon.vue";
import customOnlineUpload from "../../custom/customOnlineUpload";
import api from 'davp-screen/src/service/getData';
export default {
name: "vueRunning",
components: {
customOnlineUpload,
fontIcon,
runCode,
codemirror
},
data() {
return {
split: 0.5,
initCode: "",
code: "",
cmOptions: {
mode: "vue",
lineNumbers: true, //行号
theme: 'base16-dark',// material
autoCloseBrackets: true, //自动补全括号
matchBrackets: true, //匹配括号
showCursorWhenSelecting: true, //select显示光标
autoCloseTags: true,
tabSize: 4,
foldGutter: true, //可折叠的块
gutters: [
"CodeMirror-linenumbers",
"CodeMirror-foldgutter",
"CodeMirror-lint-markers"
],
autofocus: true,
styleActiveLine: true,
hintOptions: {
completeSingle: false
},
keyMap: "sublime",
extraKeys: {
"Ctrl-Q": "autocomplete"
}
},
runCodeHash: true,
cusOnlineForm: {
cusCode: '',
isPublish: '1',
id: ''
},
btnFlag: 1,
};
},
props: {
cusData: {
type: Object,
default: {
name: '',
cusCode: '',
id: '',
remark: '',
isPublish: ''
}
},
},
methods: {
doSave() {
// TODO 保存操作
},
run() {
this.$refs.runCodeComp.destroyCode();
this.$refs.runCodeComp.renderCode();
this.$message.success("已运行");
},
reset() {
this.$refs.runCodeComp.destroyCode();
this.code = this.initCode;
this.$nextTick(() => {
this.$refs.runCodeComp.renderCode();
this.$message.success("已重置");
});
},
onCmReady(cm) {
cm.on("keypress", () => {
cm.showHint();
});
},
getRandomId() {
return getuuid;
},
},
watch: {
$route: {
handler(val) {
},
immediate: true
}
},
created() {
//解决嵌套使用codemirror时,点击才会显示的问题。
setTimeout(() => {
this.$refs.myCodeMirror.codemirror.refresh();
}, 100);
},
mounted() {
this.$nextTick(() => {
this.code = this.initCode = this.cusData.cusCode.length === 0 ? codeList['template'] : this.cusData.cusCode;
this.cusOnlineForm.isPublish = this.cusData.isPublish;
if (this.btnFlag === 2) {
this.cusOnlineForm.id = this.cusData.id;
}
this.runCodeHash = false;
this.$nextTick(() => {
this.runCodeHash = true;
});
});
}
};
</script>
<style scoped lang="scss">
@import 'davp-screen/src/style/mixin.scss';
.comp-home {
width: 100%;
height: 100%;
box-sizing: border-box;
.online-comp-editor {
display: flex;
height: 100%;
width: 100%;
.ol-left {
width: 50%;
height: 100%;
padding: 0;
box-sizing: border-box;
border-right: 2px solid rgba(175, 171, 171, 0.17);
border-left: 2px solid rgba(175, 171, 171, 0.17);
border-bottom: 2px solid rgba(175, 171, 171, 0.17);
.operator-btn {
width: 100%;
height: 50px;
box-sizing: border-box;
border-bottom: 2px solid rgba(175, 171, 171, 0.17);
display: flex;
flex-direction: row-reverse;
flex-wrap: nowrap;
align-items: center;
position: relative;
.cus-from {
display: flex;
flex-wrap: nowrap;
align-content: center;
align-items: center;
height: 50px;
margin-right: 20px;
position: absolute;
left: 10px;
}
/deep/ .el-button {
margin: 0;
margin-right: 20px;
background-color: rgb(52, 52, 52);
color: rgb(255, 255, 255);
border-color: rgb(52, 52, 52);
}
/deep/ .el-button:hover, .el-button:focus {
color: white;
border-color: rgba(93, 93, 93, 0.88);
background-color: rgba(93, 93, 93, 0.88);
}
}
.demo-split-pane {
width: 100%;
height: calc(100%);
//height: calc(100% - 50px);
padding: 0;
}
}
.ol-right {
width: 50%;
height: 100%;
}
}
}
</style>
runCode.vue(代码预览页面)
<template>
<div :ref="'codeTemplate_' + cssId"></div>
</template>
<script>
import Vue from "vue";
import {getuuid} from "@/utils/mUtils";
export default {
name: "runCode",
data() {
return {
cssId: getuuid(),
html: "",
js: "",
css: "",
component: null
};
},
props: {
code: {
type: String,
default: ""
},
compId: {
type: String,
default: ""
}
},
methods: {
getSource(source, type) {
const regex = new RegExp(`<${type}[^>]*>`);
let openingTag = source.match(regex);
if (!openingTag) {
return "";
} else {
openingTag = openingTag[0];
}
return source.slice(
source.indexOf(openingTag) + openingTag.length,
source.lastIndexOf(`</${type}>`)
);
},
//分割代码
splitCode() {
this.js = this.getSource(this.code, "script").replace(
/export default/,
"return "
);
this.css = this.getSource(this.code, "style");
this.html = this.getSource(this.code, "template");
},
//挂载
renderCode() {
this.splitCode();
if (this.html !== "" && this.js !== "") {
const parseStrToFunc = new Function(this.js)();
parseStrToFunc.template = this.html;
const Component = Vue.extend(parseStrToFunc);
this.component = new Component().$mount();
this.$refs['codeTemplate_' + this.cssId].appendChild(this.component.$el);
if (this.css !== "") {
const style = document.createElement("style");
style.type = "text/css";
style.id = this.cssId;
style.innerHTML = this.css;
document.getElementsByTagName("head")[0].appendChild(style);
}
}
},
destroyCode() {
const $target = document.getElementById(this.cssId);
if ($target) {
$target.parentNode.removeChild($target);
}
if (this.component) {
this.$refs['codeTemplate_' + this.cssId].removeChild(this.component.$el);
this.component.$destroy();
this.component = null;
}
}
},
mounted() {
//挂载节点
this.renderCode();
},
beforeDestroy() {
this.destroyCode();
}
};
</script>
<style scoped></style>
codeMirror文件夹下:
1、index.js
import 'codemirror/lib/codemirror.css'
// language
import 'codemirror/mode/vue/vue.js'
// theme css
import 'codemirror/theme/base16-dark.css'
// active-line.js
import 'codemirror/addon/selection/active-line.js'
// styleSelectedText
import 'codemirror/addon/selection/mark-selection.js'
import 'codemirror/addon/search/searchcursor.js'
// highlightSelectionMatches
import 'codemirror/addon/scroll/annotatescrollbar.js'
import 'codemirror/addon/search/matchesonscrollbar.js'
import 'codemirror/addon/search/searchcursor.js'
import 'codemirror/addon/search/match-highlighter.js'
// keyMap
import 'codemirror/mode/clike/clike.js'
import 'codemirror/addon/edit/matchbrackets.js'
import 'codemirror/addon/comment/comment.js'
import 'codemirror/addon/dialog/dialog.js'
import 'codemirror/addon/dialog/dialog.css'
import 'codemirror/addon/search/searchcursor.js'
import 'codemirror/addon/search/search.js'
import 'codemirror/keymap/sublime.js'
// foldGutter
import 'codemirror/addon/fold/foldgutter.css'
import 'codemirror/addon/fold/brace-fold.js'
import 'codemirror/addon/fold/comment-fold.js'
import 'codemirror/addon/fold/foldcode.js'
import 'codemirror/addon/fold/foldgutter.js'
import 'codemirror/addon/fold/indent-fold.js'
import 'codemirror/addon/fold/markdown-fold.js'
import 'codemirror/addon/fold/xml-fold.js'
//hint
import 'codemirror/addon/hint/javascript-hint'
import 'codemirror/addon/hint/html-hint'
import 'codemirror/addon/hint/css-hint'
import 'codemirror/addon/hint/show-hint'
import 'codemirror/addon/hint/anyword-hint'
import 'codemirror/addon/hint/show-hint.css'
//
import 'codemirror/addon/edit/closebrackets.js'
2、myCodeMirror.css
.vue-codemirror {
height: 100%;
}
/*.CodeMirror {*/
/* height: 100%;*/
/* font-size: 16px;*/
/* font-weight: 500;*/
/* line-height: 20px;*/
/* font-family: monospace;*/
/*}*/
.CodeMirror {
height: 100%;
font-size: 16px;
font-weight: 500;
line-height: 20px;
font-family: monospace;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor .CodeMirror-line::selection,
.cm-fat-cursor .CodeMirror-line > span::selection,
.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
.cm-fat-cursor .CodeMirror-line::-moz-selection,
.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
.cm-fat-cursor { caret-color: transparent; }
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px; margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
z-index: 0;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
outline: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -50px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
codeList下:(有子文件index.js和template.js 以及文件夹iview)
1、iview文件夹下
1.1 test.js(模板代码)
export default `<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello World!'
};
},
methods: {},
mounted() {}
};
</script>
<style>
</style>`;
1.2 test.vue(模板页面)
<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello World!'
};
},
methods: {},
mounted() {}
};
</script>
<style>
</style>
2、index.js
const files = require.context("../codeList", true, /\.js$/);
let codeList = {}; //通过key取值
//icon
let iconList = {
template: "el-icon-s-home",
"element/": "el-icon-eleme",
"iview/": "ivu-icon ivu-icon-logo-vimeo"
};
//菜单
let menuList = [
{
path: "/template",
name: "template",
title: "模版"
}
];
files.keys().forEach(filePath => {
let key = filePath.substring(2, filePath.lastIndexOf("."));
// codeList
if (key !== "index"){
codeList[key] = files(filePath).default;
}
// menuList
if (key !== "index" && key !== "template") {
menulistHandle(key);
}
});
// console.log("codeList", codeList);
// console.log("menuList", menuList);
//路由处理->生成菜单
function menulistHandle(key) {
let keyArr = key.split("/");
let Pointer = menuList;
for (let i = 0, len = keyArr.length; i < len; i++) {
let lastPointer = Pointer;
let curnode = keyArr[i];
//menuList 是否存在
for (let menu of Pointer) {
if (menu.title == curnode) {
Pointer = menu.children;
break;
}
}
if (lastPointer == Pointer) {
let name =
i === keyArr.length - 1 ? key : keyArr.slice(0, i + 1).join("/") + "/";
let newNode = {
name,
title: curnode,
children: []
};
if (i === keyArr.length - 1) {
delete newNode.children;
}
Pointer.push(newNode);
Pointer = newNode.children;
}
}
}
export { codeList, menuList, iconList };
3、template.js
export default `<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello World!'
};
},
methods: {},
mounted() {}
};
</script>
<style>
</style>`;
以上为代码部分,仅供参考, 如有不清楚或者源码可联系我。
文章来源:https://blog.csdn.net/qq_44577699/article/details/134824446
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!