UNPKG

@jianghe/sand-moco

Version:

sand移动端单页核心框架

1,827 lines (1,465 loc) 44.9 kB
/*! * sand-moco.js v2.1.3 * (c) 2019-2021 Jiang He * Released under the MIT License. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var reactRedux = require('react-redux'); var React = require('react'); var ReactDOM = require('react-dom'); var dvaCore = require('dva-core'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM); // @ts-nocheck /* eslint-disable */ /** * 路由部分director */ let router; const dloc = document.location; function dlocHashEmpty() { return dloc.hash === '' || dloc.hash === '#'; } const listener = { mode: 'modern', hash: dloc.hash, history: false, check() { const h = dloc.hash; if (h != this.hash) { this.hash = h; this.onHashChanged(); } }, fire() { if (this.mode === 'modern') { this.history === true ? window.onpopstate() : window.onhashchange(); } else { this.onHashChanged(); } }, init(fn, history) { const self = this; this.history = history; if (!Router.listeners) { Router.listeners = []; } function onchange(onChangeEvent) { for (let i = 0, l = Router.listeners.length; i < l; i++) { Router.listeners[i](onChangeEvent); } } // note IE8 is being counted as 'modern' because it has the hashchange event if ('onhashchange' in window && (document.documentMode === undefined || document.documentMode > 7)) { // At least for now HTML5 history is available for 'modern' browsers only if (this.history === true) { // There is an old bug in Chrome that causes onpopstate to fire even // upon initial page load. Since the handler is run manually in init(), // this would cause Chrome to run it twise. Currently the only // workaround seems to be to set the handler after the initial page load // http://code.google.com/p/chromium/issues/detail?id=63040 setTimeout(function () { window.onpopstate = onchange; }, 500); } else { window.onhashchange = onchange; } this.mode = 'modern'; } else { // // IE support, based on a concept by Erik Arvidson ... // const frame = document.createElement('iframe'); frame.id = 'state-frame'; frame.style.display = 'none'; document.body.appendChild(frame); this.writeFrame(''); if ('onpropertychange' in document && 'attachEvent' in document) { document.attachEvent('onpropertychange', function () { if (event.propertyName === 'location') { self.check(); } }); } window.setInterval(function () { self.check(); }, 50); this.onHashChanged = onchange; this.mode = 'legacy'; } Router.listeners.push(fn); return this.mode; }, destroy(fn) { if (!Router || !Router.listeners) { return; } const listeners = Router.listeners; for (let i = listeners.length - 1; i >= 0; i--) { if (listeners[i] === fn) { listeners.splice(i, 1); } } }, setHash(s) { // Mozilla always adds an entry to the history if (this.mode === 'legacy') { this.writeFrame(s); } if (this.history === true) { window.history.pushState({}, document.title, s); // Fire an onpopstate event manually since pushing does not obviously // trigger the pop event. this.fire(); } else { dloc.hash = s[0] === '/' ? s : `/${s}`; } return this; }, writeFrame(s) { // IE support... const f = document.getElementById('state-frame'); const d = f.contentDocument || f.contentWindow.document; d.open(); d.write(`<script>_hash = '${s}'; onload = parent.listener.syncHash;<script>`); d.close(); }, syncHash() { // IE support... const s = this._hash; if (s != dloc.hash) { dloc.hash = s; } return this; }, onHashChanged() {} }; router = function router(routes) { // 执行方法也返回对象 var a = a(); var a = new a(); if (!(this instanceof Router)) return new Router(routes); this.params = {}; this.routes = {}; this.methods = ['on', 'once', 'after', 'before']; this.scope = []; this._methods = {}; this._insert = this.insert; this.insert = this.insertEx; this.historySupport = (window.history != null ? window.history.pushState : null) != null; this.configure(); this.mount(routes || {}); }; const Router = router; Router.prototype.init = function (r) { const self = this; let routeTo; this.handler = function (onChangeEvent) { const newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash; const url = self.history === true ? self.getPath() : newURL.replace(/.*#/, ''); self.dispatch('on', url.charAt(0) === '/' ? url : `/${url}`); }; listener.init(this.handler, this.history); if (this.history === false) { if (dlocHashEmpty() && r) { dloc.hash = r; } else if (!dlocHashEmpty()) { self.dispatch('on', `/${dloc.hash.replace(/^(#\/|#|\/)/, '')}`); } } else { if (this.convert_hash_in_init) { // Use hash as route routeTo = dlocHashEmpty() && r ? r : !dlocHashEmpty() ? dloc.hash.replace(/^#/, '') : null; if (routeTo) { window.history.replaceState({}, document.title, routeTo); } } else { // Use canonical url routeTo = this.getPath(); } // Router has been initialized, but due to the chrome bug it will not // yet actually route HTML5 history state changes. Thus, decide if should route. if (routeTo || this.run_in_init === true) { this.handler(); } } return this; }; Router.prototype.explode = function () { let v = this.history === true ? this.getPath() : dloc.hash; if (v.charAt(1) === '/') { v = v.slice(1); } return v.slice(1, v.length).split('/'); }; Router.prototype.setRoute = function (i, v, val) { let url = this.explode(); if (typeof i === 'number' && typeof v === 'string') { url[i] = v; } else if (typeof val === 'string') { url.splice(i, v, s); } else { url = [i]; } listener.setHash(url.join('/')); return url; }; Router.prototype.insertEx = function (method, path, route, parent) { if (method === 'once') { method = 'on'; route = function (route) { let once = false; return function () { if (once) return; once = true; return route.apply(this, arguments); }; }(route); } return this._insert(method, path, route, parent); }; Router.prototype.getRoute = function (v) { let ret = v; if (typeof v === 'number') { ret = this.explode()[v]; } else if (typeof v === 'string') { const h = this.explode(); ret = h.indexOf(v); } else { ret = this.explode(); } return ret; }; Router.prototype.destroy = function () { listener.destroy(this.handler); return this; }; Router.prototype.getPath = function () { let path = window.location.pathname; if (path.substr(0, 1) !== '/') { path = `/${path}`; } return path; }; function _every(arr, iterator) { for (let i = 0; i < arr.length; i += 1) { if (iterator(arr[i], i, arr) === false) { return; } } } function _flatten(arr) { let flat = []; for (let i = 0, n = arr.length; i < n; i++) { flat = flat.concat(arr[i]); } return flat; } function _asyncEverySeries(arr, iterator, callback) { if (!arr.length) { return callback(); } let completed = 0; (function iterate() { iterator(arr[completed], function (err) { if (err || err === false) { callback(err); callback = function callback() {}; } else { completed += 1; if (completed === arr.length) { callback(); } else { iterate(); } } }); })(); } function paramifyString(str, params, mod) { mod = str; for (const param in params) { if (params.hasOwnProperty(param)) { mod = params[param](str); if (mod !== str) { break; } } } return mod === str ? '([._a-zA-Z0-9-%()]+)' : mod; } function regifyString(str, params) { let matches; let last = 0; let out = ''; while (matches = str.substr(last).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/)) { last = matches.index + matches[0].length; matches[0] = matches[0].replace(/^\*/, '([_.()!\\ %@&a-zA-Z0-9-]+)'); out += str.substr(0, matches.index) + matches[0]; } str = out += str.substr(last); const captures = str.match(/:([^\/]+)/gi); let capture; let length; if (captures) { length = captures.length; for (let i = 0; i < length; i++) { capture = captures[i]; if (capture.slice(0, 2) === '::') { str = capture.slice(1); } else { str = str.replace(capture, paramifyString(capture, params)); } } } return str; } function terminator(routes, delimiter, start, stop) { let last = 0; let left = 0; let right = 0; var start = (start || '(').toString(); var stop = (stop || ')').toString(); let i; for (i = 0; i < routes.length; i++) { const chunk = routes[i]; if (chunk.indexOf(start, last) > chunk.indexOf(stop, last) || ~chunk.indexOf(start, last) && !~chunk.indexOf(stop, last) || !~chunk.indexOf(start, last) && ~chunk.indexOf(stop, last)) { left = chunk.indexOf(start, last); right = chunk.indexOf(stop, last); if (~left && !~right || !~left && ~right) { const tmp = routes.slice(0, (i || 1) + 1).join(delimiter); routes = [tmp].concat(routes.slice((i || 1) + 1)); } last = (right > left ? right : left) + 1; i = 0; } else { last = 0; } } return routes; } const QUERY_SEPARATOR = /\?.*/; Router.prototype.configure = function (options) { options = options || {}; for (let i = 0; i < this.methods.length; i++) { this._methods[this.methods[i]] = true; } this.recurse = options.recurse || this.recurse || false; this.async = options.async || false; this.delimiter = options.delimiter || '/'; this.strict = typeof options.strict === 'undefined' ? true : options.strict; this.notfound = options.notfound; this.resource = options.resource; this.history = options.html5history && this.historySupport || false; this.run_in_init = this.history === true && options.run_handler_in_init !== false; this.convert_hash_in_init = this.history === true && options.convert_hash_in_init !== false; this.every = { after: options.after || null, before: options.before || null, on: options.on || null }; return this; }; Router.prototype.param = function (token, matcher) { if (token[0] !== ':') { token = `:${token}`; } const compiled = new RegExp(token, 'g'); this.params[token] = function (str) { return str.replace(compiled, matcher.source || matcher); }; return this; }; Router.prototype.on = Router.prototype.route = function (method, path, route) { const self = this; if (!route && typeof path === 'function') { route = path; path = method; method = 'on'; } if (Array.isArray(path)) { return path.forEach(function (p) { self.on(method, p, route); }); } if (path.source) { path = path.source.replace(/\\\//gi, '/'); } if (Array.isArray(method)) { return method.forEach(function (m) { self.on(m.toLowerCase(), path, route); }); } path = path.split(new RegExp(this.delimiter)); path = terminator(path, this.delimiter); this.insert(method, this.scope.concat(path), route); }; Router.prototype.path = function (path, routesFn) { const length = this.scope.length; if (path.source) { path = path.source.replace(/\\\//gi, '/'); } path = path.split(new RegExp(this.delimiter)); path = terminator(path, this.delimiter); this.scope = this.scope.concat(path); routesFn.call(this, this); this.scope.splice(length, path.length); }; Router.prototype.dispatch = function (method, path, callback) { const self = this; let fns = this.traverse(method, path.replace(QUERY_SEPARATOR, ''), this.routes, ''); const invoked = this._invoked; let after; this._invoked = true; if (!fns || fns.length === 0) { this.last = []; if (typeof this.notfound === 'function') { this.invoke([this.notfound], { method, path }, callback); } return false; } if (this.recurse === 'forward') { fns = fns.reverse(); } function updateAndInvoke() { self.last = fns.after; self.invoke(self.runlist(fns), self, callback); } after = this.every && this.every.after ? [this.every.after].concat(this.last) : [this.last]; if (after && after.length > 0 && invoked) { if (this.async) { this.invoke(after, this, updateAndInvoke); } else { this.invoke(after, this); updateAndInvoke(); } return true; } updateAndInvoke(); return true; }; Router.prototype.invoke = function (fns, thisArg, callback) { const self = this; let _apply2; if (this.async) { _apply2 = function apply(fn, next) { if (Array.isArray(fn)) { return _asyncEverySeries(fn, _apply2, next); } else if (typeof fn === 'function') { fn.apply(thisArg, (fns.captures || []).concat(next)); } }; _asyncEverySeries(fns, _apply2, function () { if (callback) { callback.apply(thisArg, arguments); } }); } else { _apply2 = function _apply(fn) { if (Array.isArray(fn)) { return _every(fn, _apply2); } else if (typeof fn === 'function') { return fn.apply(thisArg, fns.captures || []); } else if (typeof fn === 'string' && self.resource) { self.resource[fn].apply(thisArg, fns.captures || []); } }; _every(fns, _apply2); } }; Router.prototype.traverse = function (method, path, routes, regexp, filter) { let fns = []; let current; let exact; let match; let next; function filterRoutes(routes) { if (!filter) { return routes; } function deepCopy(source) { const result = []; for (let i = 0; i < source.length; i++) { result[i] = Array.isArray(source[i]) ? deepCopy(source[i]) : source[i]; } return result; } function applyFilter(fns) { for (let i = fns.length - 1; i >= 0; i--) { if (Array.isArray(fns[i])) { applyFilter(fns[i]); if (fns[i].length === 0) { fns.splice(i, 1); } } else if (!filter(fns[i])) { fns.splice(i, 1); } } } const newRoutes = deepCopy(routes); newRoutes.matched = routes.matched; newRoutes.captures = routes.captures; newRoutes.after = routes.after.filter(filter); applyFilter(newRoutes); return newRoutes; } if (path === this.delimiter && routes[method]) { next = [[routes.before, routes[method]].filter(Boolean)]; next.after = [routes.after].filter(Boolean); next.matched = true; next.captures = []; return filterRoutes(next); } for (const r in routes) { if (routes.hasOwnProperty(r) && (!this._methods[r] || this._methods[r] && typeof routes[r] === 'object' && !Array.isArray(routes[r]))) { current = exact = regexp + this.delimiter + r; if (!this.strict) { exact += `[${this.delimiter}]?`; } match = path.match(new RegExp(`^${exact}`)); if (!match) { continue; } if (match[0] && match[0] == path && routes[r][method]) { next = [[routes[r].before, routes[r][method]].filter(Boolean)]; next.after = [routes[r].after].filter(Boolean); next.matched = true; next.captures = match.slice(1); if (this.recurse && routes === this.routes) { next.push([routes.before, routes.on].filter(Boolean)); next.after = next.after.concat([routes.after].filter(Boolean)); } return filterRoutes(next); } next = this.traverse(method, path, routes[r], current); if (next.matched) { if (next.length > 0) { fns = fns.concat(next); } if (this.recurse) { fns.push([routes[r].before, routes[r].on].filter(Boolean)); next.after = next.after.concat([routes[r].after].filter(Boolean)); if (routes === this.routes) { fns.push([routes.before, routes.on].filter(Boolean)); next.after = next.after.concat([routes.after].filter(Boolean)); } } fns.matched = true; fns.captures = next.captures; fns.after = next.after; return filterRoutes(fns); } } } return false; }; Router.prototype.insert = function (method, path, route, parent) { let methodType; let parentType; let isArray; let nested; let part; path = path.filter(function (p) { return p && p.length > 0; }); parent = parent || this.routes; part = path.shift(); if (/\:|\*/.test(part) && !/\\d|\\w/.test(part)) { part = regifyString(part, this.params); } if (path.length > 0) { parent[part] = parent[part] || {}; return this.insert(method, path, route, parent[part]); } if (!part && !path.length && parent === this.routes) { methodType = typeof parent[method]; switch (methodType) { case 'function': parent[method] = [parent[method], route]; return; case 'object': parent[method].push(route); return; case 'undefined': parent[method] = route; return; } return; } parentType = typeof parent[part]; isArray = Array.isArray(parent[part]); if (parent[part] && !isArray && parentType == 'object') { methodType = typeof parent[part][method]; switch (methodType) { case 'function': parent[part][method] = [parent[part][method], route]; return; case 'object': parent[part][method].push(route); return; case 'undefined': parent[part][method] = route; return; } } else if (parentType == 'undefined') { nested = {}; nested[method] = route; parent[part] = nested; return; } throw new Error(`Invalid route context: ${parentType}`); }; Router.prototype.extend = function (methods) { const self = this; const len = methods.length; let i; function extend(method) { self._methods[method] = true; self[method] = function () { const extra = arguments.length === 1 ? [method, ''] : [method]; self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments))); }; } for (i = 0; i < len; i++) { extend(methods[i]); } }; Router.prototype.runlist = function (fns) { const runlist = this.every && this.every.before ? [this.every.before].concat(_flatten(fns)) : _flatten(fns); if (this.every && this.every.on) { runlist.push(this.every.on); } runlist.captures = fns.captures; runlist.source = fns.source; return runlist; }; Router.prototype.mount = function (routes, path) { if (!routes || typeof routes !== 'object' || Array.isArray(routes)) { return; } const self = this; path = path || []; if (!Array.isArray(path)) { path = path.split(self.delimiter); } function insertOrMount(route, local) { let rename = route; const parts = route.split(self.delimiter); const routeType = typeof routes[route]; const isRoute = parts[0] === '' || !self._methods[parts[0]]; const event = isRoute ? 'on' : rename; if (isRoute) { rename = rename.slice((rename.match(new RegExp(`^${self.delimiter}`)) || [''])[0].length); parts.shift(); } if (isRoute && routeType === 'object' && !Array.isArray(routes[route])) { local = local.concat(parts); self.mount(routes[route], local); return; } if (isRoute) { local = local.concat(rename.split(self.delimiter)); local = terminator(local, self.delimiter); } self.insert(event, local, routes[route]); } for (const route in routes) { if (routes.hasOwnProperty(route)) { insertOrMount(route, path.slice(0)); } } }; var router$1 = router; // import { PAGE_ENUM } from '../common/config'; /** * 页面栈类 */ class Stacks { constructor() { // 页面集合 this.pageMap = {}; // 当前显示的页面队列 this.pageFlag = []; this.pageMap = {}; this.pageFlag = []; } /** * 页面入栈的方法 * @param page 页面实例 */ push(page) { this.pageFlag.push(page.pageId); this.pageMap[page.pageId] = page; } /** * 页面出栈的方法 */ pop() { return this.pageMap[this.pageFlag.pop()]; } /** * 移除栈中指定的pageId */ removeStacks(page) { const flagIndex = this.pageExist(page); if (flagIndex >= 0) { this.pageFlag.splice(flagIndex, 1); return true; } return false; } /** * 查询栈中是否存在page对象 * @param page page对象 * @return this.pageFlag的下标(-1为没找到) */ pageExist(page) { for (let i = 0; i < this.pageFlag.length; i++) { if (this.pageFlag[i] === page.pageId) { return i; } } return -1; } // /** // * 执行goBack时根据goBack的入参返回待返回页的pageId,如果未找到符合条件的返回页返回false // */ // findPageId(obj): string | boolean { // const { pagePath, pageKey } = obj; // if (pagePath) { // /** // * 传入了pagePath,返回距离栈顶最近的满足pagePath,pageKey的页面 // */ // for (let n = this.pageFlag.length - 2; n >= 0; n--) { // const pg = this.getPage(n); // if ( // pg && // pg.pagePath === pagePath && // (!pageKey || pg.pageKey === pageKey) // ) { // // 找到离栈顶最近的正常页 // return pg.pageId; // } // } // } else { // /** // * 未传入pagePath // */ // // 获取栈顶页 // const topPage = this.getPage(-1); // if (topPage && topPage.pageType === PAGE_ENUM.NORMAL) { // // 栈顶页是正常页返回 // for (let n = this.pageFlag.length - 2; n >= 0; n--) { // const pg = this.getPage(n); // if (pg && pg.pageType === PAGE_ENUM.NORMAL) { // // 找到离栈顶最近的正常页 // return pg.pageId; // } // } // } else if (topPage && topPage.pageType === 'temp') { // // 栈顶页是临时页返回 // const tempPage = this.getPage(-2); // if (tempPage) { // return tempPage.pageId; // } // } // } // console.warn('返回页面不存在'); // return false; // } /** * 获取栈中的page对象 * @param index 索引值如果为+是数组下标,如果为-是倒数 * @return {*} */ getPage(index) { if (this.pageFlag.length === 0) { return false; } const n = index % this.pageFlag.length; if (n < 0) { return this.pageMap[this.pageFlag[this.pageFlag.length + n]]; } else { return this.pageMap[this.pageFlag[n]]; } } } /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ /** * 获取8位随机字符串 */ function getRandom() { return Math.random() // 生成随机数字, eg: 0.123456 .toString(36) // 转化成36进制 : "0.4fzyo82mvyr" .slice(-8); } /** * 节流 * @param fn * @param time */ function throttle(fn, time) { let lastTime = 0; return (...args) => { const nowTime = +new Date(); if (!lastTime || nowTime - lastTime > time) { fn(...args); lastTime = nowTime; } }; } /** * obj转url参数 * @param data */ function obj2url(data) { const result = []; // eslint-disable-next-line for (const key in data) { const value = data[key]; if (typeof value !== 'string') { // value不是字符串,报错 console.error('参数value只支持字符串'); return ''; } result.push(`${key}=${value}`); } return result.join('&'); } /** * 校验类型item的tostring是不是为type * @param item * @param type * @return {boolean} */ function isType(item, type) { return {}.toString.call(item) === type; } /** * 页面类型枚举 * normal正常页 * temp临时页 */ const PAGE_ENUM$2 = { NORMAL: 'normal', TEPM: 'temp' }; /** * 跳页类型 * init 初始化 * go 前进 * goBack 后退 */ const JUMP_METHOD_ENUM$1 = { INIT: 'init', GO: 'go', GO_BACK: 'goBack' }; /** * 根节点 */ let ROOT_ELE = '#sand-root'; const setRootEle$1 = id => { ROOT_ELE = id; }; const getRootEle$1 = () => { return ROOT_ELE; }; /** * 页面根节点类名 */ const PAGE_ROOT_ELE$1 = 'sand-main-box'; /** * 页面生命周期枚举 */ const LIFE_CYCLE$1 = { onCreate: 'onCreate', onPause: 'onPause', onResume: 'onResume', onDestroy: 'onDestroy' }; const CONSTANTS = { PAGE_ENUM: PAGE_ENUM$2, JUMP_METHOD_ENUM: JUMP_METHOD_ENUM$1, LIFE_CYCLE: LIFE_CYCLE$1, PAGE_ROOT_ELE: PAGE_ROOT_ELE$1, setRootEle: setRootEle$1, getRootEle: getRootEle$1 }; function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z = "body,\nhtml {\n margin: 0;\n padding: 0;\n}\n#sand-root {\n position: relative;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0);\n -webkit-transform: translate3d(0, 0, 0);\n -moz-transform: translate3d(0, 0, 0);\n -ms-transform: translate3d(0, 0, 0);\n -o-transform: translateZ(0);\n transform: translate3d(0, 0, 0);\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n -ms-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000;\n -moz-perspective: 1000;\n -ms-perspective: 1000;\n perspective: 1000;\n}\n.sand-main-box {\n width: 7.5rem;\n height: 100vh;\n position: absolute;\n top: 0;\n left: 0;\n}\n"; styleInject(css_248z); const PAGE_ROOT_ELE = CONSTANTS.PAGE_ROOT_ELE; /** * 将页面组件渲染至sand-main-box,并返回真实dom */ function getPageDom(page) { const Component = page.component, moco = page.moco; // 生成页面根节点 const div = document.createElement('div'); // 设置页面类名 div.setAttribute('class', PAGE_ROOT_ELE); // 渲染界面 ReactDOM__default['default'].render( /*#__PURE__*/React__default['default'].createElement(reactRedux.Provider, { store: moco.dvaApp.getStore() }, /*#__PURE__*/React__default['default'].createElement(Component, { mocoPage: page })), div); return div; } /* eslint-disable @typescript-eslint/no-explicit-any */ const PAGE_ENUM$1 = CONSTANTS.PAGE_ENUM, LIFE_CYCLE = CONSTANTS.LIFE_CYCLE; /** * 页面类 */ class Page { constructor(opts) { // 页面id,保存到路由栈中的标志位 this.pageId = ''; // 页面路由 this.pagePath = ''; // 页面键值,和页面id的区别是页面id是系统自动生成的,而pagekey是使用者跳转页面是传入的标识页面的key this.pageKey = ''; // 页面类型(normal正常页,temp临时页) this.pageType = PAGE_ENUM$1.NORMAL; // 页面对应的React组件 this.component = null; // 页面对应的真实dom this.dom = null; // Moco实例 this.moco = null; // dva状态模型 this.model = false; // 页面传入的监听器hook this.monitorHook = { // 页面创建 // eslint-disable-next-line [LIFE_CYCLE.onCreate]: _page => {}, // 页面挂起 // eslint-disable-next-line [LIFE_CYCLE.onPause]: _page => {}, // 页面重回 // eslint-disable-next-line [LIFE_CYCLE.onResume]: _page => {}, // 页面销毁 // eslint-disable-next-line [LIFE_CYCLE.onDestroy]: _page => {} }; /** * 监听器方法 */ // eslint-disable-next-line @typescript-eslint/ban-types this.monitor = (lifeCycleName, hook) => { if (LIFE_CYCLE[lifeCycleName]) { this.monitorHook[LIFE_CYCLE[lifeCycleName]] = hook; } else { console.error('生命周期事件名错误'); } }; const pagePath = opts.pagePath, component = opts.component, _opts$model = opts.model, model = _opts$model === void 0 ? false : _opts$model, _opts$pageKey = opts.pageKey, pageKey = _opts$pageKey === void 0 ? '' : _opts$pageKey, _opts$pageType = opts.pageType, pageType = _opts$pageType === void 0 ? PAGE_ENUM$1.NORMAL : _opts$pageType; this.pagePath = pagePath; this.pageKey = pageKey; this.pageType = pageType; this.component = component; this.pageId = `${this.pagePath}${getRandom()}`; // 获取moco实例 this.moco = getMoco(); // 有dva实例和该页面有对应的dva模型,在页面创建时注册dva模型 if (this.moco.dvaApp && model) { this.model = model; this.moco.dvaApp.setModel(this.model); } // 创建dom this.createDom(); } /** * 页面创建的方法 */ onCreate() { // 有hook时执行hook this.monitorHook[LIFE_CYCLE.onCreate] && this.monitorHook[LIFE_CYCLE.onCreate](this); } /** * 页面暂停,挂起 */ onPause() { this.monitorHook[LIFE_CYCLE.onPause] && this.monitorHook[LIFE_CYCLE.onPause](this); } /** * 重新回到页面的方法 */ onResume() { this.monitorHook[LIFE_CYCLE.onResume] && this.monitorHook[LIFE_CYCLE.onResume](this); } /** * 销毁页面的方法 */ onDestroy() { try { if (this.moco) { // 移除栈 this.moco.stacks.removeStacks(this); // 移除堆 this.moco.stacks.pageMap[this.pageId] = ''; delete this.moco.stacks.pageMap[this.pageId]; } } catch (e) { console.error('移除堆区page对象失败'); } this.monitorHook[LIFE_CYCLE.onDestroy] && this.monitorHook[LIFE_CYCLE.onDestroy](this); } /** * 创建dom */ createDom() { if (!this.dom) { this.dom = getPageDom(this); } } } const getRootEle = CONSTANTS.getRootEle; /** * 首次页面显示的转场动画,直接显示 * @param showPage 需要显示的页面 */ function firstShowTransition(showPage) { const rootEle = getRootEle(); // 获取渲染根节点 const parentEle = window.document.querySelector(rootEle); // 添加到父节点中 parentEle && parentEle.appendChild(showPage.dom); } /** * 基础进入转场动画 * @param hidePage 隐藏的页 * @param showPage 当前页 */ function baseInTransition(hidePage, showPage, endCallBack) { const rootEle = getRootEle(); // 要显示页 const showDom = showPage.dom; // 上一页 const upPageDom = hidePage.dom; // 获取根节点 const parentEle = window.document.querySelector(rootEle); showDom.style.display = 'none'; showDom.style.left = '100%'; showDom.style.zIndex = '99'; showDom.style.display = 'block'; parentEle && parentEle.appendChild(showDom); setTimeout(function () { showDom.style.transition = 'left 0.4s ease'; showDom.style.left = '0'; }, 20); setTimeout(function () { showDom.style.transition = 'none'; showDom.style.zIndex = 'none'; upPageDom.style.display = 'none'; parentEle && parentEle.removeChild(upPageDom); endCallBack(); }, 600); } /** * 基础转场动画 * @param hidePage 隐藏的页 * @param showPage 当前页 */ function baseOutTransition(hidePage, showPage, endCallBack) { const rootEle = getRootEle(); // 要显示页 const showDom = showPage.dom; // 上一页 const upPageDom = hidePage.dom; // 获取根节点 const parentEle = window.document.querySelector(rootEle); showDom.style.display = 'none'; showDom.style.left = '-100%'; showDom.style.zIndex = '99'; showDom.style.display = 'block'; parentEle && parentEle.appendChild(showDom); setTimeout(function () { showDom.style.transition = 'left 0.4s ease'; showDom.style.left = '0'; }, 20); setTimeout(function () { showDom.style.transition = 'none'; showDom.style.zIndex = 'none'; upPageDom.style.display = 'none'; parentEle && parentEle.removeChild(upPageDom); endCallBack(); }, 600); } /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /** * 基于dva-core封装的类 挂载到moco实例上 */ class DvaApp { constructor() { this.dva = null; /** * 获取dva实例 */ this.getDvaApp = () => { if (!this.dva) { // 创建dva-core实例 this.dva = dvaCore.create({}, {}); if (!this.dva._store) { this.dva.start.call(this.dva); } } return this.dva; }; /** * 获取store */ this.getStore = () => { return !this.dva ? this.getDvaApp()._store : this.dva._store; }; /** * 注册dva model 使用replaceModel方法如果存在直接替换 * @param {*} model */ this.setModel = model => { return !this.dva ? this.getDvaApp().replaceModel(model) : this.dva.replaceModel(model); }; /** * 注销dva model */ this.unModel = namespace => { if (!this.dva) { this.getDvaApp().unmodel(namespace); } else { this.dva.unmodel(namespace); } }; this.dva = this.getDvaApp(); } } /* eslint-disable @typescript-eslint/no-explicit-any */ const JUMP_METHOD_ENUM = CONSTANTS.JUMP_METHOD_ENUM, PAGE_ENUM = CONSTANTS.PAGE_ENUM; /** * 初始化的目标页信息 */ const initTargetPageInfo = { // 跳页方法go||goback jumpMethod: JUMP_METHOD_ENUM.INIT, // 页面路由 pagePath: '', // 参数 args: {}, // key pageKey: '', // 页面类型 pageType: PAGE_ENUM.NORMAL, // 转场动画 transition: baseInTransition }; /** * Moco类型 */ class Moco { constructor() { /** * 来源页面 */ this.sourcePage = null; /** * 页面栈 */ this.stacks = null; /** * 跳页信息 */ this.targetPageInfo = null; /** * 在页面跳转中 */ this.isInJump = false; /** * dva实例,用于页面之间转态管理 * https://github.com/dvajs/dva/blob/master/packages/dva-core/src/index.js */ this.dvaApp = null; /** * 命中路由时 */ this.hitPath = opts => { if (this.targetPageInfo.jumpMethod === JUMP_METHOD_ENUM.INIT) { // 初始化命中路由 this.newPage(opts); } else if (this.targetPageInfo.jumpMethod === JUMP_METHOD_ENUM.GO) { // go跳页命中路由 this.goPage(opts); } else { // goBack跳页命中路由 this.backPage(); } }; /** * 转场动画结束回调 */ this.transitionEnd = (hidePage, showPage, jumpMethod) => { if (jumpMethod === JUMP_METHOD_ENUM.GO) { // 前进转场动画结束 // 执行下一个页面创建生命周期和上一个页面的pause生命周期 hidePage.onPause(); showPage.onCreate(); // 重置targetPageInfo this.targetPageInfo = initTargetPageInfo; } if (jumpMethod === JUMP_METHOD_ENUM.GO_BACK) { // 返回转场动画结束 // 执行下一个页面创建生命周期和上一个页面的pause生命周期 hidePage.onDestroy(); showPage.onResume(); // 重置targetPageInfo this.targetPageInfo = initTargetPageInfo; } this.isInJump = false; }; /** * 返回页面 * @param opts 需要显示的页面 */ this.backPage = () => { if (this.stacks.pageFlag.length > 1) { // 取出栈顶页面 const hidePage = this.stacks.getPage(-1); // 取出跳转页面 const showPage = this.stacks.getPage(-2); if (hidePage && showPage) { // 在跳页流程中 this.isInJump = true; // 前一个页面 this.sourcePage = hidePage; // 转场动画结束回调 const endCallBack = () => { this.transitionEnd( // 隐藏页 hidePage, // 显示页 showPage, // 跳转方式 this.targetPageInfo.jumpMethod); }; // 执行转场动画 this.targetPageInfo.transition(hidePage, showPage, endCallBack); } else { console.error('无法取出栈顶页面'); } } else { console.error('没有可以返回的页面'); } }; /** * 显示页面 * @param opts 需要显示的页面 */ this.goPage = opts => { // 页面栈中有几个页 const len = this.stacks.pageFlag.length; if (len > 0) { const component = opts.component, pagePath = opts.pagePath, model = opts.model; // 实例化一个新页面 const showPage = new Page({ pagePath, component, model }); // 取出栈顶页面 const hidePage = this.stacks.getPage(-1); if (hidePage) { // 在跳页流程中 this.isInJump = true; // 前一个页面 this.sourcePage = hidePage; // 不存在栈中 压栈 this.stacks.push(showPage); // 转场动画结束回调 const endCallBack = () => { this.transitionEnd( // 隐藏页 hidePage, // 显示页 showPage, // 跳转方式 this.targetPageInfo.jumpMethod); }; // 执行转场动画 this.targetPageInfo.transition(hidePage, showPage, endCallBack); } else { console.error('无法取出栈顶页面'); } } }; /** * 创建新页(栈中无页面) * @param page 页面对象 */ this.newPage = opts => { const component = opts.component, pagePath = opts.pagePath, model = opts.model; // 实例化一个新页面 const page = new Page({ pagePath, component, model }); // 将页面入栈 this.stacks.push(page); // 执行默认显示动画 firstShowTransition(page); // 执行页面的生命周期方法 page.onCreate(); // 重置targetPageInfo this.targetPageInfo = initTargetPageInfo; }; /** * 前进的跳转 * @param obj 跳转对象 */ this.go = throttle(opts => { // 在转场动画中 直接return if (this.isInJump) return; const pagePath = opts.pagePath, _opts$args = opts.args, args = _opts$args === void 0 ? {} : _opts$args, _opts$pageKey = opts.pageKey, pageKey = _opts$pageKey === void 0 ? '' : _opts$pageKey, _opts$pageType = opts.pageType, pageType = _opts$pageType === void 0 ? PAGE_ENUM.NORMAL : _opts$pageType, _opts$transition = opts.transition, transition = _opts$transition === void 0 ? baseInTransition : _opts$transition; if (!pagePath) { console.error('go方法缺少pagePath参数'); return; } if (!isType(args, '[object Object]')) { console.error('go方法args参数不合法,只能是[object Object]'); return; } // obj转url const urlArgs = obj2url(args); // 记录目标页信息 this.targetPageInfo = { jumpMethod: JUMP_METHOD_ENUM.GO, pagePath, args, pageKey, pageType, transition }; // 修改url window.location.hash = `#${pagePath}${urlArgs ? `?${urlArgs}` : ''}`; }, 300); /** * 返回的跳转 * @param obj 跳转对象 */ this.goBack = throttle((opts = {}) => { // 在转场动画中 直接return if (this.isInJump) return; const _opts$args2 = opts.args, args = _opts$args2 === void 0 ? {} : _opts$args2, _opts$pageKey2 = opts.pageKey, pageKey = _opts$pageKey2 === void 0 ? '' : _opts$pageKey2, _opts$pageType2 = opts.pageType, pageType = _opts$pageType2 === void 0 ? PAGE_ENUM.NORMAL : _opts$pageType2, _opts$transition2 = opts.transition, transition = _opts$transition2 === void 0 ? baseOutTransition : _opts$transition2; if (!isType(args, '[object Object]')) { console.error('go方法args参数不合法,只能是[object Object]'); return; } // obj转url const urlArgs = obj2url(args); // 计算出要跳转的页面 if (this.stacks.pageFlag.length > 1) { const backPage = this.stacks.getPage(-2); if (backPage) { // 记录目标页信息 this.targetPageInfo = { jumpMethod: JUMP_METHOD_ENUM.GO_BACK, pagePath: backPage.pagePath, args, pageKey, pageType, transition }; // 修改url window.location.hash = `#${backPage.pagePath}${urlArgs ? `?${urlArgs}` : ''}`; } } else { console.error('没有可以返回的页面'); } }, 300); // 创建页面栈 this.stacks = new Stacks(); // 初始化目标页面信息 this.targetPageInfo = initTargetPageInfo; // 创建dva实例 this.dvaApp = new DvaApp(); } } /** * Moco的实例化方法,Moco是单例模式,每个应用只允许存在一个Moco实例 */ let moco = null; function getMoco() { if (!moco) { moco = new Moco(); } return moco; } /* eslint-disable @typescript-eslint/no-explicit-any */ const setRootEle = CONSTANTS.setRootEle; /** * 合并路由生成路由配置, 获取路由配置的方法 * @param webpackRouteConfig webpack读取的路由配置 */ function getRoutes(webpackRouteConfig) { // 子路由 const routes = []; // 遍历webpack读取的路由配置 webpackRouteConfig.keys().forEach(key => { let config = webpackRouteConfig(key); // 取出拆分的路由配置 config = config.default || config; // 合并到主路由中 routes.push(config); }); // 完整路由 return routes; } /** * 获取路由配置,并根据路由配置,形成hash监听 */ function addListen(opts) { const webpackRouteConfig = opts.webpackRouteConfig, defaultRoute = opts.defaultRoute, _opts$rootEle = opts.rootEle, rootEle = _opts$rootEle === void 0 ? '' : _opts$rootEle; // 根据外部入参设置根节点选择器 if (rootEle) { setRootEle(rootEle); } // 合并pages下的路由 const routes = getRoutes(webpackRouteConfig); // 实例化一个moco const moco = getMoco(); // 根据路由配置生成路由集合 const routeMap = {}; for (let index = 0; index < routes.length; index++) { const route = routes[index]; const component = route.component, path = route.path, _route$enable = route.enable, enable = _route$enable === void 0 ? false : _route$enable, _route$model = route.model, model = _route$model === void 0 ? false : _route$model; if (enable) { routeMap[path] = { // 当前路由hash值命中前 before: () => {}, // 当路由匹配成功时 on: () => { // 匹配路径是调用hitPath moco.hitPath({ pagePath: path, component, model }); }, // 当离开当前注册路径时,需要执行的方法 after: () => {} }; } } // 根据路由集合,添加监听 router$1(routeMap).configure({ notfound: () => { // 没发现路由时 console.log('没发现路由'); } }).init(defaultRoute || '/'); } Object.defineProperty(exports, 'connect', { enumerable: true, get: function () { return reactRedux.connect; } }); exports.CONSTANTS = CONSTANTS; exports.Moco = Moco; exports.Page = Page; exports.addListen = addListen; exports.getMoco = getMoco; //# sourceMappingURL=sand-moco.js.map