uniapp路由管理方案(pages.json)

3,378 阅读2分钟

由来

首先让我们来看看pages.json中有哪些令人感到苦恼的问题:

  1. 当项目中页面较多时,pages.json 文件会变得很大,不方便维护,多人协作的时候容易出现代码冲突
  2. 路由跳转是要写一长串的路径,如果传参多,并且不封装的情况下路径会更长

由于有这个两个问题,所以就有了封装个自动生成pages.json的插件需求,本着有需求就干的精神,开始操练起来🚗

插件

插件已开发上传到npm:
uni-vite-plugin-pages-generator:www.npmjs.com/package/uni…

大致思路

问题一:可以将pages.json中的所有配置进行文件拆分,拆分完成后再进行自动合并,这样大致出来的目录配置和文件代码就是:

image.png

// 主包页面配置
module.exports = [
  {
    path: "pages/home/home",
    style: {
      navigationBarTitleText: "首页"
    }
  },
];

这里就可以看出可以将pages.json中的配置分开写,pages和subPackage目录分别写的是主包页面和子包页面,页面的话可能比较多,所以是文件夹,而其他的配置则直接写配置即可如tabbar:

// tabbar配置
module.exports = {
  color: "#323233",
  selectedColor: "#1989fa",
  list: [
    {
      pagePath: "pages/home/home",
      iconPath: "static/tabbar/btn_home1.png",
      selectedIconPath: "static/tabbar/btn_home2.png",
      text: "首页"
    },
    {
      pagePath: "pages/me/me",
      iconPath: "static/tabbar/btn_mine1.png",
      selectedIconPath: "static/tabbar/btn_mine2.png",
      text: "我的"
    }
  ]
};

问题二:将读取到的文件除了生成pages.json再生成一个映射文件,大致如下:

const index = {
  home: "/pages/home/home",
  me: "/pages/me/me"
};
const home = {
  householdsList: "/subPackages/home/pages/householdsList",
  householdsDetail: "/subPackages/home/pages/householdsDetail",
  peasantHousehold: "/subPackages/home/pages/peasantHousehold"
};
const pages = { ...index, ...home };

export default pages;

有了大致的思路就可以开搞了😎😎:

实现步骤

  1. 新建插件项目,如果对npm插件不了解怎么开发可以看:juejin.cn/post/684490…
  2. 在src下新建一个文件generatePagesJson.js,用于生成pages.json,代码如下:
const fs = require("fs");
const path = require("path");

// 读取主包配置文件
const indexPath = path.join(process.cwd(), process.argv[2], "/pages");
const indexFiles = fs.readdirSync(indexPath);
let indexConfig = [];
for (const file of indexFiles) {
  if (file.endsWith(".ts")) {
    const module = require(path.join(indexPath, file));
    indexConfig = module;
  }
}

// 读取子包配置文件
const subPackagesPath = path.join(process.cwd(), process.argv[2], "/subPackages");
const subPackagesConfig = [];
if (fs.existsSync(subPackagesPath)) {
  const subPackagesFiles = fs.readdirSync(subPackagesPath);
  for (const file of subPackagesFiles) {
    if (file.endsWith(".ts")) {
      const moduleName = path.basename(file, ".ts");
      const modulePath = path.join(subPackagesPath, moduleName + ".ts");
      const module = require(modulePath);
      const subPackage = {
        root: `subPackages/${moduleName}`,
        name: moduleName,
        pages: module
      };
      subPackagesConfig.push(subPackage);
    }
  }
}

// 生成 pages.json 配置
const pagesConfig = {
  pages: indexConfig,
  // 添加子包配置
  subPackages: subPackagesConfig
};

// 读取和生成其他配置文件
const configPath = path.join(process.cwd(), process.argv[2]);
const configFiles = fs.readdirSync(configPath);
for (const file of configFiles) {
  if (file.endsWith(".ts") && file !== "index.ts") {
    const configName = path.basename(file, ".ts");
    const filePath = path.join(configPath, configName + ".ts");
    const config = require(filePath);
    pagesConfig[configName] = config;
  }
}

// 写入 pages.json 文件
fs.writeFileSync(path.join(process.cwd(), "/src/pages.json"), JSON.stringify(pagesConfig, null, 2));
  1. 在src下新建一个文件generatePages.js,用于生成路由映射文件,代码如下:
const fs = require("fs");
const path = require("path");

// 读取主包配置文件
const indexPath = path.join(process.cwd(), process.argv[2], "/pages");
const indexFiles = fs.readdirSync(indexPath);
const pagesConfig = {
  index: {}
};
for (const file of indexFiles) {
  if (file.endsWith(".ts")) {
    const module = require(path.join(indexPath, file));
    for (const router of module) {
      const paths = router.path.split("/");
      const pageName = paths[paths.length - 1];
      pagesConfig.index[pageName] = "/" + router.path;
    }
  }
}

// 读取子包配置文件
const subPackagesPath = path.join(process.cwd(), process.argv[2], "/subPackages");
if (fs.existsSync(subPackagesPath)) {
  const subPackagesFiles = fs.readdirSync(subPackagesPath);
  for (const file of subPackagesFiles) {
    if (file.endsWith(".ts")) {
      const moduleName = path.basename(file, ".ts");
      const modulePath = path.join(subPackagesPath, moduleName + ".ts");
      const module = require(modulePath);
      pagesConfig[moduleName] = {};
      for (const router of module) {
        const paths = router.path.split("/");
        const pageName = paths[paths.length - 1];
        pagesConfig[moduleName][pageName] = `/subPackages/${moduleName}/${router.path}`;
      }
    }
  }
}

// 生成 pages.json 配置
const fileContent = Object.keys(pagesConfig)
  .map((key) => `const ${key} = ${JSON.stringify(pagesConfig[key], null, 2).replace(/"([^"]+)":/g, "$1:")};`)
  .join("\n");

const exportContent = `const pages = { ${Object.keys(pagesConfig)
  .map((key) => "..." + key)
  .join(", ")} };\n\nexport default pages;\n`;
const finalContent = `// Generated by script\n\n${fileContent}\n\n${exportContent}`;

// 写入 pages.json 文件
fs.writeFileSync(path.join(process.cwd(), process.argv[2], "/action/pages.ts"), finalContent);

  1. 新建一个index.js文件, 在项目启动时同时跑两个脚本:
const { execSync } = require("child_process");
const path = require("path");

let count = 0;

const exec = (folderPath) => {
  if (count !== 0) return;
  count = count + 1;
  // 此路径是在项目根目录下执行的,所以读取的为项目中的node_modules
  const generatePagesJsonPath = path.join(__dirname, "./generatePagesJson.js");
  const generatePagesPath = path.join(__dirname, "./generatePages.js");
  execSync(`node ${generatePagesJsonPath} ${folderPath}`);
  execSync(`node ${generatePagesPath} ${folderPath}`);
};

module.exports = (folderPath) => ({
  name: "pages-generator",
  buildStart() {
    exec(folderPath);
  }
});

这样就完成了代码映射文件生成和生成pages.json,插件也就写好了

  1. 然后将插件引入到我们的项目中,在vite.config.js文件中:
import pagesGeneratorPlugin from "vite-plugin-pages-generator";
...
plugins: [
   ...
   pagesGeneratorPlugin("/src/router") // "/src/router"为读取配置文件路径
]
  1. 重启则看到自己项目生成了映射文件和pages.json
  2. 根据映射文件封装路由跳转,代码如下:
/**
 * @Author GUAN
 * @Desc 简化router跳转api
 */
import { computed } from "vue"

interface RouteParams {
  [key: string]: any
}

interface BackParams {
  delta?: number
  data?: any
}

const routeStore: RouteParams = {}
let navigateLock = false // 处理快速点击多次

export const useActionRouter = () => {
  const pages = computed(() => (uni.getStorageSync("routerPages") as Record<string, string>) || {})

  const getRouteParams = (page: string): any => {
    const p = routeStore[page]
    if (!p) {
      // 在nvue文件中无法获取到数据,通过本地存储获取
      const store = uni.getStorageSync("routeStore")
      return store[page]
    }
    return p
  }

  const setRouteParams = (page: keyof typeof pages.value, params?: Record<string, any>) => {
    routeStore[page] = params
    uni.setStorageSync("routeParams", routeStore)
  }

  const navigate = (page: keyof typeof pages.value, params?: Record<string, any>): Promise<any> => {
    if (navigateLock) return Promise.reject("Multiple clicks")
    const eventName = Math.floor(Math.random() * 1000) + new Date().getTime() + "" // 生成唯一事件名
    navigateLock = true
    setRouteParams(page, params)
    uni.navigateTo({
      url: `${pages.value[page]}?eventName=${eventName}`,
      complete() {
        navigateLock = false
      }
    })

    return new Promise((resolve, reject) => {
      uni.$once(eventName, resolve)
      uni.$once(eventName, reject)
    })
  }

  const redirect = (page: keyof typeof pages.value, params?: Record<string, any>): void => {
    setRouteParams(page, params)
    uni.redirectTo({ url: pages.value[page] })
  }

  const reLaunch = (page: keyof typeof pages.value, params?: Record<string, any>): void => {
    setRouteParams(page, params)
    uni.reLaunch({ url: pages.value[page] })
  }

  const switchTab = (page: keyof typeof pages.value, params?: Record<string, any>): void => {
    setRouteParams(page, params)
    uni.switchTab({ url: pages.value[page] })
  }

  const back = ({ delta = 1, data = null }: BackParams = { delta: 1, data: null }): void => {
    const currentRoute = getCurrentPages().pop()
    // @ts-ignore
    const eventName = currentRoute?.options.eventName
    uni.$emit(eventName, data)
    uni.navigateBack({ delta })
  }

  return {
    getRouteParams,
    navigate,
    redirect,
    reLaunch,
    switchTab,
    back
  }
}

export type RouterType = typeof useActionRouter

至此,就完成了所有功能的开发啦,可以愉快的进行路由管理了😉