@lcap/asl
Version:
NetEase Application Specific Language
1,197 lines • 55.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Element = void 0;
// import stringify = require('json-stringify-safe');
const decorators_1 = require("../decorators");
const __1 = require("..");
const compiler = __importStar(require("vue-template-compiler"));
const json5 = __importStar(require("json5"));
const page_1 = require("../../service/page");
const cacheData_1 = require("../cacheData");
const babelParser = __importStar(require("@babel/parser"));
const lodash_1 = require("lodash");
/**
* 前端页面元素
* @example
* <u-input size="small" v-model="value" @change="onChange"></u-input>
*/
class Element extends __1.Vertex {
/**
* @param source 需要合并的部分参数
*/
constructor(source) {
super();
/**
* 概念类型
*/
this.level = __1.LEVEL_ENUM.element;
/**
* 元素类型
* 去除了 expression 和 text,目前只有一种节点类型
*/
this.type = 0;
/**
* 元素 Id
*/
this.id = undefined;
/**
* 元素标签
*/
this.tag = undefined;
/**
* 元素名称
* 用户可以自定义
* Vue 中的 ref
*/
this.name = undefined;
/**
* 属性列表
* 和原来的 attrsList 不同,注意区分
*/
this.attrList = [];
/**
* 事件列表
*/
this.eventList = [];
/**
* 指令列表
*/
this.directiveList = [];
/**
* 插槽目标
* @example
* 'cell'
*/
this.slotTarget = undefined;
/**
* 插槽 scope 表达式
* @example
* 'scope'
*/
this.slotScope = undefined;
/**
* 静态 class 名
* 不计划支持动态 class
*/
this.staticClass = undefined;
/**
* 静态 style
* 不计划支持动态样式
*/
this.staticStyle = undefined;
/**
* 当前节点路径,只在前端动态计算后使用
*/
this.nodePath = undefined;
/**
* 所在父元素 Id
*/
this.parentId = undefined;
/**
* 所在位置
*/
this._posIndex = undefined;
/**
* 所在父元素
*/
this.parent = undefined;
/**
* 所在父元素 Id
*/
this.viewId = undefined;
/**
* 所在子页面
*/
this.view = undefined;
/**
* 子元素
*/
this.children = [];
source && this.assign(source);
// 为了给生成的节点使用
// !this.id && this.assign({ id: uuidv4() });
// this.assign({ id: 'temp-' + uuidv4() });
}
assign(source) {
['attrList', 'eventList', 'directiveList', 'children'].forEach((key) => {
if (source[key] === null)
delete source[key];
});
super.assign(source);
}
/**
* 添加元素
*/
async create(none, actionOptions) {
if (actionOptions?.actionMode === __1.ACTION_MODE.local) {
this.view && this.view.emit('local-change');
return this;
}
__1.config.defaultApp?.emit('saving');
try {
if (actionOptions?.actionMode !== __1.ACTION_MODE.undoRedo) {
const body = this.toJSON();
body.parentId = this.parent && this.parent.id;
body._posIndex = this.parent && this.parent.children.indexOf(this);
__1.utils.logger.debug('添加元素', body);
const result = await page_1.elementService.create({
headers: {
appId: __1.config.defaultApp?.id,
operationAction: 'Element.create',
operationDesc: `添加组件"${this.getElementTitle()}"`,
},
body,
});
// Nuims createResource
// Element 对应资源增删在 undo/redo 情况下可能存在权限丢失的情况
// 暂时只增不减
// new Nuims({
// domainName: this.view.page.service.app.name,
// element: this,
// }).createResource();
this.deepPick(result, ['id', 'parentId', 'elementId']);
// attr,directive,event的id,exprssion需要合并
this.attrList.forEach((attr, index) => {
attr.deepPick(result.attrList[index], ['id', 'expression']);
});
this.directiveList.forEach((directive, index) => {
directive.deepPick(result.directiveList[index], ['id', 'expression']);
});
this.eventList.forEach((event, index) => {
event.deepPick(result.eventList[index], ['id']);
});
}
this.view && this.view.emit('change');
if (actionOptions?.loadHistory !== false) {
await __1.config.defaultApp?.history.load(actionOptions?.actionMode !== __1.ACTION_MODE.undoRedo && {
operationAction: 'Element.create',
operationBeforeImage: null,
operationAfterImage: JSON.parse(JSON.stringify(this)),
operationDesc: `添加组件"${this.getElementTitle()}"`,
});
__1.config.defaultApp?.emit('saved');
}
return this;
}
catch (err) {
if (this.view) {
await this.view.load();
this.view.emit('change');
__1.config.defaultApp?.emit('saved');
}
throw err;
}
}
/**
* 删除元素
*/
async delete(none, actionOptions) {
if (actionOptions?.actionMode === __1.ACTION_MODE.local) {
const index = this.parent.children.indexOf(this);
~index && this.parent.children.splice(index, 1);
this.view && this.view.emit('local-change');
return this;
}
__1.config.defaultApp?.emit('saving');
if (!this.parent)
throw Error('该元素为根节点!');
if (actionOptions?.actionMode !== __1.ACTION_MODE.undoRedo) {
if (this.id) {
try {
await page_1.elementService.delete({
headers: {
appId: __1.config.defaultApp?.id,
operationAction: 'Element.delete',
operationDesc: `删除组件"${this.getElementTitle()}"`,
},
query: {
id: this.id,
},
});
}
catch (err) {
await __1.config.defaultApp?.history.load();
throw err;
}
// Nuims deleteResource
// Element 对应资源增删在 undo/redo 情况下可能存在权限丢失的情况
// 暂时只增不减
// new Nuims({
// domainName: this.view.page.service.app.name,
// element: this,
// }).deleteResources();
}
}
const index = this.parent.children.indexOf(this);
~index && this.parent.children.splice(index, 1);
this.destroy();
this.view && this.view.emit('change');
if (actionOptions?.loadHistory !== false) {
await __1.config.defaultApp?.history.load(actionOptions?.actionMode !== __1.ACTION_MODE.undoRedo && {
operationAction: 'Element.delete',
operationBeforeImage: JSON.parse(JSON.stringify(this)),
operationAfterImage: null,
operationDesc: `删除组件"${this.getElementTitle()}"`,
});
__1.config.defaultApp?.emit('saved');
}
}
/**
* 修改元素
*/
async update(source, actionOptions) {
__1.config.defaultApp?.emit('saving');
source && this.assign(source);
try {
if (actionOptions?.actionMode !== __1.ACTION_MODE.undoRedo) {
const body = this.toPlainJSON();
__1.utils.logger.debug('修改组件', body);
await page_1.elementService.update({
headers: {
appId: __1.config.defaultApp?.id,
operationAction: actionOptions?.actionName || 'Element.update',
operationDesc: actionOptions?.actionDesc || `修改组件"${this.getElementTitle()}"`,
},
body,
});
}
this.view && this.view.emit('change');
await __1.config.defaultApp?.history.load(actionOptions?.actionMode !== __1.ACTION_MODE.undoRedo && {
operationAction: 'Element.update',
operationBeforeImage: null,
operationAfterImage: null,
operationDesc: `修改组件"${this.getElementTitle()}"`,
});
__1.config.defaultApp?.emit('saved');
return this;
}
catch (err) {
if (this.view) {
await this.view.load();
this.view.emit('change');
__1.config.defaultApp?.emit('saved');
}
throw err;
}
}
async move(options, actionOptions) {
try {
if (!options.parentId)
return;
__1.config.defaultApp?.emit('element.moving');
const index = this.parent.children.indexOf(this);
~index && this.parent.children.splice(index, 1);
const parent = cacheData_1.vertexsMap.get(options.parentId);
parent.children.splice(options._posIndex, 0, this);
this.assign({
parent,
parentId: options.parentId,
_posIndex: options._posIndex,
});
if (actionOptions?.actionMode !== __1.ACTION_MODE.undoRedo) {
await this.update(undefined, {
actionName: 'Element.move',
actionDesc: `移动组件"${this.getElementTitle()}"`,
});
this.view.attachNodePath();
}
else {
this.view.attachNodePath();
this.view && this.view.emit('change');
await __1.config.defaultApp?.history.load();
}
__1.config.defaultApp?.emit('element.moved');
}
catch (err) {
if (this.view) {
await this.view.load();
this.view.emit('change');
}
throw err;
}
}
/**
* 添加元素副本
* 在所在父节点的后面添加一个相同的元素
*/
async duplicate() {
const code = this.toVue();
const index = this.parent.children.indexOf(this);
const newNode = Element.fromHTML(code, this.parent, this.view);
const mergeExpression = (originList, newList) => {
newList.forEach((item) => {
const originItem = originList.find((originItem) => originItem.name === item.name);
if (originItem && originItem.expression) {
// item.expression可能为空
const expression = lodash_1.cloneDeep(originItem.expression);
Object.assign(item, { expression, value: '' });
Object.assign(item.expression, { id: '' });
__1.utils.traverse((current) => {
delete current.node.editable;
delete current.node.index;
delete current.node.parentAttr;
delete current.node.parentId;
delete current.node.id;
}, { node: item.expression }, { mode: 'anyObject' });
}
});
};
// 遍历合并
const traverseMergeNode = (origiNode, newNode) => {
const originalStack = [];
const newStack = [];
originalStack.push(origiNode);
newStack.push(newNode);
let originalTempNode;
let newTempNode;
while (originalStack.length) {
originalTempNode = originalStack.pop();
newTempNode = newStack.pop();
if (newTempNode) {
mergeExpression(originalTempNode.attrList, newTempNode.attrList);
mergeExpression(originalTempNode.directiveList, newTempNode.directiveList);
newTempNode.eventList.forEach((event) => {
const originEvent = originalTempNode.eventList.find((eventTemp) => eventTemp.name === event.name);
if (originEvent) {
Object.assign(event, {
logicId: originEvent.logicId,
});
}
});
if (originalTempNode.children.length) {
for (let i = originalTempNode.children.length - 1; i >= 0; i--) {
originalStack.push(originalTempNode.children[i]);
newStack.push(newTempNode.children[i]);
}
}
}
}
};
traverseMergeNode(this, newNode);
~index && this.parent.children.splice(index + 1, 0, newNode);
this.parent.children.forEach((item, index) => {
item._posIndex = index;
});
try {
await newNode.create();
}
catch {
~index && this.parent.children.splice(index + 1, 1);
this.parent.children.forEach((item, index) => {
item._posIndex = index;
});
}
}
/**
* 添加子元素
* @param child
*/
addChild(child, actionOptions) {
if (typeof child === 'string') {
child = Element.fromHTML(child, this, this.view);
}
if (lodash_1.isPlainObject(child)) {
child = Element.from(child, this, this.view);
}
if (!this.children.includes(child)) {
const index = child._posIndex === undefined ? this.children.length : child._posIndex;
this.children.splice(index, 0, child);
}
return child.create(undefined, actionOptions);
}
/**
* 删除子元素
* @param child
*/
removeChild(child, actionOptions) {
if (lodash_1.isPlainObject(child)) {
child = this.children.find((item) => item.id === child.id);
}
return child.delete(undefined, actionOptions);
}
getElementTitle() {
const currentNode = __1.config.allNodesAPI?.[this.tag];
return currentNode?.title || this.tag;
}
/**
* 设置组件名称
*/
async setName(name) {
const oldName = this.name;
this.assign({ name });
await this.update(undefined, {
actionDesc: `设置组件"${this.getElementTitle()}"的名称为"${name}"`,
});
// 同步权限
new __1.Nuims({
domainName: this.view.page.service.app.name,
element: this,
}).editResourceFromResourceValue(oldName ? `${this.view.tempPath}/${oldName}` : null);
this.view && this.view.emit('change');
if (this.view && this.view.page && this.view.page.service) {
this.view.page.service.emit('vertexIdToNameChange', this.id, this.name);
}
}
/**
* 添加组件属性
*/
async addAttr(attr, actionOptions) {
if (lodash_1.isPlainObject(attr)) {
attr = __1.Attr.from(attr, this);
}
if (!this.attrList.includes(attr))
this.attrList.push(attr);
await attr.create(undefined, Object.assign({
actionDesc: `添加组件"${this.getElementTitle()}"属性"${attr.name}"`,
}, actionOptions));
}
/**
* 删除组件属性
*/
async deleteAttr(attr, actionOptions) {
if (!attr)
throw Error(`该组件"${this.getElementTitle()}"没有属性"${attr.name}"`);
if (lodash_1.isPlainObject(attr))
attr = this.attrList.find((item) => item.id === attr.id);
await attr.delete(undefined, Object.assign({
actionDesc: `删除组件"${this.getElementTitle()}"属性"${attr.name}"`,
}, actionOptions));
}
/**
* 获取组件属性
*/
getAttr(name) {
return this.attrList.find((attr) => attr.name === name);
}
/**
* 设置组件属性
*/
async setAttr(name, type, value) {
let attr = this.getAttr(name);
//
if (value === undefined) {
}
else {
if (typeof value !== 'string')
value = JSON.stringify(value);
if (attr) {
attr.assign({
type,
value,
});
await attr.update(undefined, {
actionDesc: `设置组件"${this.getElementTitle()}"属性"${attr.name}"的值为"${attr.value}"`,
});
}
else {
attr = __1.Attr.from({
name,
type,
value,
elementId: this.id,
}, this);
await attr.create(undefined, {
actionDesc: `设置组件"${this.getElementTitle()}"属性"${attr.name}"的值为"${attr.value}"`,
});
this.attrList.push(attr);
}
}
this.view && this.view.emit('change');
}
/**
* 添加组件事件
*/
async addEvent(data, actionOptions) {
// (data as any).element = this;
let event = new __1.Event(data);
await event.create(undefined, Object.assign({
actionDesc: `添加组件"${this.getElementTitle()}"事件"${event.name}"`,
}, actionOptions));
event = __1.Event.from(event, this);
this.eventList.push(event);
this.view && this.view.emit('change');
await __1.config.defaultApp?.history.load();
__1.config.defaultApp?.emit('saved');
return event;
}
/**
* 删除组件属性
*/
async deleteEvent(event, actionOptions) {
if (!event)
throw Error(`该组件"${this.getElementTitle()}"没有事件"${event.name}"`);
if (lodash_1.isPlainObject(event))
event = this.eventList.find((item) => item.id === event.id);
await event.delete(undefined, Object.assign({
actionDesc: `删除组件"${this.getElementTitle()}"事件"${event.name}"`,
}, actionOptions));
const index = this.eventList.indexOf(event);
~index && this.eventList.splice(index, 1);
this.view && this.view.emit('change');
await __1.config.defaultApp?.history.load();
__1.config.defaultApp?.emit('saved');
}
/**
* 添加组件指令
*/
async addDirective(data, actionOptions) {
// (data as any).element = this;
const directive = new __1.Directive(data);
await directive.create(undefined, Object.assign({
actionDesc: `添加组件"${this.getElementTitle()}"指令"${directive.name}"`,
}, actionOptions));
this.directiveList.push(__1.Directive.from(directive, this));
this.view && this.view.emit('change');
}
/**
* 删除组件属性
*/
async deleteDirective(directive, actionOptions) {
if (!directive)
throw Error(`该组件"${this.getElementTitle()}"没有指令"${directive.name}"`);
if (lodash_1.isPlainObject(directive))
directive = this.directiveList.find((item) => item.id === directive.id);
await directive.delete(undefined, Object.assign({
actionDesc: `删除组件"${this.getElementTitle()}"属性"${directive.name}"`,
}, actionOptions));
const index = this.directiveList.indexOf(directive);
~index && this.directiveList.splice(index, 1);
this.view && this.view.emit('change');
}
/**
* 转换成 Vue 的模板格式
*/
toVue(options) {
options = Object.assign({
indentStyle: 'space',
tabSize: 4,
indentLevel: 0,
aslIdAttr: false,
nodePathAttr: false,
getExtraParts: () => [],
attrFormat: (attr, element, defaultResult) => defaultResult,
}, options);
const tabString = ' '.repeat(options.tabSize * options.indentLevel);
const insideTabString = ' '.repeat(options.tabSize * (options.indentLevel + 1));
let shouldIndent = true;
const content = !this.children ? '' : this.children.map((element) => {
const childOptions = Object.assign({}, options);
childOptions.indentLevel++;
return (shouldIndent ? '\n' + insideTabString : '') + element.toVue(childOptions);
}).join('');
if (!content.length)
shouldIndent = false;
const parts = [];
if (options.aslIdAttr) {
if (options.aslIdAttr === true)
options.aslIdAttr = 'asl-id';
parts.push(`${options.aslIdAttr}="${this.id}"`);
}
if (options.nodePathAttr) {
// 注入 asl 的 node-path
parts.push(`vusion-node-path="${this.nodePath}"`);
}
this.slotTarget && parts.push(`#${this.slotTarget}` + (this.slotScope ? `="${this.slotScope}"` : ''));
this.name && parts.push(`ref="${this.name}"`);
this.staticClass && parts.push(`class="${this.staticClass}"`);
this.staticStyle && parts.push(`style="${this.staticStyle}"`);
const { finalCode } = options;
[].concat(this.attrList, this.directiveList, this.eventList)
.forEach((attr) => {
const result = options.attrFormat(attr, this, attr.toVue(this, finalCode));
result && parts.push(result);
});
options.getExtraParts(this).forEach((part) => parts.push(part));
let partsLength = 0;
let partsString = '';
parts.forEach((part) => {
if (partsLength >= 120 || part.length >= 120) {
partsString += '\n' + tabString + ' '.repeat(3); // ' '.repeat(el.tag.length + 1);
partsLength = 0;
}
partsString += ' ' + part;
partsLength += part.length;
});
return `<${this.tag}${partsString.length ? partsString : ''}>` + content + (shouldIndent ? '\n' + tabString : '') + `</${this.tag}>`;
}
/**
* 转换成设计器中使用的 Vue 文件
* @param options
*/
toDesignerVue(options) {
options = Object.assign({
nodePathAttr: true,
attrFormat: (attr, el, defaultResult) => {
if (attr.level === __1.LEVEL_ENUM.event || attr.level === __1.LEVEL_ENUM.directive)
return false;
if (attr.level === __1.LEVEL_ENUM.attr) {
const api = __1.config.allNodesAPI[el.tag];
const apiOfAttr = api && api.attrs && api.attrs.find((_attr) => _attr.name === attr.name);
if (apiOfAttr && apiOfAttr['designer-value']) {
const designerValue = apiOfAttr['designer-value'].replace(/"/g, '\'');
try {
json5.parse(designerValue);
return `:${attr.name}="${designerValue}"`;
}
catch (e) {
return `${attr.name}="${designerValue}"`;
}
}
if (attr.type === 'dynamic'
&& !(el.tag === 'u-cascade-select' && attr.name === 'categories'))
return defaultResult.replace(/:?(\w+)(?:.*?)="(.*)"/, (m, name, value) => {
try {
const tempValue = json5.parse(value);
if (typeof tempValue === 'boolean' || typeof tempValue === 'number') { // 简单类型走属性
return `:${name}="${value.replace(/"/g, '\'')}"`;
}
}
catch (e) {
if (apiOfAttr && !apiOfAttr.type.includes('string'))
return '';
}
return `${name}="{{ ${value.replace(/"/g, '\'')} }}"`;
});
else
return defaultResult;
}
return false;
},
getExtraParts: (el) => {
const parts = [];
const api = __1.config.allNodesAPI[el.tag];
const emptySlot = api && api.slots && api.slots.find((slot) => slot.name === 'default' && slot['empty-background']);
if (!el.children.length && emptySlot) {
let addEmpty = true;
if (el.tag === 'u-grid-view'
|| el.tag === 'u-list-view'
|| el.tag === 'van-list-view') {
const hasDataSource = el.attrList.find((attr) => attr.name === 'data-source');
addEmpty = !hasDataSource;
}
addEmpty && parts.push(`vusion-empty-background="${emptySlot['empty-background']}"`);
}
return parts;
},
}, options);
return this.toVue(options);
}
/**
* 同 toVue 方法
* @param options 缩进等选项
*/
toHTML(options) {
return this.toVue(options);
}
/**
* 根据标签查找元素
* @param tag
*/
findElementByTag(tag) {
if (this.tag === tag)
return this;
else {
for (const child of this.children) {
const result = child.findElementByTag(tag);
if (result)
return result;
}
}
}
/**
* 根据属性查找元素
* @param name
* @param value
*/
findElementByAttr(name, value) {
if (this.attrList.find((attr) => attr.name === name && attr.value === value))
return this;
else {
for (const child of this.children) {
const result = child.findElementByAttr(name, value);
if (result)
return result;
}
}
}
/**
* 从 Vue 的 ASTNode 转换成 ASL 元素
* @param astNode Vue 的 ASTNode
*/
static _fromASTNode(astNode, context) {
// 临时处理组件的 text
//h5-mock
if (['u-text', 'van-text', 'u-link', 'u-button', 'u-label', 'u-radio', 'u-checkbox', 'u-navbar-item', 'u-sidebar-item', 'u-menu-item'].includes(astNode.tag)
&& astNode.children.length === 1 && astNode.children[0].type === 3) {
astNode.attrs = astNode.attrs || [];
astNode.attrs.push({ name: 'text', value: JSON.stringify(astNode.children[0].text) });
astNode.children = [];
}
// 将 scopedSlots 合并到 children 中
if (astNode.scopedSlots) {
astNode.children = astNode.children || [];
Object.keys(astNode.scopedSlots).forEach((key) => {
if (!astNode.children.find((child) => key === child.slotTarget))
astNode.children.unshift(astNode.scopedSlots[key]);
});
delete astNode.scopedSlots;
}
// 提示不支持的字段
[
'component', 'inlineTemplate', 'pre', 'ns',
'transition', 'transitionOnAppear', 'transitionMode',
'slotName', 'classBinding', 'styleBinding',
].forEach((key) => {
if (astNode[key])
console.warn(`[warn] Element NASL unsupports '${key}' field in node `, astNode);
});
let element;
if (astNode.type === 1) {
if (astNode.tag === 'router-view') {
if (__1.config.scope === 'h5') {
astNode.tag = 'van-router-view';
}
else {
astNode.tag = 'u-router-view';
}
}
// if (astNode.tag === 'template')
// astNode.tag = 'div';
element = new Element({
tag: astNode.tag,
name: astNode.attrsMap.ref,
staticClass: astNode.attrsMap.class,
staticStyle: astNode.attrsMap.style,
slotTarget: astNode.slotTarget && json5.parse(astNode.slotTarget),
slotScope: astNode.slotScope === '_empty_' ? '' : astNode.slotScope,
children: astNode.children,
});
astNode.attrs && astNode.attrs.forEach((oldAttr) => {
let attr;
if (oldAttr.value === '""' && oldAttr.end - oldAttr.start === oldAttr.name.length) {
attr = __1.Attr.from({
type: 'static',
name: oldAttr.name,
value: 'true',
}, element);
}
else {
try {
const tmp = json5.parse(oldAttr.value);
attr = __1.Attr.from({
type: typeof tmp === 'string' ? 'string' : 'static',
name: oldAttr.name,
value: typeof tmp === 'string' ? tmp : oldAttr.value,
}, element);
}
catch (e) {
const expression = this._parseExpression(oldAttr.value, context);
attr = __1.Attr.from({
type: 'dynamic',
name: oldAttr.name,
value: expression ? '' : oldAttr.value,
expression,
}, element);
if (astNode.attrsMap[`:${attr.name}.sync`]) {
attr.assign({ sync: true });
}
}
}
element.attrList.push(attr);
// element.attrMap[attr.name] = attr;
});
// compiler 处理:value.sync 时会加上update:value事件,需要过滤
astNode.events && Object.keys(astNode.events).filter((name) => !name.startsWith('update:')).forEach((name) => {
const oldEvent = astNode.events[name];
const value = oldEvent.value.split('(')[0];
element.eventList.push(__1.Event.from({
name,
value,
logicId: value[0] === '$' ? undefined : value,
}, element));
// element.eventMap[name] = new Event({
// name,
// value: oldEvent.value,
// });
});
astNode.directives && astNode.directives.forEach((directive) => {
if (directive.name === 'model') {
const valueAttr = astNode.attrs && astNode.attrs.find((attr) => attr.name === 'value');
if (!valueAttr) {
const expression = this._parseExpression(directive.value, context);
element.attrList.push(__1.Attr.from({
type: 'dynamic',
name: 'value',
value: expression ? '' : directive.value,
expression,
sync: true,
}, element));
}
}
else {
const expression = this._parseExpression(directive.value, context);
element.directiveList.push(__1.Directive.from({
type: expression ? 'string' : 'dynamic',
name: directive.name,
rawName: directive.rawName,
value: expression ? '' : directive.value,
expression,
arg: directive.arg,
modifiers: directive.modifiers,
}, element));
// element.directiveMap[directive.name] = );
}
});
if (astNode.if) {
element.directiveList.push(__1.Directive.from({
type: 'dynamic',
name: 'if',
rawName: 'v-if',
value: '',
expression: this._parseExpression(astNode.if, context),
}, element));
}
// Removed
// hasBindings:
// static:
// staticRoot:
// refInFor?: boolean;
// if?: string;
// ifProcessed?: boolean;
// elseif?: string;
// else?: true;
// ifConditions?: ASTIfCondition[];
// for?: string;
// forProcessed?: boolean;
// key?: string;
// alias?: string;
// iterator1?: string;
// iterator2?: string;
// events?: ASTElementHandlers;
// nativeEvents?: ASTElementHandlers;
// model?: {
// value: string;
// callback: string;
// expression: string;
// };
// directives?: ASTDirective[];
}
else if (astNode.type === 2) {
if (__1.config.scope === 'h5') {
element = new Element({
tag: 'van-text',
});
}
else {
element = new Element({
tag: 'u-text',
});
}
const value = astNode.text.match(/{{(.*?)}}/)[1].trim();
const expression = this._parseExpression(value, context);
element.attrList.push(new __1.Attr({
type: 'dynamic',
name: 'text',
value: expression ? '' : value,
expression,
element,
}));
}
else if (astNode.type === 3) {
if (__1.config.scope === 'h5') {
element = new Element({
tag: 'van-text',
});
}
else {
element = new Element({
tag: 'u-text',
});
}
element.attrList.push(new __1.Attr({
name: 'text',
value: astNode.text,
element,
}));
}
return element;
}
/**
* 解析属性中的表达式
* @param value
* @param $def 目前 $def 只用来查找 $def.variables 和 $def.structures
* @param dataSchema
*/
static _parseExpression(value, context = {}) {
try {
let ast = babelParser.parseExpression(value);
__1.utils.traverse((current) => {
if (!current.node)
return;
current.node.level = 'expressionNode';
delete current.node.loc;
delete current.node.start;
delete current.node.end;
delete current.node.errors;
delete current.node.comments;
delete current.node.innerComments;
delete current.node.leadingComments;
delete current.node.trailingComments;
if (current.node.type === 'CallExpression' && current.node.callee.object.name === '$utils') {
const calleeCode = current.node.callee.property.name || current.node.callee.property.value;
if (calleeCode === 'Enum') {
const enumName = current.node.arguments[0] && current.node.arguments[0].value;
if (enumName) {
let enumNode;
Object.keys(__1.dataTypesMap).forEach((dataTypeKey) => {
if (__1.dataTypesMap[dataTypeKey].type === 'enum') {
if (__1.dataTypesMap[dataTypeKey].name === enumName) {
enumNode = __1.dataTypesMap[dataTypeKey];
}
}
});
if (enumNode) {
const code = 'ID_' + enumNode.id;
current.node.arguments[0].value = code;
current.node.arguments[0].code = code;
}
}
}
Object.assign(current.node, {
type: 'BuiltInFunction',
value: calleeCode,
calleeCode,
label: '内置函数',
level: 'expressionNode',
builtInFuncParams: current.node.arguments.map((argument) => ({
level: 'expressionNode',
type: 'BuiltInFuncParam',
name: '',
builtInFuncParamValue: Object.assign({
level: 'expressionNode',
}, argument),
})),
});
delete current.node.arguments;
delete current.node.callee;
}
else if (current.node.type === 'MemberExpression') {
let object = current.node.object;
const properties = [current.node.property];
while (object.type === 'MemberExpression') {
properties.push(object.property);
object = object.object;
}
if (object.name === 'scope') {
let schema = {
$ref: '',
};
if (context.dataSchema) { // 可以直接用 dataSchema,处理 mergeBlock 的情况
const typeKey = '#/genericTypes/ScopeOf';
schema = {
type: 'genericType',
typeKey,
typeInstantiation: {
typeName: 'ScopeOf',
typeParams: [
{
typeParamName: 'T',
typeParamValue: {
$ref: context.dataSchema,
typeKey: context.dataSchema,
},
},
],
},
};
}
Object.assign(object, { schema, code: object.name });
}
if (context && object.name) {
let variable = (context.variables || []).find((variable) => variable.name === object.name);
variable = variable || object;
if (variable) {
let schemaData = __1.dataTypesMap[variable.schema && variable.schema.$ref];
if (variable.schema && variable.schema.type === 'genericType') {
const genericClass = __1.dataTypesMap[variable.schema.typeKey];
let propertyList = [];
if (genericClass) {
propertyList = lodash_1.cloneDeep(genericClass.propertyList);
propertyList = propertyList.map((property) => {
if (property.type === 'genericParam' && variable.schema.typeInstantiation) {
const param = variable.schema.typeInstantiation.typeParams.find((typeParam) => typeParam.typeParamName === property.typeParamName);
if (param) {
property = Object.assign({ name: property.name }, param.typeParamValue);
}
}
return property;
});
}
schemaData = { propertyList };
}
// 这部分功能还不稳定,先单独处理一下
if (object.name === 'scope' && properties.length >= 3) {
const tempStructure = context.structures.find((structure) => context.dataSchema === structure.schemaRef || context.dataSchema === structure.name);
if (tempStructure && !tempStructure.id) {
properties.pop(); // scope.item 那一项
let property = properties.pop();
property.schemaRef = tempStructure.name + '.' + property.name;
const originProp = tempStructure.propertyList.find((tempProp) => tempProp.name === property.name);
schemaData = originProp && __1.dataTypesMap[originProp.$ref];
property = properties.pop();
while (property && schemaData) {
if (schemaData.propertyList) {
const originProp = schemaData.propertyList.find((tempProp) => tempProp.name === property.name);
if (originProp) {
if (originProp.id) {
Object.assign(property, {
schemaRef: '#/' + originProp.id,
code: `ID_${originProp.id}`,
});
}
schemaData = __1.dataTypesMap[originProp.$ref];
}
}
property = properties.pop();
}
}
else if (tempStructure && tempStructure.id && context.recordProperty) {
properties.pop(); // scope.item 那一项
let property = properties.pop();
property.schemaRef = context.recordProperty.$ref;
property.code = `ID_${context.recordProperty.id}`;
const originProp = context.recordProperty;
schemaData = originProp && __1.dataTypesMap[originProp.$ref];
property = properties.pop();
while (property && schemaData) {
if (schemaData.propertyList) {
const originProp = schemaData.propertyList.find((tempProp) => tempProp.name === property.name);
if (originProp) {
if (originProp.id) {
Object.assign(property, {
schemaRef: `#/${originProp.id}`,
code: `ID_${originProp.id}`,
});
}
schemaData = __1.dataTypesMap[originProp.$ref];
}
}
property = properties.pop();
}
}
}
else {
let property = properties.pop();
while (property && schemaData) {
if (schemaData.propertyList) {
const originProp = schemaData.propertyList.find((tempProp) => tempProp.name === property.name);
if (originProp) {
if (originProp.id) {
Object.assign(property, {
schemaRef: '#/' + originProp.id,
});
}
schemaData = __1.dataTypesMap[originProp.$ref];
}
}
property = properties.pop();
}
}
}
}
const property = current.node.property;
if (property.type === 'StringLiteral') {
// 区分单引号的变量和纯文字
// $utils['FormatDate']()
// 把 'FormatDate' 变成 Identifier, 因为内置函数名是通过选择出来的;如果不改,会展示成输入框
if (property.extra.raw.includes("'")) {
property.type = 'Identifier';
property.name = ast.value;
property.level = 'expressionNode';
}
}
}
else if (current.node.type === 'Identifier') {
if (current.node.name && current.node.name.startsWith('ID_ENUM_LIST_')) {
current.node.schemaRef = current.node.name.replace('ID_ENUM_LIST_', '');
current.node.code = current.node.name;
}
}
delete current.node.extra;
}, { node: ast }, { mode: 'anyObject' });
if (ast) {
let hasSchema = !!context.dataSchema;
let object = ast;
while (!hasSchema && object) {
if (object.schema || object.schemaRef || (object.property && (object.property.schema || object.property.schemaRef)))
hasSchema = true;
object = object.object;
}
if (ast.type === 'ArrayExpression' || ast.type === 'ObjectExpression'
|| ast.type === 'MemberExpression' && !hasSchema) {
ast = {
label: '原子项',
level: 'expressionNode',
type: 'Unparsed',
editable: true,
code: value,
};
}
}
return __1.LogicItem.from(ast, null, null);
}
catch (e) {
return null;
}
}
/**
* 解析 Vue 模板
* 该方法不会绑定 view 和 parent,如果是添加元素优先使用 fromHTML
* @param html Vue 的模板
* @TODO 处理多个元素的问题
*/
static parse(html, context) {
html = html || '<div></div>';
const compilerOptions = {
preserveWhitespace: false,
outputSourceRange: true,
};
let ast = compiler.compile(html, compilerOptions).ast;
if (ast.tag === 'template' && !ast.slotTarget)
ast = ast.children[0];
if (context && !context.dataSchema) {
const cap = html.match(/data-schema="(.*?)"/);
context.dataSchema = cap ? cap[1] : '';
}
let root;
__1.utils.traverse((current) => {
// 处理 scopedSlots;
if (!current.parent)
root = this._fromASTNode(current.node, context);
else
current.parent.children[current.index] = this._fr