|
@@ -0,0 +1,313 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <el-drawer v-bind="$attrs" v-on="$listeners" @opened="onOpen" @close="onClose">
|
|
|
+ <div style="height:100%">
|
|
|
+ <el-row style="height:100%;overflow:auto">
|
|
|
+ <el-col :md="24" :lg="12" class="left-editor">
|
|
|
+ <el-tabs v-model="activeTab" type="card" class="editor-tabs">
|
|
|
+ <el-tab-pane name="html">
|
|
|
+ <span slot="label">
|
|
|
+ <i v-if="activeTab==='html'" class="el-icon-edit" />
|
|
|
+ <i v-else class="el-icon-document" />
|
|
|
+ template
|
|
|
+ </span>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane name="js">
|
|
|
+ <span slot="label">
|
|
|
+ <i v-if="activeTab==='js'" class="el-icon-edit" />
|
|
|
+ <i v-else class="el-icon-document" />
|
|
|
+ script
|
|
|
+ </span>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane name="css">
|
|
|
+ <span slot="label">
|
|
|
+ <i v-if="activeTab==='css'" class="el-icon-edit" />
|
|
|
+ <i v-else class="el-icon-document" />
|
|
|
+ css
|
|
|
+ </span>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ <div v-show="activeTab==='html'" id="editorHtml" class="tab-editor" />
|
|
|
+ <div v-show="activeTab==='js'" id="editorJs" class="tab-editor" />
|
|
|
+ <div v-show="activeTab==='css'" id="editorCss" class="tab-editor" />
|
|
|
+ </el-col>
|
|
|
+ <el-col :md="24" :lg="12" class="right-preview">
|
|
|
+ <div class="action-bar" :style="{'text-align': 'left'}">
|
|
|
+ <span class="bar-btn" @click="runCode">
|
|
|
+ <i class="el-icon-refresh" />
|
|
|
+ 刷新
|
|
|
+ </span>
|
|
|
+ <span class="bar-btn" @click="exportFile">
|
|
|
+ <i class="el-icon-download" />
|
|
|
+ 导出vue文件
|
|
|
+ </span>
|
|
|
+ <span ref="copyBtn" class="bar-btn copy-btn">
|
|
|
+ <i class="el-icon-document-copy" />
|
|
|
+ 复制代码
|
|
|
+ </span>
|
|
|
+ <span class="bar-btn delete-btn" @click="$emit('update:visible', false)">
|
|
|
+ <i class="el-icon-circle-close" />
|
|
|
+ 关闭
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <!-- <iframe
|
|
|
+ v-show="isIframeLoaded"
|
|
|
+ ref="previewPage"
|
|
|
+ class="result-wrapper"
|
|
|
+ frameborder="0"
|
|
|
+ src="preview.html"
|
|
|
+ @load="iframeLoad"
|
|
|
+ /> -->
|
|
|
+ <!-- <div v-show="!isIframeLoaded" v-loading="true" class="result-wrapper" /> -->
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ </el-drawer>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script>
|
|
|
+import { parse } from '@babel/parser'
|
|
|
+import ClipboardJS from 'clipboard'
|
|
|
+import { saveAs } from 'file-saver'
|
|
|
+import {
|
|
|
+ makeUpHtml, vueTemplate, vueScript, cssStyle, makeUpWcHtml
|
|
|
+} from '@/components/generator/html'
|
|
|
+import { makeUpJs, makeUpWcJs } from '@/components/generator/js'
|
|
|
+import { makeUpCss } from '@/components/generator/css'
|
|
|
+import { exportDefault, beautifierConf } from '@/utils/index'
|
|
|
+import loadMonaco from '@/utils/loadMonaco'
|
|
|
+import loadBeautifier from '@/utils/loadBeautifier'
|
|
|
+
|
|
|
+const editorObj = {
|
|
|
+ html: null,
|
|
|
+ js: null,
|
|
|
+ css: null
|
|
|
+}
|
|
|
+const mode = {
|
|
|
+ html: 'html',
|
|
|
+ js: 'javascript',
|
|
|
+ css: 'css'
|
|
|
+}
|
|
|
+let beautifier
|
|
|
+let monaco
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'PreviewCode',
|
|
|
+ components: {},
|
|
|
+ props: {
|
|
|
+ formData: {
|
|
|
+ type: Object,
|
|
|
+ default() {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ generateConf: {
|
|
|
+ type: Object,
|
|
|
+ default() {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ activeTab: 'html',
|
|
|
+ htmlCode: '',
|
|
|
+ jsCode: '',
|
|
|
+ cssCode: '',
|
|
|
+ codeFrame: '',
|
|
|
+ isIframeLoaded: false,
|
|
|
+ isInitcode: false, // 保证open后两个异步只执行一次runcode
|
|
|
+ isRefreshCode: false, // 每次打开都需要重新刷新代码
|
|
|
+ scripts: [],
|
|
|
+ links: [],
|
|
|
+ monaco: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ },
|
|
|
+ watch: {},
|
|
|
+ created() {
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ window.addEventListener('keydown', this.preventDefaultSave)
|
|
|
+ const clipboard = new ClipboardJS('.copy-btn', {
|
|
|
+ text: trigger => {
|
|
|
+ const codeStr = this.generateCode()
|
|
|
+ this.$notify({
|
|
|
+ title: '成功',
|
|
|
+ message: '代码已复制到剪切板,可粘贴。',
|
|
|
+ type: 'success'
|
|
|
+ })
|
|
|
+ return codeStr
|
|
|
+ }
|
|
|
+ })
|
|
|
+ clipboard.on('error', e => {
|
|
|
+ this.$message.error('代码复制失败')
|
|
|
+ })
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener('keydown', this.preventDefaultSave)
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ preventDefaultSave(e) {
|
|
|
+ if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
|
|
|
+ e.preventDefault()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onOpen() {
|
|
|
+ const { type = 'file' } = this.generateConf
|
|
|
+ this.htmlCode = makeUpWcHtml(this.formData, type)
|
|
|
+ this.jsCode = makeUpWcJs(this.formData, type)
|
|
|
+ this.cssCode = makeUpCss(this.formData)
|
|
|
+ console.log('html', this.jsCode)
|
|
|
+
|
|
|
+ loadBeautifier(btf => {
|
|
|
+ beautifier = btf
|
|
|
+ this.htmlCode = beautifier.html(this.htmlCode, beautifierConf.html)
|
|
|
+ this.jsCode = beautifier.js(this.jsCode, beautifierConf.js)
|
|
|
+ // this.cssCode = beautifier.css(this.cssCode, beautifierConf.html)
|
|
|
+
|
|
|
+ loadMonaco(val => {
|
|
|
+ monaco = val
|
|
|
+ this.setEditorValue('editorHtml', 'html', this.htmlCode)
|
|
|
+ this.setEditorValue('editorJs', 'js', this.jsCode)
|
|
|
+ this.setEditorValue('editorCss', 'css', this.cssCode)
|
|
|
+ if (!this.isInitcode) {
|
|
|
+ this.isRefreshCode = true
|
|
|
+ this.isIframeLoaded && (this.isInitcode = true) && this.runCode()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onClose() {
|
|
|
+ this.isInitcode = false
|
|
|
+ this.isRefreshCode = false
|
|
|
+ },
|
|
|
+ iframeLoad() {
|
|
|
+ if (!this.isInitcode) {
|
|
|
+ this.isIframeLoaded = true
|
|
|
+ this.isRefreshCode && (this.isInitcode = true) && this.runCode()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setEditorValue(id, type, codeStr) {
|
|
|
+ if (editorObj[type]) {
|
|
|
+ editorObj[type].setValue(codeStr)
|
|
|
+ } else {
|
|
|
+ editorObj[type] = monaco.editor.create(document.getElementById(id), {
|
|
|
+ value: codeStr,
|
|
|
+ theme: 'vs-dark',
|
|
|
+ language: mode[type],
|
|
|
+ automaticLayout: true
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // ctrl + s 刷新
|
|
|
+ editorObj[type].onKeyDown(e => {
|
|
|
+ if (e.keyCode === 49 && (e.metaKey || e.ctrlKey)) {
|
|
|
+ this.runCode()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ runCode() {
|
|
|
+ const jsCodeStr = editorObj.js.getValue()
|
|
|
+ try {
|
|
|
+ const ast = parse(jsCodeStr, { sourceType: 'module' })
|
|
|
+ const astBody = ast.program.body
|
|
|
+ if (astBody.length > 1) {
|
|
|
+ this.$confirm(
|
|
|
+ 'js格式不能识别,仅支持修改export default的对象内容',
|
|
|
+ '提示',
|
|
|
+ {
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ )
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (astBody[0].type === 'ExportDefaultDeclaration') {
|
|
|
+ const postData = {
|
|
|
+ type: 'refreshFrame',
|
|
|
+ data: {
|
|
|
+ generateConf: this.generateConf,
|
|
|
+ html: editorObj.html.getValue(),
|
|
|
+ js: jsCodeStr.replace(exportDefault, ''),
|
|
|
+ css: editorObj.css.getValue(),
|
|
|
+ scripts: this.scripts,
|
|
|
+ links: this.links
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$refs.previewPage.contentWindow.postMessage(
|
|
|
+ postData,
|
|
|
+ location.origin
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ this.$message.error('请使用export default')
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ this.$message.error(`js错误:${err}`)
|
|
|
+ console.error(err)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ generateCode() {
|
|
|
+ const html = vueTemplate(editorObj.html.getValue())
|
|
|
+ const script = vueScript(editorObj.js.getValue())
|
|
|
+ const css = cssStyle(editorObj.css.getValue())
|
|
|
+ return beautifier.html(html + script + css, beautifierConf.html)
|
|
|
+ },
|
|
|
+ exportFile() {
|
|
|
+ this.$prompt('文件名:', '导出文件', {
|
|
|
+ inputValue: `${+new Date()}.vue`,
|
|
|
+ closeOnClickModal: false,
|
|
|
+ inputPlaceholder: '请输入文件名'
|
|
|
+ }).then(({ value }) => {
|
|
|
+ if (!value) value = `${+new Date()}.vue`
|
|
|
+ const codeStr = this.generateCode()
|
|
|
+ const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
|
|
|
+ saveAs(blob, value)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ showResource() {
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@import '@/styles/mixin.scss';
|
|
|
+.tab-editor {
|
|
|
+ position: absolute;
|
|
|
+ top: 33px;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+.left-editor {
|
|
|
+ position: relative;
|
|
|
+ height: 100%;
|
|
|
+ background: #1e1e1e;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.setting{
|
|
|
+ position: absolute;
|
|
|
+ right: 15px;
|
|
|
+ top: 3px;
|
|
|
+ color: #a9f122;
|
|
|
+ font-size: 18px;
|
|
|
+ cursor: pointer;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+.right-preview {
|
|
|
+ height: 100%;
|
|
|
+ .result-wrapper {
|
|
|
+ height: calc(100vh - 33px);
|
|
|
+ width: 100%;
|
|
|
+ overflow: auto;
|
|
|
+ padding: 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+}
|
|
|
+@include action-bar;
|
|
|
+::v-deep .el-drawer__header {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+</style>
|