| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- <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/FormGenerator/components/generator/html';
- import { makeUpJs } from '@/components/FormGenerator/components/generator/js';
- import { makeUpCss } from '@/components/FormGenerator/components/generator/css';
- import {
- exportDefault,
- beautifierConf,
- titleCase
- } from '@/components/FormGenerator/utils/index';
- import ResourceDialog from './ResourceDialog';
- import loadMonaco from '@/components/FormGenerator/utils/loadMonaco';
- import loadBeautifier from '@/components/FormGenerator/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 '@/components/FormGenerator/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;
- :deep(.el-drawer__header) {
- display: none;
- }
- </style>
|