1
0
mirror of https://github.com/sahadev/vue-component-creater-ui.git synced 2025-12-18 00:14:30 +08:00
This commit is contained in:
shangbin
2021-04-09 20:06:44 +08:00
commit e87ebafdcb
97 changed files with 33753 additions and 0 deletions

24
src/utils/common.js Normal file
View File

@@ -0,0 +1,24 @@
import isEqual from "lodash/isEqual";
export function getRawComponentKey(__rawVueInfo__) {
return Object.keys(__rawVueInfo__)[0];
}
export function getRawComponentContent(__rawVueInfo__) {
return __rawVueInfo__[getRawComponentKey(__rawVueInfo__)];
}
/**
* 比较两个对象是否完全相等
*/
export function compareTwoObjectIsEqual(o1, o2) {
return isEqual(o1, o2);
}
export function isArray(arr) {
return Object.prototype.toString.apply(arr) === "[object Array]";
}
export function isObject(obj) {
return Object.prototype.toString.apply(obj) === "[object Object]";
}

262
src/utils/forCode.js Normal file
View File

@@ -0,0 +1,262 @@
import { isObject, isArray, getRawComponentKey } from '@/utils/common';
import presetAttribute from "../libs/presetAttribute";
const cryptoRandomString = require("crypto-random-string");
// 将预生成的ID替换否则当有两个组件挂在同一个树上时后一个会将前一个的属性覆盖
export function replaceRowID(codeObj, html) {
// 生成一个唯一的ID使Code和Dom一一对应与原代码脱离关系
let newHtml = html;
function deep(obj) {
if (isObject(obj)) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const element = obj[key];
if (key == "lc_id") {
const oldID = obj[key];
const newID = cryptoRandomString({
length: 10,
type: "base64",
});
newHtml = newHtml.replace(oldID, newID);
obj[key] = newID;
} else if (isObject(element)) {
deep(element);
} else if (isArray(element)) {
element.forEach((item) => deep(item));
}
}
}
}
}
deep(codeObj);
return newHtml;
}
/** 在这里维护一棵以ID为KEY的树 */
export function updateLinkTree(codeObj) {
if (!window.tree) {
window.tree = {};
}
if (!window.treeWithID) {
const innerObj = {};
Object.defineProperty(window, 'treeWithID', {
get: function () {
return innerObj;
},
set: function (newValue) {
innerObj = newValue;
}
})
}
flatCodeObj(codeObj);
}
/**
* 获取这个元素所对应的代码结构
*/
export function findRawVueInfo(element) {
// 这里对于form这样的怪物来说会导致指向错乱的问题 2020年12月07日14:39:30
// if (element.__rawVueInfo__) {
// return element.__rawVueInfo__;
// } else
if (window.tree[element.attributes.lc_id.nodeValue]) {
return window.tree[element.attributes.lc_id.nodeValue];
} else {
return findRawVueInfo(element.parentNode);
}
}
export function flatCodeObj(codeObj) {
function deep(object) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
const element = object[key];
// 如果对__children的子属性遍历时它内部的元素需要指向外层的节点作为父节点
if (
object.hasOwnProperty("__key__") &&
object["__key__"] === "__children" &&
isObject(element)
) {
delete object["__key__"];
}
if (key === "lc_id" && object.hasOwnProperty("__key__")) {
const outerKey = object["__key__"];
const newObj = {
[outerKey]: object
};
// 这个关系也需要链接
newObj.__proto__ = object.__proto__;
delete object.__key__;
window.tree[element] = newObj;
// 这个备份用于生成兄弟组件时获取原始代码所用需要保留ID的信息才可以实现DOM与代码对象的关联
const copy = Object.create(newObj.__proto__);
Object.assign(copy, JSON.parse(JSON.stringify(newObj)));
window.treeWithID[element] = copy;
} else if (key === "__children") {
object.__children.forEach((child) => {
child["__key__"] = key;
deep(child);
});
} else if (isObject(element)) {
element["__key__"] = key;
deep(element);
}
}
}
}
deep(codeObj);
}
// 将所有子节点指向其父节点,父节点指向子节点容易,而子节点找到具体的父节点不容易
export function linkRelationShip(unitRootCodeObj) {
function deep(obj) {
if (isObject(obj)) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const element = obj[key];
if (isObject(element)) {
element.__proto__ = { parentCodeNode: obj };
deep(element);
} else if (isArray(element) && key === "__children") {
element.forEach((item) => {
item.__proto__ = { parentCodeNode: obj };
deep(item);
});
}
}
}
}
}
deep(unitRootCodeObj);
}
// 使生成的代码不携带ID属性
export function removeAllID(codeObj) {
function deep(obj) {
if (isObject(obj)) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const element = obj[key];
if (key == "lc_id" || key == "lc-mark") {
delete obj[key];
} else if (isObject(element)) {
deep(element);
} else if (isArray(element)) {
element.forEach((item) => deep(item));
}
}
}
}
}
deep(codeObj);
}
export function generateRawInfo(target) {
if (target.attributes.lc_id) {
// 获取源代码结构
const rowCode = window.templateSourceMap[target.attributes.lc_id.nodeValue];
return rowCode;
} else if (target.__vue__) {
// 代表这是一个Vue组件组成的元素这里的逻辑渐渐不再使用
const temp = findVueInfo(target.__vue__.$vnode);
return temp;
} else {
// 这是一个普通的元素
const temp = {
[target.localName]: {
__text__: target.innerText,
class: target.className,
}
};
return temp;
}
}
/**
* 通过运行时查找这个VNODE的关键信息
*/
export function findVueInfo(vnode) {
const obj = {};
if (vnode.text) {
obj["__text__"] = vnode.text;
} else if (vnode.componentOptions) {
const nodeBaseInfo = vnode.componentOptions;
if (nodeBaseInfo.tag) {
obj[nodeBaseInfo.tag] = nodeBaseInfo.propsData;
}
if (nodeBaseInfo.children && nodeBaseInfo.children.length > 0) {
for (let index = 0; index < nodeBaseInfo.children.length; index++) {
const child = nodeBaseInfo.children[index];
merge(obj[nodeBaseInfo.tag], findVueInfo(child));
}
}
}
return obj;
}
/**
* 这里需要将o2作为o1的子值 这里使用回调方法而不是用Promise的原因为需要严格保证外部的调用时序
*/
export function merge(o1, o2, currentPointPositionAfterIndex = -1, onFinish = () => { }) {
if (o1 && o2) {
if (!o1["__children"]) {
o1["__children"] = [];
}
// 更新结构关系,将插入到指定的位置
if (currentPointPositionAfterIndex > -1) {
o1["__children"].splice(currentPointPositionAfterIndex, 0, o2);
} else {
o1["__children"].push(o2);
}
onFinish();
// 这里踩了一个坑所有的对象的默认属性__proto__都指向同一个对象会引起意外的问题
o2.__proto__ = { parentCodeNode: o1 };
}
}
// 特殊分隔符
export function getSplitTag() {
return "@#$!";
}
export function insertPresetAttribute(vueInfo) {
const key = getRawComponentKey(vueInfo);
const value = vueInfo[key];
const presetAttr = presetAttribute[key];
if (presetAttr) {
for (const key in presetAttr) {
if (presetAttr.hasOwnProperty(key)) {
// 将原先的属性做新增或者替换操作
const element = presetAttr[key];
value[key] = element;
}
}
}
}
/**
* 寻找实际的可以代表整个复合组件Dom这是个核心方法根据某个元素查找实际的以Vue组件为单位的最小元素
*/
export function findCodeElemNode(element) {
if (element.attributes && element.attributes.lc_id) {
return element;
} else if (element.parentNode) {
return findCodeElemNode(element.parentNode);
} else {
return null;
}
}

18
src/utils/get-imports.js Normal file
View File

@@ -0,0 +1,18 @@
export default function(code, { imports }) {
return {
name: 'get-imports',
visitor: {
ImportDeclaration(path) {
imports.push({
variables: path.node.specifiers.map(spec => ({
local: spec.local.name,
imported: spec.imported ? spec.imported.name : 'default'
})),
module: path.node.source.value
});
path.remove();
}
}
};
}

40
src/utils/get-pkgs.js Normal file
View File

@@ -0,0 +1,40 @@
import parsePackageName from 'parse-package-name';
export default async function(code, imports, scripts) {
const replacements = [];
for (const [index, item] of imports.entries()) {
const moduleName = `__npm_module_${index}`;
const pkg = parsePackageName(item.module);
const version = pkg.version || 'latest';
scripts.push({
path: pkg.path ? `/${pkg.path}` : '',
name: moduleName,
module:
pkg.name === 'vue' && !pkg.path
? `vue@${version}/dist/vue.esm.js`
: `${pkg.name}@${version}`
});
let replacement = '\n';
for (const variable of item.variables) {
if (variable.imported === 'default') {
replacement += `var ${
variable.local
} = ${moduleName}.default || ${moduleName};\n`;
} else {
replacement += `var ${variable.local} = ${moduleName}.${
variable.imported
};\n`;
}
}
if (replacement) {
replacements.push(replacement);
}
}
if (replacements.length > 0) {
code = replacements.join('\n') + code;
}
return code;
}

59
src/utils/iframe.js Normal file
View File

@@ -0,0 +1,59 @@
/**
* From: https://github.com/egoist/codepan/blob/2c22bb3d7a7a4e31fd99fc640d320f7ec24d2951/src/utils/iframe.js
*/
import { Loading } from 'element-ui';
class Iframe {
constructor({ container, el, sandboxAttributes = [] }) {
if (!el) {
throw new Error('Expect "el" to mount iframe to!');
}
this.$container = container;
this.$el = el;
this.sandboxAttributes = sandboxAttributes;
}
setHTML(obj) {
let html;
if (typeof obj === 'string') {
html = obj;
} else {
const { head = '', body = '' } = obj;
html = `<!DOCTYPE html><html><head>${head}</head><body>${body}</body></html>`;
}
// 关闭上一个实例引起的loading
if (this.loadingInstance) {
this.loadingInstance.close();
}
this.loadingInstance = Loading.service({
target: this.$container,
text: '渲染中,请稍后...'
});
const iframe = this.createIframe();
iframe.addEventListener('load', () => {
this.loadingInstance.close();
})
this.$el.parentNode.replaceChild(iframe, this.$el);
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(html);
iframe.contentWindow.document.close();
this.$el = iframe;
}
createIframe() {
const iframe = document.createElement('iframe');
iframe.setAttribute('sandbox', this.sandboxAttributes.join(' '));
iframe.setAttribute('scrolling', 'yes');
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = '0';
return iframe;
}
}
export default (...args) => new Iframe(...args);

View File

@@ -0,0 +1,67 @@
import { generateRawInfo, getSplitTag } from './forCode';
import { getRawComponentKey } from './common';
// 遍历DOM树初始化lc-mark标记的元素
export function deepLCEle(rootElement, onCountIncrease = () => { }) {
// 对dragParent下所有含有lc-mark属性的Element实现可拖拽能力
function deep(ele) {
if (ele.attributes["lc-mark"]) {
// 统计标记组件数量
onCountIncrease();
initElement(ele);
}
if (ele.children.length > 0) {
const length = ele.children.length;
for (let i = 0; i < length; i++) {
deep(ele.children.item(i));
}
}
}
deep(rootElement);
}
// 对组件初始化,使组件可以拖拽
export function initElement(element) {
element.draggable = true;
// 给每个组件添加拖拽事件
element.addEventListener("dragstart", function (event) {
event.dataTransfer.effectAllowed = "copy";
const raw = generateRawInfo(element);
const str = `${element.localName}${getSplitTag()}${element.innerText
}${getSplitTag()}${0}${getSplitTag()}${element.style.cssText
}${getSplitTag()}${JSON.stringify(raw)}`;
event.dataTransfer.setData("text/plain", str);
try {
const tag = getRawComponentKey(raw);
window.trackManager.track("lc_on_start_drag", {
tag,
});
} catch (error) { }
event.stopPropagation();
});
// 处理组件标记
element.addEventListener("mouseenter", (event) => {
const parentClassList = element.parentElement.classList;
if (parentClassList && parentClassList.contains("mark-element-unit")) {
parentClassList.remove("mark-element-unit");
element.isRemoveParentStyle = true;
}
element.classList.add("mark-element-unit");
event.stopPropagation();
});
element.addEventListener("mouseleave", (event) => {
element.classList.remove("mark-element-unit");
if (element.isRemoveParentStyle) {
element.parentElement.classList.add("mark-element-unit");
}
event.stopPropagation();
});
}

177
src/utils/lineHelper.js Normal file
View File

@@ -0,0 +1,177 @@
let currentPostion = null,
currentTarget = null,
preSelectTarget = null; // 记录上一次鼠标所在位置
const TOP = 1,
MIDDLE = 2,
BOTTOM = 3;
import { findCodeElemNode, findRawVueInfo } from "@/utils/forCode";
import { getRawComponentContent } from "@/utils/common";
export function initContainerForLine(targetElement, _currentPointer = () => {}) {
const crossX = document.querySelector(".x");
const currentPointer = (...args) => {
_currentPointer(...args);
};
targetElement.addEventListener("dragover", (event) => {
event.preventDefault();
drawLine(event);
});
targetElement.addEventListener("dragleave", (event) => {
if (event.target === targetElement) {
targetElement.classList.remove("in-element");
crossX.style = "display: none;";
} else {
clearTargetOutline();
}
});
/**
* 获得一个元素在父元素中的索引
* @param {*} element
* @returns
*/
function findElementIndex(element) {
// 根据代码结构查找
const parentElementNode = findCodeElemNode(element.parentElement);
const lc_id = element.getAttribute("lc_id");
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;
});
return index;
}
}
return -1;
}
function clearTargetOutline() {
if (preSelectTarget) {
preSelectTarget.classList.remove("in-element");
}
}
function drawLine(event) {
const realTarget = event.target;
// 2021年03月26日15:56:35 新的逻辑是:只有上下定位辅助线,不再计算左右辅助线
const directionObj = judgeTopOrBottom(realTarget, event.clientX, event.clientY);
const position = getElCoordinate(realTarget);
// 如果鼠标点在目标的上部分则绘制上部分辅助线
if (directionObj.top && targetElement !== realTarget) {
if (currentPostion === TOP && currentTarget === realTarget) {
return;
}
currentPostion = TOP;
currentTarget = realTarget;
clearTargetOutline();
crossX.style = `top:${position.top}px;width:${position.width}px;left:${position.left}px;display:block;`;
currentPointer(realTarget.parentElement, findElementIndex(realTarget));
} else if (directionObj.bottom && targetElement !== realTarget) {
// 如果鼠标点在目标的下部分,则绘制下部分辅助线
if (currentPostion === BOTTOM && currentTarget === realTarget) {
return;
}
currentPostion = BOTTOM;
currentTarget = realTarget;
clearTargetOutline();
crossX.style = `top:${position.bottom}px;width:${position.width}px;left:${position.left}px;display:block;`;
currentPointer(realTarget.parentElement, findElementIndex(realTarget) + 1);
} else {
currentPostion = MIDDLE;
currentTarget = realTarget;
realTarget.classList.add("in-element");
preSelectTarget = realTarget;
crossX.style = `display:none;`;
currentPointer(realTarget, -1);
}
}
}
// 获取一个元素在屏幕上的坐标点
function getElCoordinate(e) {
const rect = e.getBoundingClientRect();
return rect;
}
// 判断一个点是否在该元素内
function judgeEleIsContentPoint(e, x, y) {
const position = getElCoordinate(e);
return x >= position.left && x <= position.right && y >= position.top && y <= position.bottom;
}
/**
* 判断上还是下
* @param {*} e
* @param {*} x
* @param {*} y
* @returns
*/
function judgeTopOrBottom(e, x, y) {
const position = getElCoordinate(e);
const cutDistance = Math.round((position.bottom - position.top) / 3);
return {
top: y < position.top + cutDistance,
middle: y >= position.top + cutDistance && y <= position.top + cutDistance * 2,
bottom: y > position.top + cutDistance * 2,
};
}
// 判断点在元素的方向
function direction(e, x, y) {
const position = getElCoordinate(e);
// 基本方向判定
const direction = {
left: x < position.left ? position.left - x : 0,
right: x > position.right ? x - position.right : 0,
top: y < position.top ? position.top - y : 0,
bottom: y > position.bottom ? y - position.bottom : 0,
};
// 判定方向更靠近哪个
let count = 0;
let directionStrArray = [];
for (const key in direction) {
const element = direction[key];
if (element) {
directionStrArray.push(key);
count++;
}
}
if (count === 2) {
// 进一步判定更靠近哪个方向
const num1 = direction[directionStrArray[0]];
const num2 = direction[directionStrArray[1]];
direction[directionStrArray[0]] = num1 > num2;
direction[directionStrArray[1]] = num2 > num1;
}
direction.position = position;
return direction;
}

55
src/utils/params.js Normal file
View File

@@ -0,0 +1,55 @@
import qs from 'query-string';
import deepmerge from 'deepmerge';
const DEFAULT_PARAMS = {
pkgs: ['https://static.imonkey.xueersi.com/vue-creater-platform/resource/vue.js', 'https://static.imonkey.xueersi.com/vue-creater-platform/resource/element-ui/index.js'],
css: ['https://static.imonkey.xueersi.com/vue-creater-platform/resource/element-ui/index.css'],
};
let params = DEFAULT_PARAMS;
function getArr(str) {
if (Array.isArray(str)) {
return str;
}
if (typeof str === 'string') {
return str.split(',');
}
return [];
}
export function clear() {
params = DEFAULT_PARAMS;
}
export function parse(str) {
try {
merge(JSON.parse(str));
} catch (e) {
console.error('error', e.message);
}
}
export function queryParse(str) {
const query = qs.parse(str);
const pkgs = getArr(query.pkg);
const css = getArr(query.css);
const options = { pkgs, css };
if (query.cdn) {
options.cdn = query.cdn;
}
if (query.vue) {
options.vue = query.vue;
}
merge(options);
}
export function get() {
return deepmerge(DEFAULT_PARAMS, params);
}
export function merge(opts) {
params = deepmerge(params, opts);
}

35
src/utils/store.js Normal file
View File

@@ -0,0 +1,35 @@
import Vue from 'vue';
import { stringify } from 'query-string';
const API = 'https://text.cinwell.xyz';
export async function upload(text) {
Vue.toasted.show('Saving...');
try {
const result = await fetch(API, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: stringify({
text,
hash: 1
})
});
Vue.toasted.clear();
return await result.text();
} catch (e) {
Vue.toasted.clear();
Vue.toasted.show('Failed: ' + e.message, {
type: 'error',
duration: 2000
});
}
}
export function downloadURL(hash) {
return `${API}/${hash}`;
}