import { excludedInJSON, immutable } from '../decorators'; import { LEVEL_ENUM, Vertex, Service, WebService, MicroService, SERVICE_TYPE, utils, History, updateGenericTypeList, vertexsMap, Entity, Structure, Enum, dataTypesMap, BaseVariable, variablesMap, updateDataTypeList, Module } from '..'; import appService from '../../service/app'; interface AppLoadAllOptions { asyncLoadServicesDetail?: boolean, asyncLoadViewsDetail?: boolean, } /** * 应用类 * @example OA * @category App */ export class App extends Vertex { /** * 概念类型 */ @immutable() public readonly level: LEVEL_ENUM = LEVEL_ENUM.app; /** * App Id */ @immutable() public readonly id: string = undefined; /** * Alias of id */ @immutable() public readonly appId: string = undefined; /** * 应用标识 */ @immutable() public readonly name: string = undefined; /** * 应用标题 */ @immutable() public readonly title: string = undefined; /** * 应用图标 */ @immutable() public readonly icon: string = undefined; /** * 应用描述 */ @immutable() public readonly description: string = undefined; /** * 域名 */ @immutable() public readonly dnsAddr: string = undefined; /** * 官方类型 */ @immutable() public readonly officialType: 'offical' | 'nonofficial'; /** * IDE 版本 */ @immutable() public readonly ideVersion: string = undefined; /** * 轻舟项目 Id */ @immutable() public readonly projectId: string = undefined; /** * 租户 Id */ @immutable() public readonly tenantId: string = undefined; /** * 租户 Id */ @immutable() public readonly userGroupId: string = undefined; /** * h5 pc type */ @immutable() public readonly scope: 'pc' | 'h5' = undefined; /** * 发布状态 */ @immutable() public readonly deploying: string = undefined; /** * 发布 Id,用于获取CICD详情 */ @immutable() public readonly deploymentId: string = undefined; /** * 应用下的服务 */ @immutable() public readonly services: Array = []; /** * 首个 Web 服务 * 后续 App 创建不了多个模块了,默认处理该服务 */ @immutable() public readonly firstWebService: WebService = undefined; /** * 首个 Web 服务 * 后续 App 创建不了多个模块了,默认处理该服务 */ @immutable() public readonly firstMicroService: MicroService = undefined; @immutable() public modules: Module[] = []; /** * 历史记录 * 用于处理撤销重做 */ @immutable() @excludedInJSON() public readonly history: History = undefined; /** * 历史记录 * 用于处理撤销重做 */ @immutable() @excludedInJSON() public readonly webConfig?: string = undefined; /** * @param source 需要合并的部分参数 */ constructor(source?: Partial) { super(); source && this.assign(source); this.history = new History({ app: this }); } /** * 加载是否内置官方模板创建应用 */ loadWebConfig(appDetail: any) { const { pages } = appDetail.services .find((service: any) => service.type === 'app') || {}; let webConfig; try { const pagesConfig = JSON.parse(pages); webConfig = pagesConfig.config; } catch (error) {} this.assign({ webConfig }); } /** * 加载详情并同步 envList 信息 * @requires this.id */ async loadEnvList(appDetail: any) { if (!appDetail) { appDetail = await appService.loadApp({ query: { id: this.id, }, headers: { 'request-lcpAppId': this.id, }, }); } const { envList } = appDetail.services .find((service: any) => service.type === 'microService') || {}; this.assign({ envList }); } /** * 加载详情并同步 App 信息 * @requires this.id */ async load() { const result = await appService.loadApp({ query: { id: this.id, }, headers: { 'request-lcpAppId': this.id, }, }); const { status } = result || {}; if (['PULLING', 'PUSHING', 'PRE_PULLING'].includes(status)) { this.emit('updateIdeStatus', status); } await this.loadEnvList(result); this.loadWebConfig(result); delete result.services; this.assign(result); return this; } /** * 加载 App 下的所有服务, * 不能再次被调用的方法 重要!!!调用会引起未知的问题 */ async loadServices() { const result = await appService.loadServices({ query: { appId: this.id, }, headers: { 'request-lcpAppId': this.id, }, }); const services = result.map((service: Service) => { if (service.type === SERVICE_TYPE.web) { service = WebService.from(service, this); if (!this.firstWebService) this.assign({ firstWebService: service as WebService }); } else { service = MicroService.from(service, this); if (!this.firstMicroService) this.assign({ firstMicroService: service as MicroService }); } service.assign({ app: this }); return service; }); await this.firstWebService.loadPackageInfo(); this.assign({ services }); return services; } /** * 加载页面和逻辑详情 */ async _loadViewsDetail() { const tasks: Array> = []; this.firstWebService.pages.forEach((page) => { utils.traverse((current) => { if (current.node.level === 'view') { const view = current.node; view.$def.logics = []; // load syncDef 不会添加重复逻辑 tasks.push(view.load()); } }, { node: page.rootView }); }); // 自动 load,不用 await await Promise.all(tasks); this.emit('loadViewsDetail'); } /** * 需要先调用 loadServices 之后再调用此函数 * 加载 App 下的服务具体内容 */ private async _loadServicesDetail(options: AppLoadAllOptions = { asyncLoadServicesDetail: false, asyncLoadViewsDetail: false, }) { // 前端许多功能依赖后端数据,所以必须先 load 后端服务 // loadMicroService await Promise.all([ this.firstMicroService.loadEntities(), this.firstMicroService.loadStructures(), this.firstMicroService.loadEnums(), ]); // interfaces params 的 children 依赖 entities 和 structures,所以必须拆成两个 Promise await Promise.all([ this.firstMicroService.loadInterfaces(), this.firstMicroService.loadProcesses(), ]); // loadWebService await this.firstWebService.loadPages(); if (options.asyncLoadViewsDetail) this._loadViewsDetail(); else await this._loadViewsDetail(); this.emit('loadedAll'); } /** * 加载 App 下所有子节点 * @example * const app = new App({ id: appId }); * await app.loadAll(); */ async loadAll(options: AppLoadAllOptions = { asyncLoadServicesDetail: false, asyncLoadViewsDetail: false, }) { await this.load(); // 请求其它接口前,需要先查询到 app 上的 baseVersion await Promise.all([ this.loadServices(), updateGenericTypeList(this.id), ]); if (options.asyncLoadServicesDetail) this._loadServicesDetail(options); else await this._loadServicesDetail(options); } /** * 从后端 JSON 生成规范的 App 对象 * @param source JSON */ from(source: any) { return new App(source); } /** * 清理内存,避免内存泄漏 */ clear() { function traverse(obj: any, callback: (obj: any) => void, ws = new WeakSet()) { if (typeof obj !== 'object' || obj === null) return; if (ws.has(obj)) return; ws.add(obj); if (Array.isArray(obj)) obj.forEach((item) => { traverse(item, callback, ws); }); else { callback(obj); Object.values(obj).forEach((item) => { traverse(item, callback, ws); }); } } traverse(this, (obj: any) => { vertexsMap.delete(obj?.id); if (obj instanceof Entity || obj instanceof Structure || obj instanceof Enum) delete dataTypesMap[obj.schemaRef]; if (obj instanceof BaseVariable) variablesMap.delete(obj.id); }); updateDataTypeList(); } getModule(moduleName: string) { const { modules } = this; let module = modules.find((m) => m.name === moduleName); if (module) return module; module = new Module({ name: moduleName }); modules.push(module); return module; } } export default App;