import { action, circular, excludedInJSON, immutable } from '../decorators'; import { config, history, utils, LEVEL_ENUM, Vertex, PackageJSON, App, Page, DataNode, View, Element, Directive, ExpressionNode, Logic, genFinalCode, ActionOptions, ACTION_MODE } from '..'; import { elementService, attributeService, eventService, directiveService } from '../../service/page'; import { LogicItem, evaluate } from '../logic/LogicItem'; import { traverse } from '../utils'; function generateBody(body: Attr) { if (body.expression) { traverse((current) => { delete current.node.editable; delete current.node.index; delete current.node.memberIdentifierSchema; }, { node: body.expression as any }, { mode: 'anyObject' }); } } /** * 前端组件属性 * 纯组件属性,不包含事件、指令等 * @examples * - 纯字符串:color="primary" * - 静态表达式::min="23" * - 静态表达式::data="[{ text: '选项1', value: '选项1' }, { text: '选项2', value: '选项2' }]" * - 动态表达式::expr="$utils.LowerCase(a + b)" */ export class Attr extends Vertex { /** * 概念类型 */ @immutable() public readonly level: LEVEL_ENUM = LEVEL_ENUM.attr; /** * 属性类型 */ @immutable() public readonly type: 'string' | 'static' | 'dynamic' = 'string'; /** * 属性 Id */ @immutable() public readonly id: string; /** * 属性名 */ @immutable() public readonly name: string; /** * 属性原始值 * 用户输入框中填入的值,同样也是 HTML 中 "" 中间的字符串 * 和 ASTElement 中的有区别 */ @immutable() public readonly value: string; /** * model 是否开启 */ @immutable() public readonly model?: boolean = false; /** * sync 是否开启 */ @immutable() public readonly sync?: boolean = false; /** * 关联的动态表达式 */ @immutable() public readonly expression?: ExpressionNode; /** * 所属元素 Id */ @immutable() public readonly elementId: string; /** * 所属元素 */ @circular() @immutable() public readonly element: Element; /** * @param source 需要合并的部分参数 */ constructor(source?: Partial) { super(); source && this.assign(source); } /** * 转换成 Vue 的模板格式 */ toVue(placeholder?: any, finalCode?: boolean) { let value = this.value; if (this.expression && this.type === 'dynamic') { value = evaluate(this.expression, finalCode); value && (value = genFinalCode(value, finalCode)); } if (value !== undefined && value !== null && typeof value === 'string') { value = value.replace(/"/g, '\''); } if (this.type === 'string') { return value !== undefined && value !== null ? `${this.name}="${value}"` : ''; } else { return value !== undefined && value !== null && value !== '' ? `:${this.name}${this.sync ? '.sync' : ''}="${value}"` : ''; } } /** * 从后端 JSON 生成规范的 Attr 对象 */ public static from(source: any, element: Element) { const attr = new Attr(source); attr.assign({ element }); return attr; } /** * 添加组件属性 */ @action('添加组件属性') async create(none?: void, actionOptions?: ActionOptions) { if (actionOptions?.actionMode === ACTION_MODE.local) { this.element?.view?.emit('local-change'); return this; } config.defaultApp?.emit('saving'); if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) { const body = this.toJSON(); generateBody(body); utils.logger.debug('添加组件属性', body); const result: Attr = await attributeService.create({ headers: { appId: config.defaultApp?.id, operationAction: actionOptions?.actionName || 'Attr.create', operationDesc: actionOptions?.actionDesc || `添加组件"${this.element.tag}"属性"${this.name}"`, }, body, }); this.assign({ id: result }); } this.element?.view?.emit('change'); await config.defaultApp?.history.load(); config.defaultApp?.emit('saved'); return this; } /** * 删除组件属性 */ @action('删除组件属性') async delete(none?: void, actionOptions?: ActionOptions) { if (actionOptions?.actionMode === ACTION_MODE.local) { const index = this.element.attrList.indexOf(this); ~index && this.element.attrList.splice(index, 1); this.element?.view?.emit('local-change'); return this; } config.defaultApp?.emit('saving'); if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) { if (this.id) { try { await attributeService.delete({ headers: { appId: config.defaultApp?.id, operationAction: actionOptions?.actionName || 'Attr.delete', operationDesc: actionOptions?.actionDesc || `删除组件"${this.element.tag}"属性"${this.name}"`, }, query: { id: this.id, }, }); } catch (err) { await config.defaultApp?.history.load(); throw err; } } } const index = this.element.attrList.indexOf(this); ~index && this.element.attrList.splice(index, 1); this.destroy(); this.element?.view?.emit('change'); await config.defaultApp?.history.load(); config.defaultApp?.emit('saved'); } /** * 修改组件属性 */ @action('修改组件属性') async update(data?: Attr, actionOptions?: ActionOptions) { config.defaultApp?.emit('saving'); data && this.assign(data); if (actionOptions?.actionMode !== ACTION_MODE.undoRedo) { const body = this.toJSON(); generateBody(body); utils.logger.debug('修改组件属性', body); await attributeService.update({ headers: { appId: config.defaultApp?.id, operationAction: actionOptions?.actionName || 'Attr.update', operationDesc: actionOptions?.actionDesc || `修改组件"${this.element.tag}"属性"${this.name}"`, }, body, }); } this.element?.view?.emit('change'); await config.defaultApp?.history.load(); config.defaultApp?.emit('saved'); return this; } } export default Attr;