"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createComponent = createComponent; /// const assert_1 = require("oak-domain/lib/utils/assert"); const types_1 = require("oak-domain/lib/types"); const page_common_1 = require("./page.common"); const lodash_1 = require("oak-domain/lib/utils/lodash"); const OakProperties = { oakId: '', oakPath: '', oakParentEntity: '', oakFrom: '', oakActions: '', oakZombie: false, oakDisablePulldownRefresh: false, }; const OakPropertyTypes = { oakId: String, oakPath: String, oakParentEntity: String, oakFrom: String, oakActions: String, oakZombie: Boolean, oakDisablePulldownRefresh: Boolean, }; const oakBehavior = Behavior({ methods: { t(key, params) { return this.features.locales.t(key, params); }, addFeatureSub(name, callback) { const unsubHandler = this.features[name].subscribe(callback); this.featuresSubscribed.push({ name, callback, unsubHandler, }); }, removeFeatureSub(name, callback) { const f = this.featuresSubscribed.find((ele) => ele.callback === callback && ele.name === name); (0, lodash_1.pull)(this.featuresSubscribed, f); f.unsubHandler && f.unsubHandler(); }, unsubscribeAll() { this.featuresSubscribed.forEach((ele) => { if (ele.unsubHandler) { ele.unsubHandler(); ele.unsubHandler = undefined; } }); }, subscribeAll() { this.featuresSubscribed.forEach((ele) => { if (!ele.unsubHandler) { ele.unsubHandler = this.features[ele.name].subscribe(ele.callback); } }); }, iAmThePage() { const pages = getCurrentPages(); if (pages[pages.length - 1] === this) { return true; } return false; }, setState(data, callback) { this.setData(data, () => { this.state = this.data; callback && callback.call(this); }); }, reRender() { return page_common_1.reRender.call(this, this.oakOption); }, async onLoad(query) { /** * 小程序以props传递数据,和以页面间参数传递数据的处理不一样,都在这里处理 * 目前处理的还不是很完善,在实际处理中再做 */ const { properties } = this.oakOption; const dataResolved = {}; const assignProps = (data, property, type) => { if (data.hasOwnProperty(property)) { let value = data[property]; if (typeof data[property] === 'string' && type !== 'string') { switch (type) { case 'boolean': { value = new Boolean(data[property]); break; } case 'number': { value = new Number(data[property]); break; } case 'object': { value = JSON.parse(data[property]); break; } default: { (0, assert_1.assert)(false); } } } Object.assign(dataResolved, { [property]: value, }); } }; /** * query是跳页面时从queryString里传值 * this.data是properties中有定义的时候在会自动赋值,这里没必要再处理一遍 */ if (properties) { for (const key in properties) { if (query[key]) { assignProps(query, key, typeof properties[key]); } } } for (const key in OakProperties) { if (query[key]) { assignProps(query, key, typeof OakProperties[key]); } } if (Object.keys(dataResolved).length > 0) { this.setState(dataResolved); } }, save(key, item) { return this.features.localStorage.save(key, item); }, load(key) { return this.features.localStorage.load(key); }, clear(key) { if (key) { return this.features.localStorage.remove(key); } return this.features.localStorage.clear(); }, setNotification(data) { this.features.notification.setNotification(data); }, consumeNotification() { return this.features.notification.consumeNotification(); }, setMessage(data) { return this.features.message.setMessage(data); }, consumeMessage() { return this.features.message.consumeMessage(); }, navigateBack(delta) { return this.features.navigator.navigateBack(delta); }, navigateTo(option, state) { return this.features.navigator.navigateTo(option, state); }, redirectTo(option, state) { return this.features.navigator.redirectTo(option, state); }, switchTab(option, state) { return this.features.navigator.switchTab(option, state); }, clean(lsn, dontPublish, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.clean(path2, lsn, dontPublish); }, savePoint() { return this.features.runningTree.savePoint(); }, isDirty(path) { return this.features.runningTree.isDirty(path || this.state.oakFullpath); }, execute(action, messageProps, path, opers) { return page_common_1.execute.call(this, action, path, messageProps, opers); }, getFreshValue(path) { return page_common_1.getFreshValue.call(this, path); }, select(entity, selection) { return page_common_1.select.call(this, entity, selection); }, checkOperation(entity, { action, data, filter }, checkerTypes, cacheInsensative) { return this.features.cache.checkOperation(entity, { action, data, filter, }, checkerTypes, cacheInsensative); }, tryExecute(path, action) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; const operations = this.features.runningTree.getOperations(path2); if (operations?.length) { for (const oper of operations) { const { entity, operation } = oper; const operation2 = action ? { ...operation, action, } : operation; const result = this.checkOperation(entity, operation2); if (result !== true) { return result; } } return true; } return false; }, getOperations(path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.getOperations(path2); }, async refresh(pageNumber) { return page_common_1.refresh.call(this, pageNumber); }, loadMore() { return page_common_1.loadMore.call(this); }, getId() { return this.features.runningTree.getId(this.state.oakFullpath); }, setNamedFilters(filters, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.setNamedFilters(path2, filters, refresh); }, setFilters(filters, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.setNamedFilters(path2, filters); }, getFilters(path) { if (this.state.oakFullpath) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; const namedFilters = this.features.runningTree.getNamedFilters(this.state.oakFullpath); return namedFilters.map(({ filter }) => { if (typeof filter === 'function') { return filter(); } return filter; }); } }, getFilterByName(name, path) { if (this.state.oakFullpath) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; const filter = this.features.runningTree.getNamedFilterByName(path2, name); if (filter?.filter) { if (typeof filter.filter === 'function') { return filter.filter(); } return filter.filter; } } }, addNamedFilter(namedFilter, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.addNamedFilter(path2, namedFilter, refresh); }, removeNamedFilter(namedFilter, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.removeNamedFilter(path2, namedFilter, refresh); }, removeNamedFilterByName(name, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.removeNamedFilterByName(path2, name, refresh); }, setNamedSorters(namedSorters, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.setNamedSorters(path2, namedSorters, refresh); }, getSorters(path) { if (this.state.oakFullpath) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; const namedSorters = this.features.runningTree.getNamedSorters(path2); const sorters = namedSorters .map(({ sorter }) => { if (typeof sorter === 'function') { return sorter(); } return sorter; }) .filter((ele) => !!ele); return sorters; } }, getSorterByName(name, path) { if (this.state.oakFullpath) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; const sorter = this.features.runningTree.getNamedSorterByName(path2, name); if (sorter?.sorter) { if (typeof sorter.sorter === 'function') { const sortItem = sorter.sorter(); // 要支持自定义sorter函数返回完整的sorter,但这种sorter应当确保是无名的不被查找 (0, assert_1.assert)(!(sortItem instanceof Array), '不应该有非item的sorter被查找'); return sortItem; } return sorter.sorter; } } }, addNamedSorter(namedSorter, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.addNamedSorter(path2, namedSorter, refresh); }, removeNamedSorter(namedSorter, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.removeNamedSorter(path2, namedSorter, refresh); }, removeNamedSorterByName(name, refresh, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.removeNamedSorterByName(path2, name, refresh); }, getPagination(path) { if (this.state.oakFullpath) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.getPagination(path2); } }, setPageSize(pageSize, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.setPageSize(path2, pageSize); }, setCurrentPage(currentPage, path) { (0, assert_1.assert)(currentPage !== 0); if (this.state.oakEntity && this.state.oakFullpath) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.setCurrentPage(path2, currentPage); } }, addItem(data, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.addItem(path2, data); }, addItems(data, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.addItems(path2, data); }, updateItem(data, id, action, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.updateItem(path2, data, id, action); }, updateItems(data, ids, action, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.updateItems(path2, data, ids, action); }, removeItem(id, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.removeItem(path2, id); }, removeItems(ids, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.removeItems(path2, ids); }, recoverItem(id, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.recoverItem(path2, id); }, recoverItems(ids, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.recoverItems(path2, ids); }, resetItem(id, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; this.features.runningTree.resetItem(path2, id); }, setId(id) { return this.features.runningTree.setId(this.state.oakFullpath, id); }, unsetId() { return this.features.runningTree.unsetId(this.state.oakFullpath); }, update(data, action, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.update(path2, data, action); }, create(data, path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.create(path2, data); }, remove(path) { const path2 = path ? `${this.state.oakFullpath}.${path}` : this.state.oakFullpath; return this.features.runningTree.remove(path2); }, isCreation(path) { // 调用getFreshValue,不redo的情况下如果是creation返回应当是空 const value = this.getFreshValue(path); (0, assert_1.assert)(!(value instanceof Array)); return value?.$$createAt$$ === 1; }, async aggregate(aggregation) { return await this.features.cache.aggregate(this.state.oakEntity, aggregation); }, loadMissedLocales(key) { this.features.locales.loadMissedLocale(key); }, subDataEvents(events, callback) { return this.features.subscriber.sub(events, callback); }, }, observers: { oakPath(data) { if (data && data !== this.prevState.oakFullpath) { (0, assert_1.assert)(data); if (this.oakLifetime !== 'ready') { // 在init状态小程序也会调这个observer,先忽略之 return; } const pathState = page_common_1.onPathSet.call(this, this.oakOption, this.iAmThePage()); this.setState(pathState, async () => { if (this.prevState.oakFullpath === undefined) { // oakFullpath后置的情况,容一下错 if (this.oakLifetime !== 'ready') { // 在init状态小程序也会调这个observer,先忽略之 return; } // 如果每个页面都在oakFullpath形成后再渲染子结点,这个if感觉是不应该命中的 console.warn('发生了结点先形成再配置oakPath的情况,请检查代码修正'); this.oakOption.lifetimes?.ready && this.oakOption.lifetimes?.ready.call(this); const { oakFullpath } = this.state; if (oakFullpath && !this.features.runningTree.isListChildOrStale(oakFullpath)) { await this.refresh(); this.oakOption.lifetimes?.mature && this.oakOption.lifetimes.mature.call(this); } else { this.reRender(); } } else { // listNode修改子结点上的oakPath时能跑到这里,直接reRender this.reRender(); } }); } }, oakId(data) { if (this.oakLifetime !== 'ready') { // 在init状态小程序也可能会调这个observer,先忽略之 return; } // bugfixed: oakId是props,不能用prevState来追踪其前项值 if (this.state.oakFullpath) { if (data) { this.features.runningTree.setId(this.state.oakFullpath, data); } else { this.features.runningTree.unsetId(this.state.oakFullpath); } } }, }, }); function translateListeners(listeners) { if (listeners) { const result = {}; for (const ln in listeners) { result[ln] = function (...args) { // 实测中小程序也是在update之后再调用observer,此时state上的值已经变成后项,因此增加prevState来缓存之 const propNames = ln.split(','); const prev = {}; const next = {}; let dirty = false; propNames.forEach((pn, idx) => { prev[pn] = this.prevState[pn]; next[pn] = args[idx]; if (prev[pn] !== next[pn]) { dirty = true; } }); if (dirty) { listeners[ln].call(this, prev, next); } }; } return result; } } function translatePropertiesToPropertyDefinitions(properties) { const definitions = {}; if (properties) { Object.keys(properties).forEach((prop) => { if (properties[prop] === null) { definitions[prop] = { type: null, value: null, }; return; } switch (typeof properties[prop]) { case 'string': { if (properties[prop]) { definitions[prop] = { type: String, value: properties[prop], }; } else { definitions[prop] = String; } break; } case 'boolean': { definitions[prop] = { type: Boolean, value: properties[prop], }; break; } case 'number': { definitions[prop] = { type: Number, value: properties[prop], }; break; } case 'object': { if (properties[prop] instanceof Array) { if (properties[prop].length > 0) { definitions[prop] = { type: Array, value: properties[prop], }; } else { definitions[prop] = Array; } } else { if (Object.keys(properties[prop]).length > 0) { definitions[prop] = { type: Object, value: properties[prop], }; } else { definitions[prop] = Object; } } break; } case 'function': { Object.assign(definitions, { [prop]: Function, }); } default: { // 小程序也支持传函数 https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html // 其它什么类型都写null,小程序能识别出来 Object.assign(definitions, { [prop]: null, }); break; } } }); } return definitions; } function createComponent(option, features) { const { entity, data, properties, methods, wechatMp, lifetimes, listeners, } = option; const { attached, show, hide, created, detached, ready, moved, error } = lifetimes || {}; const { options, externalClasses } = wechatMp || {}; const { onPullDownRefresh, onReachBottom, ...restMethods } = (methods || {}); const observers = translateListeners(listeners); return Component({ externalClasses, behaviors: [oakBehavior], data: typeof data !== 'function' ? Object.assign({}, data, { oakFullpath: '', oakLoading: !!option.entity && !!option.projection, width: 'xs', }) : { oakFullpath: '', oakLoading: !!option.entity && !!option.projection, width: 'xs', }, properties: Object.assign({}, translatePropertiesToPropertyDefinitions(properties), OakPropertyTypes), methods: { async onPullDownRefresh() { if (!this.state.oakLoading && this.iAmThePage() && !this.props.oakDisablePulldownRefresh && !this.state.oakPullDownRefreshLoading) { try { this.setState({ oakPullDownRefreshLoading: true, }); await (onPullDownRefresh ? onPullDownRefresh.call(this) : this.refresh()); lifetimes?.mature && lifetimes.mature.call(this); this.setState({ oakPullDownRefreshLoading: false, }); await wx.stopPullDownRefresh(); } catch (err) { this.setState({ oakPullDownRefreshLoading: false, }); await wx.stopPullDownRefresh(); throw err; } } else { await wx.stopPullDownRefresh(); } }, async onReachBottom() { if (!this.state.oakLoadingMore && this.iAmThePage() && this.oakOption.isList) { await (onReachBottom ? onReachBottom.call(this) : this.loadMore()); } }, ...restMethods, }, observers, pageLifetimes: { show() { const { show } = this.oakOption.lifetimes || {}; show && show.call(this); this.reRender(); // this.subscribeAll(); }, hide() { const { hide } = this.oakOption.lifetimes || {}; hide && hide.call(this); /** * 不能unsubscribeAll,小程序的页面不会销毁,如果一个页面需要token才能取数据,这时候去了登录页再回来就得依靠feature上的subscribe来refresh * by Xc 20250913 */ // this.unsubscribeAll(); }, resize(resizeOption) { const { resize } = this.oakOption.lifetimes || {}; resize && resize.call(this, resizeOption); } }, lifetimes: { created() { const { setData } = this; this.state = this.data; this.props = this.data; this.prevState = {}; this.setData = (data, callback) => { this.prevState = (0, lodash_1.cloneDeep)(this.data); setData.call(this, data, () => { this.state = this.data; this.props = this.data; callback && callback.call(this); }); }; this.oakOption = option; this.features = features; this.featuresSubscribed = []; created && created.call(this); this._readyCalled = false; this.oakLifetime = 'created'; }, attached() { if (typeof data === 'function') { // ts的编译好像有问题,这里不硬写as过不去 const data2 = data.call(this); this.setData(data2, () => { const fnData = {}; for (const k in this.data) { if (typeof this.data[k] === 'function') { fnData[k] = this.data[k].bind(this); } } if (Object.keys(fnData).length > 0) { this.setData(fnData); } }); } else { const fnData = {}; for (const k in this.data) { if (typeof this.data[k] === 'function') { fnData[k] = this.data[k].bind(this); } } if (Object.keys(fnData).length > 0) { this.setData(fnData); } } this.addFeatureSub('locales', () => this.reRender()); if (option.entity) { this.addFeatureSub('cache', () => this.reRender()); } if (option.features) { option.features.forEach((ele) => { if (typeof ele === 'string') { this.addFeatureSub(ele, () => this.reRender()); } else { (0, assert_1.assert)(typeof ele === 'object'); const { feature, behavior, callback } = ele; this.addFeatureSub(feature, async () => { if (behavior) { switch (behavior) { case 'reRender': { this.reRender(); return; } default: { (0, assert_1.assert)(behavior === 'refresh'); await this.refresh(undefined, true); lifetimes?.mature && lifetimes.mature.call(this); return; } } } else if (callback) { callback.call(this); } else { this.reRender(); } }); } }); } attached && attached.call(this); if (this.props.oakPath || (this.iAmThePage() && this.oakOption.path)) { const pathState = page_common_1.onPathSet.call(this, this.oakOption, this.iAmThePage()); this.setState(pathState, async () => { if (this.oakLifetime === 'detached') { return; } const { oakFullpath } = this.state; if (oakFullpath && !this.features.runningTree.isListChildOrStale(oakFullpath)) { try { await this.refresh(); lifetimes?.mature && lifetimes.mature.call(this); } catch (err) { if (err instanceof types_1.OakException) { err.tag2 = true; } throw err; } } else { this.reRender(); } if (this.oakLifetime === 'detached') { return; } this.oakLifetime = 'attached'; if (this._readyCalled) { // 小程序生命周期里的ready已经调用过了,在这里调用ready try { ready && await ready.call(this); } catch (err) { if (err instanceof types_1.OakException) { err.tag2 = true; } throw err; } if (this.oakLifetime === 'detached') { return; } this.oakLifetime = 'ready'; } }); } else if (!this.oakOption.entity) { this.reRender(); this.oakLifetime = 'attached'; } }, detached() { this.unsubscribeAll(); this.state.oakFullpath && page_common_1.destroyNode.call(this, this.iAmThePage(), this.state.oakFullpath); detached && detached.call(this); this.oakLifetime = 'detached'; }, async ready() { /* // 等oakFullpath构建完成后再ready // 这代码已经看不懂,感觉没用。 by Xc 20240701 if (this.state.oakFullpath) { if (this.props.oakId) { this.features.runningTree.setId( this.state.oakFullpath, this.props.oakId ); } ready && ready.call(this); } else if (!this.oakOption.entity) { ready && ready.call(this); this.oakLifetime = 'ready'; } */ if (this.oakLifetime === 'attached') { try { ready && await ready.call(this); } catch (err) { if (err instanceof types_1.OakException) { err.tag2 = true; } throw err; } this.oakLifetime = 'ready'; } this._readyCalled = true; }, moved() { moved && moved.call(this); }, error(err) { error && error.call(this, err); }, }, }); }