import _ from 'lodash' import {invokeMethod} from "./utils" import {windows} from './Defaults' import {lookupScope} from "./lib/lib"; import {scopeOnLoad} from './lib/config' export class Scope { /** * 业务模块的唯一编号 */ id = _.uniqueId('scope_') originalVjson /** * 一个 ExtJS 能接受的配置对象 */ vjson /** * 原始 vjsonModel 对象 */ model /** * 双向绑定的模型对象 */ viewModel /** * 构建完成之后的 Ext控件句柄 */ _handle /** * 与 watch 装饰器配合使用. * viewModel 属性更改时触发成员方法 */ _watchList /** * 页面显示的时候带的参数 在设计刷新的时候使用 */ _vjsonOption _dataOption /** * 最顶部的 scope 对象 */ topScope _addWatch(tplExpress, fn) { if (!this._watchList) { this._watchList = [] } this._watchList.push({watch: tplExpress, fn}) } _applyWatchList() { _.forEach(this._watchList, item => { this.viewModel.bind(item.watch, item.fn.bind(this)) }) } get isScope() { return true; } /** * 产生一个当前模块有效的唯一id * @param key 唯一编号 */ uid(key) { return this.id + key } /** * 对话框"保存"成功. * 关闭对话框,并响应 success 方法 * @param data 要傳回的數據(可以為空) */ dialogSuccess(data?) { this._handle.fireEvent('success', this, data) const sender = this._handle.config.animateTarget const scope = lookupScope(sender) if (typeof this['success'] === 'function') { this['success'].call(scope, sender, data) } this.close() } /** * 设置等待状态 * @param value * @param msg */ setLoading(value: boolean) { const scope = this scope._handle?.setLoading(value) } /** * 以对话框模式打开当前模块 * @param sender 发送者(按钮或Scope对象) * @param vjsonOption 界面覆盖选项(可以为空) * @param dataOption 数据覆盖选项(可以为空) */ showDialog(sender, vjsonOption, dataOption) { const that = this const vmodel = _.defaultsDeep({ // }, dataOption, that.model) this.viewModel = new Ext.app.ViewModel(vmodel); this.viewModel.yvanScope = this this._applyWatchList() this["scopeKey"] = "dialog-" + getVjsonHash(JSON.stringify(this.vjson) + JSON.stringify(vjsonOption)) const config = _.defaultsDeep({ animateTarget: sender, viewModel: this.viewModel, yvanScope: this, referenceHolder: true, }, vjsonOption, that.vjson, windows) if (config.height === 'unset') { delete config.height } if (config.width === 'unset') { delete config.width } // const holder = sender?.lookupReferenceHolder() // delete config.constrain const topScope = lookupScope(sender)?.topScope if (topScope) { config.constrain = true this.topScope = topScope } const win = new Ext.Window(config); // if (holder) { // holder.add(win) // } if (topScope) { topScope._handle.add(win) } win.addListener('beforerender', function (sender) { // 记录句柄 if (sender && !that._handle) { that._handle = sender } }) win.addListener('afterrender', function (sender) { // 调用onLoad回调 try { that.onLoad() } catch (e) { console.error('errorAt onLoad', e) } }) win.addListener('destroy', this._destroy.bind(this)) win.show(); } /** * 以标签模式打开当前模块 * @param vjsonOption 界面覆盖选项(可以为空) * @param dataOption 数据覆盖选项(可以为空) */ showPage(vjsonOption, dataOption) { const that = this this._vjsonOption = vjsonOption; this._dataOption = dataOption; const vmodel = _.defaultsDeep({ data: {} }, that.model, dataOption) this.viewModel = new Ext.app.ViewModel(vmodel); this.viewModel.yvanScope = this this._applyWatchList() this["scopeKey"] = "page-" + getVjsonHash(JSON.stringify(this.vjson) + JSON.stringify(vjsonOption)) // 根级不能设置id delete that.vjson.id const config = _.defaultsDeep({ viewModel: this.viewModel, yvanScope: this, referenceHolder: true, }, vjsonOption, that.vjson) const tt = Ext.getCmp('TT') const handle = tt.addScope(this, config, (handle) => { handle.addListener('added', function (sender) { // 记录句柄 if (sender && !that._handle) { that._handle = sender } }) handle.addListener('afterrender', function (sender) { // 调用onLoad回调 try { that.onLoad() } catch (e) { console.error('errorAt onLoad', e) } }) handle.addListener('destroy', this._destroy.bind(this)) }) return handle } /** * 直接渲染到元素 * @param element 渲染目标 * @param vjsonOption 界面覆盖选项(可以为空) * @param dataOption 数据覆盖选项(可以为空) */ renderTo(element, vjsonOption, dataOption) { const that = this this._vjsonOption = vjsonOption; this._dataOption = dataOption; const vmodel = _.defaultsDeep({ data: {} }, that.model, dataOption) this["scopeKey"] = "render-" + getVjsonHash(JSON.stringify(this.vjson) + JSON.stringify(vjsonOption)) this.viewModel = new Ext.app.ViewModel(vmodel); this.viewModel.yvanScope = this this._applyWatchList() const config = _.defaultsDeep({ viewModel: this.viewModel, yvanScope: this, referenceHolder: true, renderTo: element, listeners: { afterrender(sender) { // 记录句柄 if (sender && !that._handle) { that._handle = sender } // 调用onLoad回调 try { that.onLoad() } catch (e) { console.error('errorAt onLoad', e) } // 如果vjson中配置了 afterrender ,需要恢复状态 invokeMethod(that.vjson.listeners?.afterrender, that, arguments) }, }, }, vjsonOption, that.vjson) new Ext.container.Viewport(config); } /** * 关闭对话框(或标签页) */ close() { this._handle.close() } /** * 获取 viewModel 里包含的数据(只读) */ get data() { return this.viewModel.getData() } /** * 设置 viewModel 中的数据 * 可以是 key, value 模式 * 也可以是 {key:value} 模式 */ set(path, value) { return this.viewModel.set(path, value) } /** * 寻找模块内所有的 xtype 对应的对象 * @param xtypeKey */ down(xtypeKey) { return this._handle.down(xtypeKey) } /** * 获取所有设置过 Reference 名称的组件 */ get refs() { return this._handle.getReferences() } _destroy() { const that = this that.onDestroy() delete that._watchList delete that._handle } constructor({model, vjson}) { const that = this this.model = model this.originalVjson = _.cloneDeep(vjson) this.vjson = this.originalVjson // this.buildVjson() } /** * 模块载入完成之后的回调 */ onLoad() { if (scopeOnLoad && typeof scopeOnLoad === 'function') { scopeOnLoad(this) } } /** * 组件卸载之后的回调 */ onDestroy() { } } /** * 观察装饰器,viewModel 属性更改时触发成员方法 * @param tplExpress tpl表达式,例如 "{form.f1}" */ export function watch(tplExpress, deep = false) { return function (target, propertyKey, pd) { target._addWatch({bindTo: tplExpress, deep}, target[propertyKey]) return target[propertyKey] } } // 获取vjson的hash值 function getVjsonHash(str: string): string { let hash = 3465217896,i,ch; for (i = str.length - 1; i >= 0; i--) { ch = str.charCodeAt(i); hash ^= ((hash << 5) + ch + (hash >> 2)); } return (hash & 0x7FFFFFFF) + ""; }