mirror of
https://github.com/sahadev/vue-component-creater-ui.git
synced 2025-06-06 13:04:05 +08:00
483 lines
14 KiB
JavaScript
483 lines
14 KiB
JavaScript
/**
|
||
* 这个文件主要用于将结构渲染出来,并提供一些辅助的能力
|
||
*
|
||
* 这个过程的核心可以简化为:
|
||
* 结构 -> vue代码 -> html
|
||
*
|
||
*/
|
||
import { parseComponent } from 'vue-template-compiler/browser';
|
||
import { merge, insertPresetAttribute, getSplitTag, replaceRowID, updateLinkTree, findCodeElemNode, findRawVueInfo, removeAllID } from "@/utils/forCode";
|
||
import { getRawComponentContent, getRawComponentKey, isObject } from '@/utils/common';
|
||
import { createNewCodeGenerator } from "@/libs/code-generator-factory";
|
||
import EventEmitter from 'eventemitter3'
|
||
import { cloneDeep } from 'lodash-es';
|
||
|
||
/**
|
||
* 主控制面板辅助类,用于代码的生成与绘制
|
||
*/
|
||
export class MainPanelProvider {
|
||
|
||
constructor() {
|
||
// 开启编辑模式
|
||
this.editMode = true;
|
||
this.eventEmitter = new EventEmitter();
|
||
this.currentPointPositionAfterIndex = -1;
|
||
|
||
// 存储所有的渲染记录, 保存副本
|
||
this.renderStack = [];
|
||
this.redoStack = [];
|
||
|
||
this.externalJS = {};
|
||
}
|
||
|
||
/**
|
||
* 对内渲染
|
||
* @param {*} rawDataStructure
|
||
*/
|
||
_render(rawDataStructure) {
|
||
this._rawDataStructure = rawDataStructure;
|
||
// 对外只提供副本,防止外面污染内部
|
||
const codeStructureCopy = cloneDeep(rawDataStructure);
|
||
this.eventEmitter.emit('codeStructureUpdated', codeStructureCopy);
|
||
|
||
this.initCodeGenerator();
|
||
|
||
console.groupCollapsed('---> for code generator warn <---');
|
||
|
||
const readyForMoutedElement = this.createMountedElement();
|
||
|
||
// 生成原始代码
|
||
let code = this.codeGenerator.outputVueCodeWithJsonObj(rawDataStructure);
|
||
|
||
// 将xxx: () => {} 转换为xxx(){}
|
||
code = code.replace(/:\s*\(([\w\s]*)\)\s*=>/g,"\($1\)");
|
||
|
||
// 生成展示代码
|
||
let codeForShow = code.replace(/\s{1}lc_id=".+"/g, '');
|
||
codeForShow = codeForShow.replace(/\s{1}lc-mark/g, "");
|
||
this.eventEmitter.emit("codeCreated", codeForShow);
|
||
|
||
console.groupEnd();
|
||
|
||
const { template, script, styles, customBlocks } = parseComponent(code);
|
||
|
||
let newScript = script.content.replace(/\s*export default\s*/, "")
|
||
|
||
const componentOptions = (new Function(`return ${newScript}`))();
|
||
const render = compile(template.content);
|
||
|
||
componentOptions.render = function () {
|
||
const rootVNode = render.apply(this, arguments);
|
||
return rootVNode;
|
||
};
|
||
// componentOptions.staticRenderFns = render.staticRenderFns;
|
||
|
||
// 渲染当前代码
|
||
createBaseApp(componentOptions).mount(readyForMoutedElement);
|
||
|
||
// 拍平数据结构
|
||
this.editMode && this.flatDataStructure(rawDataStructure);
|
||
|
||
// 开启编辑模式
|
||
this.editMode && this.enableEditMode();
|
||
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 对外渲染函数:将一个指定的数据结构渲染出来,并会保存这次的渲染记录
|
||
* @param {*} rawDataStructure
|
||
*/
|
||
render(rawDataStructure) {
|
||
this._render(rawDataStructure);
|
||
return this;
|
||
}
|
||
|
||
setEditMode(editMode) {
|
||
this.editMode = editMode;
|
||
this.reRender();
|
||
}
|
||
|
||
/**
|
||
* 初始化代码编译
|
||
*/
|
||
initCodeGenerator() {
|
||
this.codeGenerator = createNewCodeGenerator();
|
||
this.codeGenerator.setExternalJS(this.externalJS);
|
||
}
|
||
|
||
getControlPanelRoot() {
|
||
return document.getElementById('render-control-panel');
|
||
}
|
||
|
||
saveJSCode(code) {
|
||
this.externalJS = code;
|
||
this.codeGenerator.setExternalJS(code);
|
||
this.reRender();
|
||
}
|
||
|
||
/**
|
||
* 生成一个新的待挂载容器
|
||
*/
|
||
createMountedElement() {
|
||
const renderControlPanel = this.getControlPanelRoot();
|
||
const child = document.createElement('div');
|
||
|
||
// 清空子节点
|
||
while (renderControlPanel.firstChild) {
|
||
renderControlPanel.removeChild(renderControlPanel.firstChild)
|
||
}
|
||
|
||
renderControlPanel.appendChild(child);
|
||
|
||
return child;
|
||
}
|
||
|
||
/**
|
||
* 初始化组件的一些事件
|
||
*/
|
||
initComonentsEvent() {
|
||
const renderControlPanel = this.getControlPanelRoot();
|
||
const elements = renderControlPanel.querySelectorAll("[lc_id]");
|
||
elements.forEach(element => {
|
||
element.addEventListener("click", event => {
|
||
event.stopPropagation();
|
||
this.markElement(element);
|
||
})
|
||
})
|
||
|
||
this.initDropEvent();
|
||
}
|
||
|
||
/**
|
||
* 高亮当前编辑元素等处理,并将当前编辑的元素指针指向这个节点
|
||
*/
|
||
markElement(realNode) {
|
||
// 处理之前的状态
|
||
if (this.currentEditElement) {
|
||
this.currentEditElement.classList.remove("mark-element");
|
||
}
|
||
|
||
if (realNode) {
|
||
this.currentEditElement = realNode;
|
||
|
||
const rawVueInfo = this.markElementInner(realNode);
|
||
|
||
// 对外只提供副本,防止外面污染内部
|
||
const codeRawInfoCopy = cloneDeep(rawVueInfo);
|
||
this.eventEmitter.emit("selected", codeRawInfoCopy);
|
||
} else {
|
||
this.eventEmitter.emit("selected", null);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 这个方法会返回节点所对应的代码结构
|
||
* @param {*} realNode
|
||
* @returns
|
||
*/
|
||
markElementInner(realNode) {
|
||
realNode.classList.add("mark-element");
|
||
const rawVueInfo = findRawVueInfo(realNode);
|
||
// 显示当前组件的名称
|
||
realNode.setAttribute('lc-component-name', getRawComponentKey(rawVueInfo));
|
||
return rawVueInfo;
|
||
}
|
||
|
||
/**
|
||
* 清除选中
|
||
*/
|
||
clearElementSelect() {
|
||
this.markElement(null);
|
||
}
|
||
|
||
/**
|
||
* 设置新组建要放入的位置与对象
|
||
* @param {*} index
|
||
*/
|
||
setDropInfo(dropInfo) {
|
||
this.currentPointDropInfo = dropInfo;
|
||
}
|
||
|
||
/**
|
||
* 初始化拖拽事件
|
||
*/
|
||
initDropEvent() {
|
||
const renderControlPanel = this.getControlPanelRoot();
|
||
|
||
renderControlPanel.addEventListener("dragenter", (event) => {
|
||
event.preventDefault();
|
||
});
|
||
|
||
renderControlPanel.addEventListener("dragleave", (event) => {
|
||
event.preventDefault();
|
||
});
|
||
|
||
renderControlPanel.addEventListener("dragover", (event) => {
|
||
// Specifying Drop Targets
|
||
event.preventDefault();
|
||
});
|
||
|
||
// 拖入预览容器释放时的处理
|
||
renderControlPanel.addEventListener("drop", (event) => {
|
||
if (!this.currentPointDropInfo.target) {
|
||
return;
|
||
}
|
||
|
||
const data = event.dataTransfer.getData("text/plain");
|
||
const [, , , , rawInfo] = data.split(getSplitTag());
|
||
|
||
const newDropObj = JSON.parse(rawInfo);
|
||
|
||
// 插入预设属性
|
||
insertPresetAttribute(newDropObj);
|
||
|
||
// 使新拖入的代码与原来的做脱离
|
||
replaceRowID(newDropObj, '');
|
||
|
||
// 更新到一个Map上面,维持引用,由于render中统一做了处理,所以这段代码是可以删除的 2021年02月04日11:59:10
|
||
updateLinkTree(newDropObj);
|
||
|
||
// 更新代码结构关系
|
||
const codeTargetElement = findCodeElemNode(this.currentPointDropInfo.target);
|
||
if (codeTargetElement) {
|
||
let temp = findRawVueInfo(codeTargetElement);
|
||
|
||
this.backup();
|
||
// 合并
|
||
merge(getRawComponentContent(temp), newDropObj, this.currentPointDropInfo.index, () => {
|
||
this.eventEmitter.emit('onMerged');
|
||
});
|
||
|
||
this.render(this._rawDataStructure);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 开启编辑模式,并禁用默认的事件,添加编辑事件
|
||
*/
|
||
enableEditMode() {
|
||
const renderControlPanel = this.getControlPanelRoot();
|
||
// 加一个延迟的作用是:给el-table这种绘制需要时间的组件留出充足的时间,否则会造成el-table渲染不到页面上
|
||
setTimeout(() => {
|
||
// 这种方式可以禁用原节点所有的事件
|
||
const elClone = renderControlPanel.cloneNode(true);
|
||
renderControlPanel.parentNode.replaceChild(elClone, renderControlPanel);
|
||
this.eventEmitter.emit("mounted", elClone);
|
||
// 事件初始化
|
||
this.initComonentsEvent();
|
||
}, 500);
|
||
}
|
||
|
||
/**
|
||
* 将数据拍平,以id: data 的方式联结
|
||
* @param {*} rawDataStructure
|
||
*/
|
||
flatDataStructure(rawDataStructure) {
|
||
updateLinkTree(rawDataStructure);
|
||
}
|
||
|
||
/**
|
||
* 选中某个一个元素时
|
||
*/
|
||
onSelectElement(callback) {
|
||
this.subscribeEventListener("selected", callback);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 当根节点挂载后
|
||
* @param {*} callback
|
||
*/
|
||
onRootElementMounted(callback) {
|
||
this.subscribeEventListener("mounted", callback);
|
||
return this;
|
||
}
|
||
|
||
|
||
/**
|
||
* 当Vue代码生成后
|
||
* @param {*} callback
|
||
*/
|
||
onCodeCreated(callback) {
|
||
this.subscribeEventListener("codeCreated", callback);
|
||
return this;
|
||
}
|
||
|
||
|
||
/**
|
||
* 当代码结构更新后
|
||
* @param {*} callback
|
||
*/
|
||
onCodeStructureUpdated(callback) {
|
||
this.subscribeEventListener("codeStructureUpdated", callback);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 当代码合并完成后
|
||
* @param {*} callback
|
||
*/
|
||
onMerged(callback) {
|
||
this.subscribeEventListener("onMerged", callback);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 当节点删除后
|
||
* @param {*} callback
|
||
*/
|
||
onNodeDeleted(callback) {
|
||
this.subscribeEventListener("onNodeDeleted", callback);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 重新渲染
|
||
*/
|
||
reRender() {
|
||
this._render(this._rawDataStructure);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 保存属性
|
||
* @param {*} resultList
|
||
* @param {*} rawInfo
|
||
*/
|
||
saveAttribute(resultList, lc_id) {
|
||
const param = resultList;
|
||
const object = getRawComponentContent(window.tree[lc_id]);
|
||
|
||
this.backup();
|
||
// 这里是为了满足当有属性删除的情况,保留保留属性:__children lc-mark lc_id
|
||
for (const key in object) {
|
||
if (object.hasOwnProperty(key) &&
|
||
key != "__children" && key != "lc-mark" && key != "lc_id" &&
|
||
!isObject(object[key]/** 值为Object的情况目前已经没有了2021年02月04日11:56:28 */)) {
|
||
delete object[key];
|
||
}
|
||
}
|
||
|
||
param.forEach((element) => {
|
||
object[element.key] = element.value;
|
||
});
|
||
// 渲染当前的变更
|
||
this.render(this._rawDataStructure);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 移动一个节点到另一个节点
|
||
* @param {*} removeID 被移动的节点
|
||
* @param {*} movePath 移动的路径
|
||
*/
|
||
onLevelChange(removeID, movePath) {
|
||
this.backup();
|
||
|
||
const removedNodeArray = this.remove(removeID, false);
|
||
// 去除根节点路径,以便完全匹配
|
||
movePath.splice(0, 1)
|
||
|
||
let rootNode = this._rawDataStructure;
|
||
let lastIndex = -1;
|
||
for (let i = 0; i < movePath.length - 1; i++) {
|
||
rootNode = getRawComponentContent(rootNode).__children[movePath[i]];
|
||
lastIndex = movePath[i + 1];
|
||
}
|
||
|
||
if (rootNode && removedNodeArray.length > 0) {
|
||
getRawComponentContent(rootNode).__children.splice(lastIndex, 0, removedNodeArray[0]);
|
||
}
|
||
|
||
// 渲染当前的变更
|
||
this.render(this._rawDataStructure);
|
||
}
|
||
|
||
/**
|
||
* 移除一个节点
|
||
* @param {*} lc_id
|
||
*/
|
||
remove(lc_id, backup = true) {
|
||
let removeNodes = null;
|
||
const readyForDeleteElement = document.querySelector(`[lc_id="${lc_id}"]`);
|
||
if (readyForDeleteElement) {
|
||
const parentElementNode = findCodeElemNode(readyForDeleteElement.parentElement);
|
||
|
||
if (parentElementNode) {
|
||
const parentRawInfo = findRawVueInfo(parentElementNode);
|
||
|
||
const attributes = getRawComponentContent(parentRawInfo);
|
||
if (attributes) {
|
||
const childrenArray = attributes.__children;
|
||
|
||
const index = childrenArray.findIndex((item) => {
|
||
return getRawComponentContent(item).lc_id == lc_id;
|
||
});
|
||
|
||
backup && this.backup();
|
||
removeNodes = childrenArray.splice(index, 1);
|
||
this.eventEmitter.emit("onNodeDeleted");
|
||
|
||
// 渲染当前的变更
|
||
this.render(this._rawDataStructure);
|
||
} else {
|
||
console.warn(`没有发现原始代码属性:lc_id: ${lc_id}`);
|
||
}
|
||
} else {
|
||
console.warn(`没有发现lc_id: ${lc_id}所对应的父Dom节点`);
|
||
}
|
||
} else {
|
||
console.warn(`没有发现lc_id: ${lc_id}所对应的Dom节点`);
|
||
}
|
||
|
||
return removeNodes;
|
||
}
|
||
|
||
/**
|
||
* 事件订阅
|
||
* @param {*} event -> mounted:当新的根节点被挂载 | selected:选择了某个节点 | codeCreated:生成了代码 | codeStructureUpdated:代码结构更新
|
||
* @param {*} listener
|
||
*/
|
||
subscribeEventListener(event, listener) {
|
||
this.eventEmitter.on(event, listener);
|
||
}
|
||
|
||
/**
|
||
* 备份当前结构
|
||
*/
|
||
backup() {
|
||
this.renderStack.push(cloneDeep(this._rawDataStructure));
|
||
// 当重新变更结构时,重做栈要置空
|
||
this.redoStack = [];
|
||
}
|
||
|
||
/**
|
||
* 撤销
|
||
*/
|
||
undo() {
|
||
if (this.renderStack.length > 0) {
|
||
// 存储当前的渲染到重做栈中
|
||
this.redoStack.push(cloneDeep(this._rawDataStructure));
|
||
|
||
const readyForRender = this.renderStack.pop();
|
||
this._render(readyForRender);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重做
|
||
*/
|
||
redo() {
|
||
if (this.redoStack.length > 0) {
|
||
this.renderStack.push(cloneDeep(this._rawDataStructure));
|
||
|
||
const readyForRender = this.redoStack.pop();
|
||
this._render(readyForRender);
|
||
}
|
||
}
|
||
}
|
||
|
||
|