mirror of
https://github.com/sahadev/vue-component-creater-ui.git
synced 2025-09-28 07:13:21 +08:00
init
This commit is contained in:
321
src/components/AttributeInput.vue
Normal file
321
src/components/AttributeInput.vue
Normal file
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<el-card class="attribute-container">
|
||||
|
||||
<center>
|
||||
<el-switch v-model="editMode" active-text="自由编辑" inactive-text="约束编辑" active-color="#13ce66"
|
||||
inactive-color="#13ce66">
|
||||
</el-switch>
|
||||
</center>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<div name="1" v-show="!editMode">
|
||||
<div>
|
||||
<div class="item" v-for="(item, index) in localAttributes" :key="index">
|
||||
<el-input v-model="item.key" :placeholder="'key' + index" class="half-width"></el-input>
|
||||
<div class="split">:</div>
|
||||
<el-input v-model="item.value" :placeholder="'value' + index" class="half-width"></el-input>
|
||||
<i class="el-icon-minus" @click="deleteItem(index)"></i>
|
||||
</div>
|
||||
|
||||
<div class="quick-add-root">
|
||||
快速增加一些属性:
|
||||
<div style="margin-top: 5px;">
|
||||
<transition name="el-zoom-in-center">
|
||||
<el-tag v-if="attributeKeys.indexOf('class') == -1" size="mini" type="success" @click="onClassClick"
|
||||
effect="dark">Class
|
||||
</el-tag>
|
||||
</transition>
|
||||
<transition name="el-zoom-in-center">
|
||||
<el-tag v-if="attributeKeys.indexOf('@click') == -1" size="mini" type="success" @click="onEventClick"
|
||||
effect="dark">点击事件</el-tag>
|
||||
</transition>
|
||||
<transition name="el-zoom-in-center">
|
||||
<el-tag v-if="!attributeKeys.includes('__text__')" size="mini" type="success" @click="onTextClick"
|
||||
effect="dark">文本内容</el-tag>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div name="2" v-show="editMode">
|
||||
<el-input type="textarea" :autosize="{ minRows: 4}" placeholder="请输入属性, 以key: value的形式(冒号后要有空格)"
|
||||
v-model="textAttributes">
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<center style="margin-top: 10px;">
|
||||
<el-tooltip class="item" effect="dark" content="新增属性 ctrl+a" placement="bottom">
|
||||
<el-button type="primary" class="center" @click="createNew" icon="el-icon-circle-plus-outline" circle>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="保存属性 ctrl+s" placement="bottom">
|
||||
<el-button type="success" class="center" @click="save" icon="el-icon-refresh" circle></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="移除该组件 ctrl+d" placement="bottom">
|
||||
<el-button v-if="enableRemoveButton" type="danger" class="center" icon="el-icon-delete" @click="remove" circle>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="复制一个兄弟组件 ctrl+c" placement="bottom">
|
||||
<el-button v-if="enableBroButton" type="primary" class="center" icon="el-icon-copy-document" @click="copyBro"
|
||||
circle>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<center>
|
||||
<span class="shortcut-tip">支持快捷键操作</span>
|
||||
</center>
|
||||
</center>
|
||||
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRawComponentKey, getRawComponentContent } from "@/utils/common";
|
||||
import { brotherEleEnum, copyBroCode } from "@/libs/bro-ele-config";
|
||||
const keymaster = require('keymaster');
|
||||
|
||||
export default {
|
||||
props: ['__rawVueInfo__', 'enableRemoveButton', 'shortcutInitMode'],// __rawVueInfo__为当前编辑的原始代码对象, shortcutInitMode快捷键的初始化方式
|
||||
data: function () {
|
||||
return {
|
||||
input: "",
|
||||
localAttributes: [],
|
||||
enable: true,
|
||||
autoplay: false,
|
||||
editMode: false,
|
||||
textAttributes: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const container = document.querySelector(".attribute-container");
|
||||
container.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
if (this.shortcutInitMode === 'auto') {
|
||||
// 这种方式用于在检视图中,因为它依赖组件的创建和销毁
|
||||
this.initShortcut();
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.shortcutInitMode === 'auto') {
|
||||
// 防止内存泄漏
|
||||
this.destroyShortcut();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
destroyShortcut() {
|
||||
console.log(`destroyShortcut by mode: ${this.shortcutInitMode}`)
|
||||
keymaster.unbind('⌘+a, ctrl+a');
|
||||
keymaster.unbind('⌘+s, ctrl+s');
|
||||
keymaster.unbind('⌘+d, ctrl+d');
|
||||
keymaster.unbind('⌘+c, ctrl+c');
|
||||
},
|
||||
|
||||
initShortcut() {
|
||||
console.log(`init by mode: ${this.shortcutInitMode}`)
|
||||
|
||||
|
||||
keymaster('⌘+a, ctrl+a', () => {
|
||||
if (this.enable) {
|
||||
this.createNew();
|
||||
return false
|
||||
}
|
||||
});
|
||||
keymaster('⌘+s, ctrl+s', () => {
|
||||
if (this.enable) {
|
||||
this.save();
|
||||
return false
|
||||
}
|
||||
});
|
||||
keymaster('⌘+d, ctrl+d', () => {
|
||||
if (this.enable) {
|
||||
this.remove();
|
||||
return false
|
||||
}
|
||||
});
|
||||
keymaster('⌘+c, ctrl+c', () => {
|
||||
if (this.enable && this.enableBroButton) {
|
||||
this.copyBro();
|
||||
return false
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onClassClick() {
|
||||
this.localAttributes.push({ key: "class", value: "class-name" });
|
||||
},
|
||||
onEventClick() {
|
||||
this.localAttributes.push({ key: "@click", value: "onEventClick" });
|
||||
},
|
||||
onTextClick() {
|
||||
this.localAttributes.push({ key: "__text__", value: "content" });
|
||||
},
|
||||
|
||||
createNew() {
|
||||
window.trackManager.track("lc_on_attribute_add");
|
||||
this.localAttributes.push({ key: "", value: "" });
|
||||
},
|
||||
save() {
|
||||
window.trackManager.track("lc_on_attribute_save");
|
||||
|
||||
try {
|
||||
|
||||
let resultList = [];
|
||||
if (!this.editMode) {
|
||||
resultList = this.localAttributes.filter((item) => {
|
||||
return !!item.key;
|
||||
});
|
||||
} else {
|
||||
const attributes = this.textAttributes.split('\n');
|
||||
resultList = attributes.map(item => {
|
||||
const [key, value] = item.split(": ");
|
||||
return {
|
||||
key, value
|
||||
}
|
||||
})
|
||||
this.localAttributes = resultList;
|
||||
}
|
||||
|
||||
this.$emit("save", { resultList, lc_id: this.rawInfoID });
|
||||
|
||||
this.$notify({
|
||||
title: "提示",
|
||||
message: '代码已更新',
|
||||
position: 'bottom-right',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
this.$message.error(error);
|
||||
}
|
||||
},
|
||||
remove() {
|
||||
window.trackManager.track("lc_on_attribute_remove");
|
||||
this.$emit("remove", { lc_id: this.rawInfoID });
|
||||
},
|
||||
deleteItem(index) {
|
||||
window.trackManager.track("lc_on_element_delete");
|
||||
this.localAttributes.splice(index, 1);
|
||||
},
|
||||
copyBro() {
|
||||
copyBroCode(this.__rawVueInfo__);
|
||||
this.$emit('codeRefresh');
|
||||
},
|
||||
onShow() {
|
||||
// 这种方式适用于常规模式下的初始化,因为这个实例初始化后不会被销毁,一直常驻内存。但又不能受到其它实例销毁时的影响,所以需要明确的再次初始化。
|
||||
this.initShortcut();
|
||||
},
|
||||
onHide() {
|
||||
this.destroyShortcut();
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
componentName() {
|
||||
return this.__rawVueInfo__ ? getRawComponentKey(this.__rawVueInfo__) : '';
|
||||
},
|
||||
rawInfoID() {
|
||||
return this.__rawVueInfo__ ? getRawComponentContent(this.__rawVueInfo__).lc_id : '';
|
||||
},
|
||||
enableBroButton() {
|
||||
const checkResult = brotherEleEnum().find(item => {
|
||||
return item.name == this.componentName;
|
||||
});
|
||||
return checkResult && checkResult.length != 0;
|
||||
},
|
||||
attributeList() {
|
||||
const result = [];
|
||||
const vueRawInfo = this.__rawVueInfo__;
|
||||
if (vueRawInfo) {
|
||||
const object = vueRawInfo[getRawComponentKey(vueRawInfo)];
|
||||
|
||||
for (const key in object) {
|
||||
if (object.hasOwnProperty(key)) {
|
||||
const element = object[key];
|
||||
if (typeof element !== "object" && key != 'lc-mark' && key != 'lc_id') { // 这两个是保留字段,不对外提供使用
|
||||
result.push({ key: key, value: element });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
attributeKeys() {
|
||||
return this.localAttributes.map(item => item.key)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
attributeList: {
|
||||
handler: function () {
|
||||
this.localAttributes = this.attributeList;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
localAttributes(newValue) {
|
||||
if (newValue.length === 0) {
|
||||
newValue.push({ key: "", value: "" });
|
||||
}
|
||||
|
||||
this.textAttributes = newValue.map(item => `${item.key}: ${item.value}`).join('\n')
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 10px;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.half-width {
|
||||
width: 0%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: inline-block !important;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quick-add-root {
|
||||
padding: 5px 10px;
|
||||
border: 1px dashed #c6c6c6;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.split {
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.el-icon-plus,
|
||||
.el-icon-minus {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.shortcut-tip {
|
||||
font-size: 12px;
|
||||
color: grey;
|
||||
padding: 2px;
|
||||
border-bottom: grey solid 1px;
|
||||
}
|
||||
</style>
|
120
src/components/Code.vue
Normal file
120
src/components/Code.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<el-dialog title="代码预览" :visible.sync="codeDialogVisible" width="70%" top="10vh" :before-close="handleClose"
|
||||
:center=true>
|
||||
<pre style="max-height: 60vh;">
|
||||
<code v-html="formatCode"></code>
|
||||
</pre>
|
||||
<div>
|
||||
<center style="color: #666; font-size: 12px;">使用代码前请确认相应的组件库已集成至项目</center>
|
||||
</div>
|
||||
<span slot="footer">
|
||||
|
||||
<el-tooltip effect="dark" content="拷贝" placement="left">
|
||||
<img class="round-icon" :src="iconCopy" alt="" @click="copyCheck">
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="dark" content="下载" placement="right">
|
||||
<img class="round-icon" :src="iconDownload" alt="" @click="download">
|
||||
</el-tooltip>
|
||||
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './prism.css'
|
||||
import Prism from "prismjs";
|
||||
import prettier from "prettier/standalone";
|
||||
import parserHtml from "prettier/parser-html";
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
export default {
|
||||
props: ['rawCode', 'codeDialogVisible'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
// 在此自动生成
|
||||
|
||||
iconCopy: require("@/assets/icon/copy-outline.svg"),
|
||||
iconDownload: require("@/assets/icon/code-download-outline.svg"),
|
||||
};
|
||||
},
|
||||
beforeCreate() { },
|
||||
created() { },
|
||||
beforeMount() { },
|
||||
mounted() { },
|
||||
beforeUpdate() { },
|
||||
updated() { },
|
||||
destoryed() { },
|
||||
methods: {
|
||||
// 在此自动生成
|
||||
request() {
|
||||
// 网络请求,可选
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit("update:codeDialogVisible", false);
|
||||
},
|
||||
copyCheck() {
|
||||
this.copy();
|
||||
},
|
||||
copy() {
|
||||
if (copy(this.prettyCode)) {
|
||||
this.$message.success("代码已复制到剪贴板");
|
||||
} else {
|
||||
this.$message.error("代码复制有点问题?");
|
||||
}
|
||||
},
|
||||
download() {
|
||||
window.trackManager.track("lc_on_code_download");
|
||||
let blob = new Blob([this.prettyCode], {
|
||||
type: "text/plain;charset=utf-8",
|
||||
});
|
||||
saveAs(blob, "VueComponent.vue");
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
codeDialogVisible(newValue) {
|
||||
if (newValue) {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
prettyCode() {
|
||||
return prettier.format(this.rawCode, {
|
||||
parser: "html",
|
||||
plugins: [parserHtml],
|
||||
vueIndentScriptAndStyle: true,
|
||||
});
|
||||
},
|
||||
|
||||
formatCode() {
|
||||
// 代码格式化工具需要赓续为支持Vue的,当前prettier对js代码不够友好
|
||||
return Prism.highlight(this.prettyCode, Prism.languages.markup, "html");
|
||||
}
|
||||
},
|
||||
fillter: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 在此自动生成 */
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 0 30px !important;
|
||||
}
|
||||
|
||||
.round-icon {
|
||||
background: #4dba87;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
padding: 10px;
|
||||
margin-left: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
125
src/components/CodePreviewWrap.vue
Normal file
125
src/components/CodePreviewWrap.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<preview :value="preview" class="panel"></preview>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Preview from '@/components/Preview';
|
||||
import { parseComponent } from 'vue-template-compiler/browser';
|
||||
import getImports from '@/utils/get-imports';
|
||||
import getPkgs from '@/utils/get-pkgs';
|
||||
import isAbsouteUrl from 'is-absolute-url';
|
||||
import * as params from '@/utils/params';
|
||||
|
||||
export default {
|
||||
props: ['code'],
|
||||
|
||||
components: {
|
||||
Preview
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
preview: '',
|
||||
}),
|
||||
|
||||
watch: {
|
||||
code: {
|
||||
handler: function (newValue) {
|
||||
this.compile(newValue);
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async compile(code) {
|
||||
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
const imports = [];
|
||||
const { template, script, styles, customBlocks } = parseComponent(code);
|
||||
let config;
|
||||
|
||||
if ((config = customBlocks.find(n => n.type === 'config'))) {
|
||||
params.clear();
|
||||
params.parse(config.content);
|
||||
}
|
||||
|
||||
let compiled;
|
||||
const pkgs = [];
|
||||
let scriptContent = 'exports = { default: {} }';
|
||||
|
||||
if (script) {
|
||||
try {
|
||||
compiled = window.Babel.transform(script.content, {
|
||||
presets: ['es2015', 'es2016', 'es2017', 'stage-0'],
|
||||
plugins: [[getImports, { imports }]]
|
||||
}).code;
|
||||
} catch (e) {
|
||||
this.preview = `<pre style="color: red">${e.message}</pre>`;
|
||||
return;
|
||||
}
|
||||
scriptContent = await getPkgs(compiled, imports, pkgs);
|
||||
}
|
||||
|
||||
const heads = this.genHeads();
|
||||
const scripts = [];
|
||||
|
||||
pkgs.forEach(pkg => {
|
||||
scripts.push(
|
||||
`<script src=//packd.now.sh/${pkg.module}${pkg.path}?name=${pkg.name
|
||||
}><\/script>`
|
||||
);
|
||||
});
|
||||
|
||||
styles.forEach(style => {
|
||||
heads.push(`<style>${style.content}</style>`);
|
||||
});
|
||||
|
||||
scripts.push(`
|
||||
<script>
|
||||
var exports = {};
|
||||
${scriptContent}
|
||||
var component = exports.default;
|
||||
component.template = component.template || ${JSON.stringify(
|
||||
template.content
|
||||
)}
|
||||
|
||||
new Vue(component).$mount('#app')
|
||||
<\/script>`);
|
||||
|
||||
this.preview = {
|
||||
head: heads.join('\n'),
|
||||
body: '<div lc_id="app"></div>' + scripts.join('\n')
|
||||
};
|
||||
},
|
||||
|
||||
genHeads() {
|
||||
let heads = [];
|
||||
|
||||
const { pkgs, css } = params.get();
|
||||
|
||||
return [].concat(
|
||||
[]
|
||||
.concat(pkgs)
|
||||
.map(
|
||||
pkg =>
|
||||
`<script src="${isAbsouteUrl(pkg) ? '' : prefix}${pkg}"><\/script>`
|
||||
),
|
||||
css.map(
|
||||
item =>
|
||||
`<link rel=stylesheet href="${isAbsouteUrl(item) ? '' : prefix
|
||||
}${item}">`
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style src="modern-normalize"></style>
|
||||
<style scoped>
|
||||
.panel {
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
160
src/components/CodeStructure.vue
Normal file
160
src/components/CodeStructure.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<el-drawer :visible.sync="drawer" :with-header="false" size="70%" direction="btt">
|
||||
<div class="container">
|
||||
|
||||
<center>组件结构检视图
|
||||
<br>
|
||||
<span style="font-size:12px;">Components
|
||||
Structure</span>
|
||||
</center>
|
||||
|
||||
<el-row :gutter="20" style="height:0px;flex-grow:1;">
|
||||
<el-col :span="16" style="height: 100%;">
|
||||
<div style="overflow: scroll;height:100%; margin: 0 20px;padding: 10px;">
|
||||
<v-tree :radio="true" :canDeleteRoot="false" :data='treeData' :draggable='false' :halfcheck='false'
|
||||
:multiple="false" @node-click="onNodeClick" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<attribute-input ref="attributeInput" :enableRemoveButton="true" v-if="currentEditRawInfo && drawer"
|
||||
@save="onSaveAttr" shortcutInitMode="auto" @remove="onRemove" @codeRefresh="codeRefresh"
|
||||
:__rawVueInfo__="currentEditRawInfo">
|
||||
</attribute-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "./halower-tree.min.css";
|
||||
import { VTree } from '@/libs/v2-tree'
|
||||
import { isObject } from "@/utils/common";
|
||||
|
||||
export default {
|
||||
props: ['visible'],
|
||||
components: {
|
||||
VTree,
|
||||
AttributeInput: resolve => { require(["./AttributeInput"], resolve) },
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// 在此自动生成
|
||||
treeData: [],
|
||||
currentEditRawInfo: null
|
||||
};
|
||||
},
|
||||
beforeCreate() { },
|
||||
created() { },
|
||||
beforeMount() { },
|
||||
mounted() { },
|
||||
beforeUpdate() { },
|
||||
updated() { },
|
||||
destoryed() { },
|
||||
methods: {
|
||||
// 在此自动生成
|
||||
request() {
|
||||
// 网络请求,可选
|
||||
},
|
||||
codeRefresh() {
|
||||
this.$emit('codeRefresh');
|
||||
},
|
||||
|
||||
// 获取这个节点的key
|
||||
getRawComponentKey(__rawVueInfo__) {
|
||||
return Object.keys(__rawVueInfo__)[0];
|
||||
},
|
||||
|
||||
convertStructure(rawInfo) {
|
||||
const title = this.getRawComponentKey(rawInfo);
|
||||
const object = rawInfo[title];
|
||||
const children = [];
|
||||
if (isObject(object)) {
|
||||
for (const key in object) {
|
||||
if (object.hasOwnProperty(key)) {
|
||||
if (key === '__children') {
|
||||
const element = object[key];
|
||||
|
||||
element.forEach(item => {
|
||||
const temp = this.convertStructure(item);
|
||||
temp && children.push(temp);
|
||||
})
|
||||
} else if (isObject(object[key])) {
|
||||
// 组成一个新的结构,适配只有一个子节点的数据结构
|
||||
const __obj = {};
|
||||
__obj[key] = object[key];
|
||||
const child = this.convertStructure(__obj);
|
||||
child && children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: title,
|
||||
expanded: true,
|
||||
children: children,
|
||||
rawInfo: rawInfo,
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
onNodeClick(nodeInfo) {
|
||||
this.currentEditRawInfo = nodeInfo.rawInfo;
|
||||
},
|
||||
|
||||
onRemove({ lc_id }) {
|
||||
this.$emit("remove", { lc_id });
|
||||
// 为了降低复杂性,这里先不做删除失败的处理
|
||||
this.currentEditRawInfo = null;
|
||||
},
|
||||
onSaveAttr(resultList) {
|
||||
this.$emit("save", resultList);
|
||||
},
|
||||
|
||||
updateCode(codeRawInfo) {
|
||||
this.treeData = [this.convertStructure(codeRawInfo)];
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
canInitShortcut(newValue) {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
drawer: {
|
||||
get() {
|
||||
return this.visible;
|
||||
},
|
||||
set() {
|
||||
this.$emit('update:visible', false);
|
||||
}
|
||||
},
|
||||
canInitShortcut() {
|
||||
return this.currentEditRawInfo !== null && this.drawer;
|
||||
}
|
||||
},
|
||||
fillter: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 在此自动生成 */
|
||||
|
||||
center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
::v-deep .el-drawer__body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
43
src/components/Preview.vue
Normal file
43
src/components/Preview.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div ref="container" id="container">
|
||||
<div ref="iframe"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import createIframe from '@/utils/iframe';
|
||||
|
||||
const sandboxAttributes = [
|
||||
'allow-modals',
|
||||
'allow-forms',
|
||||
'allow-pointer-lock',
|
||||
'allow-popups',
|
||||
'allow-same-origin',
|
||||
'allow-scripts'
|
||||
];
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
|
||||
mounted() {
|
||||
this.iframe = createIframe({
|
||||
container: this.$refs.container,
|
||||
el: this.$refs.iframe,
|
||||
sandboxAttributes
|
||||
});
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.iframe.setHTML(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
298
src/components/RawComponents.vue
Normal file
298
src/components/RawComponents.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template lc_id="OpUzJauqXb">
|
||||
<div lc_id="wL/ZpJzwjh" class="container">
|
||||
|
||||
<nav style="display:flex;">
|
||||
<div :index="index + ''" v-for="(item, index) in iconArray" @click="onSelectElement(index)" :key="item.icon"
|
||||
:class="{'active':currentIndex === index}" class="main-icon-container">
|
||||
<img v-if="item.enable" :src="item.icon" class="icon">
|
||||
<el-tooltip v-else class="item" effect="dark" content="暂未开放,敬请期待" placement="right">
|
||||
<img :src="item.icon" class="icon" style="width: 34px;height: 34px;">
|
||||
</el-tooltip>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="bottom-toolbar">
|
||||
<div class="main-title">
|
||||
Low Code Generator
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="el-dropdown-link">
|
||||
<i ref="help" class="el-icon-question" style="font-size:22px;color:#4dba87;"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item icon="el-icon-circle-check">基础组件数: {{ componentUnitNum }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item icon="el-icon-document" command="help">说明文档</el-dropdown-item>
|
||||
<el-dropdown-item icon="el-icon-chat-line-round" command="chat">在线沟通</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav v-if="currentSelectBrand.titleArray && currentSelectBrand.titleArray.length > 0">
|
||||
<center style="margin-bottom:10px;">
|
||||
<div style="padding:5px;font-size:12px;color:grey;">快速查找需要的</div>
|
||||
<el-autocomplete class="inline-input" v-model="componentSearch" :fetch-suggestions="querySearch" size="mini"
|
||||
placeholder="请输入..." @select="handleSelect"></el-autocomplete>
|
||||
</center>
|
||||
<div class="dismiss-scroll">
|
||||
<div v-for="(item, index) in currentSelectBrand.titleArray" :key="item.title" class="second-nav"
|
||||
:class="{'active':currentSelectBrand.selectIndex === index}" @click="selectSubnav(currentSelectBrand, index)">
|
||||
<div style="weight: 600; font-size: 16px;">{{item.titleChinese}}</div>
|
||||
<div style="font-size: 12px; color: grey;">{{item.titleEnglish}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div style="overflow:scroll;padding:0 10px;">
|
||||
<keep-alive>
|
||||
<component :is="currentSelectBrand.componentName" @mounted='onMouted'></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// import vant from "../rawComponents/vant";
|
||||
// import iview from "../rawComponents/iview";
|
||||
// import quasar from "../rawComponents/quasar";
|
||||
const { generateColor } = require('random-color-generator2');
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeName: "0",
|
||||
componentSearch: '',
|
||||
iconArray: [{
|
||||
icon: require('@/assets/logo/html-n.png'),
|
||||
clickCallback: this.onSelectElement,
|
||||
className: "demonstration-raw",
|
||||
enable: true,
|
||||
componentName: 'raw',
|
||||
titleArray: [],
|
||||
}, {
|
||||
icon: require('@/assets/logo/element-n.png'),
|
||||
clickCallback: this.onSelectElement,
|
||||
className: "demonstration-element",
|
||||
selectIndex: 0,
|
||||
componentName: 'ele',
|
||||
enable: true,
|
||||
titleArray: [],
|
||||
}, {
|
||||
icon: require('@/assets/logo/vant-n.png'),
|
||||
enable: false
|
||||
}, {
|
||||
icon: require('@/assets/logo/iview-n.png'),
|
||||
enable: false
|
||||
}, {
|
||||
icon: require('@/assets/logo/quasar-n.png'),
|
||||
enable: false
|
||||
},],
|
||||
|
||||
currentIndex: 1
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
||||
querySearch(queryString, cb) {
|
||||
const result = queryString ? this.currentSelectBrand.titleArray.filter(item => {
|
||||
return item.titleChinese.indexOf(queryString) >= 0 || (item.titleEnglish && item.titleEnglish.toLowerCase().indexOf(queryString.toLowerCase()) >= 0)
|
||||
}) : this.currentSelectBrand.titleArray;
|
||||
cb(result.map(item => {
|
||||
return {
|
||||
value: item.titleChinese + ' ' + item.titleEnglish,
|
||||
element: item.element
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
scrollTo(item) {
|
||||
item.element.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start"
|
||||
});
|
||||
},
|
||||
|
||||
handleSelect(item) {
|
||||
this.scrollTo(item);
|
||||
},
|
||||
|
||||
handleCommand(command) {
|
||||
if (command === 'help') {
|
||||
window.open('/doc')
|
||||
} else if (command === 'chat') {
|
||||
window.open('https://gitter.im/low_code_generator/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link')
|
||||
}
|
||||
},
|
||||
|
||||
onSelectElement(index) {
|
||||
if (this.iconArray[index].enable) {
|
||||
this.currentIndex = index;
|
||||
}
|
||||
},
|
||||
|
||||
onMouted() {
|
||||
// 这里目前只支持ele,所以只写了1
|
||||
this.initOnly(this.iconArray[1]);
|
||||
},
|
||||
|
||||
selectSubnav(obj, index) {
|
||||
obj.selectIndex = index;
|
||||
this.scrollTo(obj.titleArray[index]);
|
||||
},
|
||||
|
||||
init() {
|
||||
this.initOnly(this.iconArray[0]);
|
||||
},
|
||||
|
||||
initOnly(mountedObject) {
|
||||
const titles = document.getElementsByClassName(mountedObject.className);
|
||||
|
||||
if (titles.length > 1) {
|
||||
for (let i = 0; i < titles.length; i++) {
|
||||
const element = titles[i];
|
||||
const arr = element.textContent.split(' ');
|
||||
mountedObject.titleArray.push({
|
||||
titleChinese: arr.length === 2 ? arr[1] : arr[0],
|
||||
titleEnglish: arr.length === 1 ? null : arr[0],
|
||||
element: element
|
||||
})
|
||||
}
|
||||
} else if (titles.length === 1) {
|
||||
mountedObject.onlyTitle = {
|
||||
element: titles[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
surprise() {
|
||||
const that = this;
|
||||
function color() {
|
||||
that.$refs.help.style = `color:${that.colorPointer.next().value};font-size: 24px;`;
|
||||
window.requestAnimationFrame(color);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(color);
|
||||
}
|
||||
},
|
||||
|
||||
created() { this.colorPointer = generateColor(true, 2); },
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
// this.surprise();
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentSelectBrand() {
|
||||
return this.iconArray[this.currentIndex];
|
||||
},
|
||||
componentUnitNum() {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentIndex() {
|
||||
// 对没有二级菜单的选项来说
|
||||
if (this.currentSelectBrand.onlyTitle) {
|
||||
this.scrollTo(this.currentSelectBrand.onlyTitle);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
raw: () => import("../rawComponents/raw"),
|
||||
// vant,
|
||||
// iview,
|
||||
// quasar,
|
||||
ele: () => import("../rawComponents/element"),
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
nav {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.second-nav {
|
||||
padding: 10px 15px;
|
||||
width: 130px;
|
||||
&:hover {
|
||||
background-color: #ecf5ff;
|
||||
border-radius: 5px;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.main-icon-container {
|
||||
padding: 10px;
|
||||
line-height: 0;
|
||||
|
||||
&:hover {
|
||||
background: rgb(236, 245, 255);
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
.active {
|
||||
border-right: 3px solid rgb(19, 206, 102);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
::v-deep .el-submenu__title {
|
||||
padding: 0 15px !important;
|
||||
}
|
||||
|
||||
.dismiss-scroll {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
/*隐藏滚轮*/
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #4dba87;
|
||||
transform: rotate(-90deg);
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
bottom: 11px;
|
||||
left: 0;
|
||||
transform-origin: -2% 10%;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.bottom-toolbar {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
padding: 20px 0;
|
||||
}
|
||||
</style>
|
2
src/components/halower-tree.min.css
vendored
Normal file
2
src/components/halower-tree.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
125
src/components/prism.css
Normal file
125
src/components/prism.css
Normal file
@@ -0,0 +1,125 @@
|
||||
/* PrismJS 1.20.0
|
||||
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* okaidia theme for JavaScript, CSS and HTML
|
||||
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
|
||||
* @author ocodia
|
||||
*/
|
||||
|
||||
code,
|
||||
pre {
|
||||
color: #f8f8f2;
|
||||
background: none;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code,
|
||||
pre {
|
||||
background: #272822;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code {
|
||||
padding: 0.1em;
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.token.boolean,
|
||||
.token.number {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
Reference in New Issue
Block a user