123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- <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">
- <div class="setting" title="资源引用" @click="showResource">
- <el-badge :is-dot="!!resources.length" class="item">
- <i class="el-icon-setting" />
- </el-badge>
- </div>
- <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>
- <resource-dialog
- :visible.sync="resourceVisible"
- :origin-resource="resources"
- @save="setResource"
- />
- </div>
- </template>
- <script>
- import { parse } from '@babel/parser'
- import ClipboardJS from 'clipboard'
- import { saveAs } from 'file-saver'
- import {
- makeUpHtml, vueTemplate, vueScript, cssStyle
- } from '@/components/generator/html'
- import { makeUpJs } from '@/components/generator/js'
- import { makeUpCss } from '@/components/generator/css'
- import { exportDefault, beautifierConf, titleCase } from '@/utils/index'
- import ResourceDialog from './ResourceDialog'
- 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 {
- components: { ResourceDialog },
- props: ['formData', 'generateConf'],
- data() {
- return {
- activeTab: 'html',
- htmlCode: '',
- jsCode: '',
- cssCode: '',
- codeFrame: '',
- isIframeLoaded: false,
- isInitcode: false, // 保证open后两个异步只执行一次runcode
- isRefreshCode: false, // 每次打开都需要重新刷新代码
- resourceVisible: false,
- scripts: [],
- links: [],
- monaco: null
- }
- },
- computed: {
- resources() {
- return this.scripts.concat(this.links)
- }
- },
- 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 } = this.generateConf
- this.htmlCode = makeUpHtml(this.formData, type)
- this.jsCode = makeUpJs(this.formData, type)
- this.cssCode = makeUpCss(this.formData)
- 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() {
- this.resourceVisible = true
- },
- setResource(arr) {
- const scripts = []; const
- links = []
- if (Array.isArray(arr)) {
- arr.forEach(item => {
- if (item.endsWith('.css')) {
- links.push(item)
- } else {
- scripts.push(item)
- }
- })
- this.scripts = scripts
- this.links = links
- } else {
- this.scripts = []
- this.links = []
- }
- }
- }
- }
- </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>
|