UNPKG

@lcap/asl

Version:

NetEase Application Specific Language

1,197 lines 55.4 kB
"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