util.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. import { warn, err } from '../helpers/warn';
  2. import { isObject, resolveRule, copyObject } from '../helpers/util';
  3. import { proxyBeforeEnter } from './proxy/proxy';
  4. import { Global } from '../helpers/config';
  5. const pagesConfigReg = /props:\s*\(.*\)\s*(\([\s\S]*\))\s*},/;
  6. const pagesConfigRegCli = /props:\s*Object\.assign\s*(\([\s\S]*\))\s*},/; // 脚手架项目
  7. const defRoutersReg = /props:\s*{([\s\S]+)}\s*},/;
  8. /**
  9. * 解析验证当前的 component 选项是否配置正确 只有vueRouterDev:false 才会调用此方法
  10. * @param {Function|Object} component
  11. * @param {Object} item
  12. * @param {Boolean} useUniConfig
  13. */
  14. export const resolveRender = function ({
  15. component,
  16. components,
  17. }, item, useUniConfig) {
  18. if (components != null) {
  19. warn(`vueRouterDev:false时 路由表配置中 ‘components’ 无效,\r\n\r\n ${JSON.stringify(item)}`);
  20. }
  21. if (useUniConfig == true) { // 采用uni-pages.json中的配置时component可以为空
  22. return false;
  23. }
  24. if (item.path == '*') { // 唯独这个情况在vue-router中可以不用component
  25. return true;
  26. }
  27. if (component == null) {
  28. return err(`vueRouterDev:false时 路由表中 ‘component’ 选项不能为空:\r\n\r\n ${JSON.stringify(item)}`);
  29. }
  30. if (component.constructor === Function) {
  31. item.component = {
  32. render: component,
  33. };
  34. } else if (component.constructor === Object) {
  35. if (component.render == null || component.render.constructor !== Function) {
  36. err(`vueRouterDev:false时 路由表配置中 ‘render’ 函数缺失或类型不正确:\r\n\r\n ${JSON.stringify(item)}`);
  37. }
  38. } else {
  39. err(
  40. `vueRouterDev:false时 路由表配置中 ‘component’ 选项仅支持 Function、Object 类型。并确保 Object 类型时传递了 ‘render’ 函数 :\r\n\r\n ${JSON.stringify(item)}`,
  41. );
  42. }
  43. };
  44. /**
  45. * 递归解析 H5配置中有存在嵌套对象的情况,优先以path为key存储。没有则找aliasPath作为key
  46. * @param {Object} objRoutes
  47. * @param {Array} children
  48. * @param {Boolean} useUniConfig 是否使用pages.json下的页面配置
  49. */
  50. export const resloveChildrenPath = function (objRoutes, children, useUniConfig) {
  51. for (let i = 0; i < children.length; i += 1) {
  52. const item = children[i];
  53. resolveRender(item, item, useUniConfig);
  54. if (item.path != null) {
  55. objRoutes[item.path] = {
  56. ...item,
  57. ...{
  58. _ROUTERPATH: true, // 使用page.json中的path为路径
  59. },
  60. };
  61. } else {
  62. objRoutes[item.aliasPath] = {
  63. ...item,
  64. ...{
  65. _ROUTERPATH: false,
  66. },
  67. };
  68. }
  69. if (item.children && item.children.constructor === Array) {
  70. resloveChildrenPath(objRoutes, item.children, useUniConfig);
  71. }
  72. }
  73. };
  74. /**
  75. * 格式化原始路由表
  76. * @param {Object} routes 路由表
  77. * @param {Boolean} userRoute 是否为用户自己配置的路由表
  78. * @param {Boolean} H5CONFIG
  79. */
  80. export const fromatRoutes = function (routes, userRoute, {
  81. vueRouterDev,
  82. useUniConfig,
  83. }) {
  84. if (userRoute && vueRouterDev) { // 如果是用户的路由表并且 完全采用vueRouter开发 则不作处理直接返回
  85. return routes;
  86. }
  87. const objRoutes = {};
  88. for (let i = 0; i < routes.length; i += 1) {
  89. const item = routes[i];
  90. const path = item.path === '/' ? item.alias : item.path;
  91. if (userRoute) {
  92. if (item.children && item.children.constructor === Array) {
  93. resloveChildrenPath(objRoutes, item.children, useUniConfig);
  94. }
  95. resolveRender(item, item, useUniConfig); // 是否使用pages.json下的页面配置
  96. }
  97. objRoutes[path] = {
  98. ...item,
  99. ...{
  100. _PAGEPATH: path.substring(1),
  101. },
  102. };
  103. }
  104. return objRoutes;
  105. };
  106. /**
  107. * 解析vueRouter中 component 下 render函数中的配置信息
  108. * @param {String} FunStr
  109. */
  110. export const getFuntionConfig = function (FunStr) {
  111. let matchText = FunStr.match(pagesConfigReg);
  112. let prefix = '';
  113. if (matchText == null) { // 是uni-app自带的默认路由及配置 也可能是脚手架项目
  114. matchText = FunStr.match(pagesConfigRegCli);
  115. if (matchText == null) { // 确认不是脚手架项目
  116. try {
  117. // eslint-disable-next-line
  118. matchText = FunStr.match(defRoutersReg)[1];
  119. // eslint-disable-next-line
  120. matchText = eval(`Object.assign({${matchText}})`);
  121. prefix = 'system-';
  122. } catch (error) {
  123. err(`读取uni-app页面构建方法配置错误 \r\n\r\n ${error}`);
  124. }
  125. } else {
  126. // eslint-disable-next-line
  127. matchText = eval(`Object.assign${matchText[1]}`);
  128. }
  129. } else {
  130. // eslint-disable-next-line
  131. matchText = eval(`Object.assign${matchText[1]}`);
  132. }
  133. return {
  134. config: matchText,
  135. prefix,
  136. FunStr,
  137. };
  138. };
  139. /**
  140. * 通过一个未知的路径名称 在路由表中查找指定路由表 并返回
  141. * @param {String} path //不管是aliasPath名的路径还是path名的路径
  142. * @param {Object} routes//当前对象的所有路由表
  143. */
  144. export const pathToRute = function (path, routes) {
  145. let PATHKEY = '';
  146. let rute = {};
  147. const routeKeys = Object.keys(routes);
  148. for (let i = 0; i < routeKeys.length; i += 1) {
  149. const key = routeKeys[i];
  150. const item = routes[key];
  151. rute = item;
  152. if (item.aliasPath == path) { // path参数是优先采用aliasPath为值得 所以可以先判断是否与aliasPath相同
  153. PATHKEY = 'aliasPath';
  154. break;
  155. }
  156. // eslint-disable-next-line
  157. if (`/${item._PAGEPATH}` == path) { // 路径相同
  158. PATHKEY = 'path';
  159. break;
  160. }
  161. }
  162. return {
  163. PATHKEY: {
  164. [PATHKEY]: path,
  165. },
  166. rute,
  167. };
  168. };
  169. /**
  170. * 通过一个路径name 在路由表中查找指定路由表 并返回
  171. * @param {String} name//实例化路由时传递的路径表中所匹配的对应路由name
  172. * @param {Object} routes//当前对象的所有路由表
  173. */
  174. export const nameToRute = function (name, routes) {
  175. const routesKeys = Object.keys(routes);
  176. for (let i = 0; i < routesKeys.length; i += 1) {
  177. const key = routesKeys[i];
  178. const item = routes[key];
  179. if (item.name == name) {
  180. return item;
  181. }
  182. }
  183. err(`路由表中没有找到 name为:'${name}' 的路由`);
  184. };
  185. /**
  186. * 根据用户传入的路由规则 格式化成正确的路由规则
  187. * @param {Object} rule 用户需要跳转的路由规则
  188. * @param {Object} selfRoutes simple-router下的所有routes对象
  189. * @param {Object} CONFIG 当前路由下的所有配置信息
  190. */
  191. export const formatUserRule = function (rule, selfRoutes, CONFIG) {
  192. let type = '';
  193. const ruleQuery = (type = 'query', rule.query || (type = 'params', rule.params)) || (type = '', {});
  194. let rute = {}; // 默认在router中的配置
  195. if (type == '' && rule.name != null) { // 那就是可能没有穿任何值咯
  196. type = 'params';
  197. }
  198. if (type != 'params') {
  199. const route = pathToRute(rule.path || rule, selfRoutes);
  200. if (Object.keys(route.PATHKEY)[0] == '') {
  201. err(`'${route.PATHKEY['']}' 路径在路由表中未找到`);
  202. return null;
  203. }
  204. rute = route.rute;
  205. if (rule.path) {
  206. rule.path = rute.path;
  207. }
  208. }
  209. if (type != '') { // 当然是对象啦 这个主要是首页H5PushTo调用时的
  210. if (type == 'params' && CONFIG.h5.paramsToQuery) { // 如果是name规则并且设置了转query,那么就转path跳转了
  211. const {
  212. aliasPath,
  213. path,
  214. } = nameToRute(rule.name, selfRoutes);
  215. delete rule.name;
  216. delete rule.params;
  217. rule.path = aliasPath || path;
  218. type = 'query';
  219. }
  220. const query = Global.$parseQuery.transfer(ruleQuery);
  221. if (CONFIG.encodeURI) {
  222. if (query != '') {
  223. rule[type] = {
  224. query: query.replace(/^query=/, ''),
  225. };
  226. }
  227. } else {
  228. rule[type] = ruleQuery;
  229. }
  230. } else { // 纯字符串,那就只有是path啦
  231. rule = rute.path;
  232. }
  233. return rule;
  234. };
  235. /**
  236. * 根据是否获取非vue-Router next管道参数,来进行格式化
  237. *
  238. * @param {Object} to
  239. * @param {Object} from
  240. * @param {Router} Router //router当前实例对象
  241. */
  242. export const getRouterNextInfo = function (to, from, Router) {
  243. let [toRoute, fromRoute] = [to, from];
  244. const H5 = Router.CONFIG.h5;
  245. if (H5.vueNext === false && H5.vueRouterDev === false) { // 不采用vue-router中的to和from,需要格式化成Router中$Route获取的一样一样的
  246. let [toPath, fromPath] = [{}, {}];
  247. toPath[to.meta.PATHKEY] = to.meta.PATHKEY === 'path' ? `/${to.meta.pagePath}` : `${to.path}`;
  248. fromPath[from.meta.PATHKEY] = from.meta.PATHKEY === 'path' ? `/${from.meta.pagePath}` : `${from.path}`;
  249. if (to.meta.PATHKEY == null) { // 未使用uni-pages.json中的配置、通过addRoutes时 meta.PATHKEY 可能未undefined
  250. toPath = pathToRute(to.path, Router.selfRoutes).PATHKEY;
  251. }
  252. if (from.meta.PATHKEY == null) {
  253. fromPath = pathToRute(from.path, Router.selfRoutes).PATHKEY;
  254. }
  255. const isEmptyTo = Object.keys(to.query).length != 0 ? copyObject(to.query) : copyObject(to.params);
  256. const isEmptyFrom = Object.keys(from.query).length != 0 ? copyObject(from.query) : copyObject(from.params);
  257. /* eslint-disable */
  258. delete isEmptyTo.__id__; // 删除uni-app下的内置属性
  259. delete isEmptyFrom.__id__;
  260. /* eslint-enable */
  261. const toQuery = Global.$parseQuery.queryGet(isEmptyTo).decode;
  262. const fromQuery = Global.$parseQuery.queryGet(isEmptyFrom).decode;
  263. toRoute = resolveRule(Router, toPath, toQuery, Object.keys(toPath)[0]);
  264. fromRoute = resolveRule(Router, fromPath, fromQuery, Object.keys(fromPath)[0]);
  265. } else {
  266. if (fromRoute.name == null && toRoute.name != null) { // 这种情况是因为uni-app在使用vue-router时搞了骚操作。
  267. fromRoute = {
  268. ...fromRoute,
  269. ...{
  270. name: toRoute.name,
  271. },
  272. }; // 这个情况一般出现在首次加载页面
  273. }
  274. }
  275. return {
  276. toRoute,
  277. fromRoute,
  278. };
  279. };
  280. export const vueDevRouteProxy = function (routes, Router) {
  281. const proxyRoutes = [];
  282. for (let i = 0; i < routes.length; i += 1) {
  283. const item = routes[i];
  284. const childrenRoutes = Reflect.get(item, 'children');
  285. if (childrenRoutes != null) {
  286. const childrenProxy = vueDevRouteProxy(childrenRoutes, Router);
  287. item.children = childrenProxy;
  288. }
  289. const ProxyRoute = proxyBeforeEnter(Router, item);
  290. proxyRoutes.push(ProxyRoute);
  291. }
  292. return proxyRoutes;
  293. };
  294. /**
  295. * 组装成编码后的路由query传递信息
  296. * @param {Object} CONFIG simple-router 对象配置
  297. * @param {Object} query 传递的参数
  298. * @param {Object} mode 路由模式
  299. */
  300. export const encodeURLQuery = function (CONFIG, query, mode) {
  301. if (Object.keys(query).length == 0) { // 没有传值的时候 我们啥都不管
  302. return '';
  303. }
  304. if (CONFIG.h5.vueRouterDev === false) { // 没有采取完全模式开发时 才转换
  305. const { strQuery, historyObj } = Global.$parseQuery.queryGet(query);
  306. if (mode === 'history') {
  307. return historyObj;
  308. }
  309. return strQuery;
  310. } // 完全彩种 vue-router 开发的时候 我们不用管
  311. if (mode === 'history') { // 此模式下 需要的就是对象
  312. return query;
  313. }
  314. return Global.$parseQuery.stringify(query); // hash转成字符串拼接
  315. };
  316. /**
  317. * 把一个未知的路由跳转规则进行格式化为 hash、history 可用的,主要表现在 history模式下直接传入path会报错__id__错误的问题
  318. * @param {*} path 需要判断修改的路径规则
  319. */
  320. export const strPathToObjPath = function (path) {
  321. if (path == null) { // 我们也不用管啦,这个情况是路由守卫中传递的
  322. return path;
  323. }
  324. if (isObject(path)) { // 是对象我们不用管
  325. return path;
  326. }
  327. return { // 这种情况就是只有path时,直接返回path对象了
  328. path,
  329. };
  330. };
  331. /**
  332. * 通过 getCurrentPages() api 获取指定页面的 page 对象 默认是获取当前页面page对象
  333. * @param {Number} index //需要获取的页面索引
  334. * @param {Boolean} all //是否获取全部的页面
  335. */
  336. export const getPages = function (index = 0, all) {
  337. const pages = getCurrentPages(all);
  338. return pages.reverse()[index];
  339. };
  340. /**
  341. * 获取当前页面下的 Route 信息
  342. *
  343. * @param {Object} pages 获取页面对象集合
  344. * @param {Object} Vim 用户传递的当前页面对象
  345. */
  346. export const H5GetPageRoute = function (pages, Vim) {
  347. if (pages.length > 0) { // 直接取当前页面的对象
  348. const currentRoute = pages[pages.length - 1].$route;
  349. return getRouterNextInfo(currentRoute, currentRoute, this).toRoute;
  350. } if (Vim && Vim.$route) {
  351. return getRouterNextInfo(Vim.$route, Vim.$route, this).toRoute;
  352. }
  353. return {};
  354. };
  355. /**
  356. * 在useUniConfig:true 的情况下重新拼装路由表 useUniConfig:false 不需要读取page.json中的数据 直接使用component作为页面组件
  357. * @param {Router} Router//unis-simple-router 路由对象
  358. * @param {vueRouter} vueRouter//vue-router对象
  359. * @param {Boolean} useUniConfig//是否采用uni-page.json中的配置选项
  360. * @param {Array} routes//需要循环的routes表
  361. */
  362. export const diffRouter = function (Router, vueRouter, useUniConfig, routes) {
  363. const newRouterMap = [];
  364. if (useUniConfig) { // 使用pages.json的样式配置 只是单纯的把url路径改成用户自定义的 保留uni的所以的配置及生命周期、缓存
  365. const Routes = routes || vueRouter.options.routes;
  366. const cloneSelfRoutes = copyObject(Router.selfRoutes); // copy一个对象随便搞xxoo
  367. Routes.forEach(((item) => {
  368. const path = item.path === '/' ? item.alias : item.path;
  369. const vueRoute = (Router.vueRoutes[path] || Router.vueRoutes[item.path]) || Router.selfRoutes[path];
  370. const CselfRoute = Router.selfRoutes[path];
  371. delete cloneSelfRoutes[path]; // 移除已经添加到容器中的路由,用于最后做对比 是否page.json中没有,而实例化时传递了
  372. if (CselfRoute == null) {
  373. return err(
  374. `读取 ‘pages.json’ 中页面配置错误。实例化时传递的路由表中未找到路径为:${path} \r\n\r\n 可以尝试把 ‘useUniConfig’ 设置为 ‘false’。或者配置正确的路径。如果你是动态添加的则不用理会`,
  375. );
  376. }
  377. let pageConfigJson = {
  378. config: {},
  379. };
  380. if (vueRoute.component) {
  381. pageConfigJson = getFuntionConfig(vueRoute.component.render.toString());
  382. CselfRoute.component = {
  383. render: (h) => vueRoute.component.render(h),
  384. };
  385. }
  386. delete CselfRoute.components; // useUniConfig:true 时不允许携带components
  387. delete CselfRoute.children; // useUniConfig:true 时不允许携带children
  388. CselfRoute.meta = {
  389. ...pageConfigJson.config,
  390. ...item.meta || {},
  391. PATHKEY: CselfRoute.aliasPath ? 'aliasPath' : 'path',
  392. pagePath: CselfRoute.path.substring(1),
  393. };
  394. CselfRoute.path = CselfRoute.aliasPath || (item.path === '/' ? item.path : CselfRoute.path);
  395. item.alias = item.path === '/' ? item.alias : CselfRoute.path; // 重新给vueRouter赋值一个新的路径,欺骗uni-app源码判断
  396. const ProxyRoute = proxyBeforeEnter(Router, CselfRoute);
  397. newRouterMap.push(ProxyRoute);
  398. }));
  399. if (Object.keys(cloneSelfRoutes).length > 0) { // 确实page.json中没有,而实例化时传递了
  400. const testG = cloneSelfRoutes['*']; // 全局通配符,他是个例外'通配符'可以被添加
  401. if (testG && routes == null) {
  402. const ProxyRoute = proxyBeforeEnter(Router, Router.selfRoutes['*']);
  403. newRouterMap.push(ProxyRoute);
  404. }
  405. if (routes == null) { // 非动态添加时才打印警告
  406. const cloneSelfRoutesKeys = Object.keys(cloneSelfRoutes);
  407. for (let i = 0; i < cloneSelfRoutesKeys.length; i += 1) {
  408. const key = cloneSelfRoutesKeys[i];
  409. if (key !== '*') { // 通配符不警告
  410. warn(`实例化时传递的routes参数:\r\n\r\n ${JSON.stringify(cloneSelfRoutes[key])} \r\n\r\n 在pages.json中未找到。自定排除掉,不会添加到路由中`);
  411. }
  412. }
  413. }
  414. }
  415. } else { // 不使用任何的uni配置完全使用 完全使用component作为页面使用
  416. const Routes = routes || Router.selfRoutes;
  417. const RoutesKeys = Object.keys(Routes);
  418. for (let i = 0; i < RoutesKeys.length; i += 1) {
  419. const key = RoutesKeys[i];
  420. const item = Routes[key];
  421. // eslint-disable-next-line
  422. if (item._ROUTERPATH != null) { // 不寻找children下的路径,只取第一层
  423. continue;
  424. }
  425. delete item.components;
  426. delete item.children;
  427. item.path = item.aliasPath || item.path; // 优先获取别名为路径
  428. if (item.path !== '*') {
  429. item.component = item.component.render || item.component; // render可能是用户使用addRoutes api进行动态添加的
  430. }
  431. item.meta = {
  432. ...item.meta || {},
  433. PATHKEY: item.aliasPath ? 'aliasPath' : 'path',
  434. pagePath: item.path.substring(1),
  435. };
  436. const ProxyRoute = proxyBeforeEnter(Router, item);
  437. newRouterMap.push(ProxyRoute);
  438. }
  439. }
  440. return newRouterMap;
  441. };