Skip to content

一. 动态路由

1.1. 基于角色添加路由

1.2. 基于菜单动态匹配

1.3. 动态创建页面和路由对象

  • coderwhy add3page_setup xxxxx

1.4. 从文件中读取所有的路由

  • localRoutes
  • 创建工具:根据菜单去匹配正确的路由
js
function loadLocalRoutes() {
  // 1.动态获取所有的路由对象, 放到数组中
  // * 路由对象都在独立的文件中
  // * 从文件中将所有路由对象先读取数组中
  const localRoutes: RouteRecordRaw[] = [];

  // 1.1.读取router/main所有的ts文件
  const files: Record<string, any> = import.meta.glob(
    "../router/main/**/*.ts",
    {
      eager: true,
    }
  );
  // 1.2.将加载的对象放到localRoutes
  for (const key in files) {
    const module = files[key];
    localRoutes.push(module.default);
  }

  return localRoutes;
}

1.5. 根据菜单动态的映射路由

  • routes
  • router.addRoute('main', xxxx)
js
export function mapMenusToRoutes(userMenus: any[]) {
  // 1.加载本地路由
  const localRoutes = loadLocalRoutes()

  // 2.根据菜单去匹配正确的路由
  const routes: RouteRecordRaw[] = []
  for (const menu of userMenus) {
    for (const submenu of menu.children) {
      const route = localRoutes.find((item) => item.path === submenu.url)
      if (router) routes.push(route!)
    }
  }
  return routes
}

1.6. 刷新保持路由的注册状态

  • 设置每次刷新都调用本地储存
js
import { createPinia } from "pinia";
import type { App } from "vue";
import useLoginStore from "./login/login";

const pinia = createPinia();

function registerStore(app: App<Element>) {
  // 1.use的pinia
  app.use(pinia);

  // 2.加载本地的数据
  const loginStore = useLoginStore();
  loginStore.loadLocalCacheAction();
}

export default registerStore;
  • 将数据进行本地储存
js
loadLocalCacheAction() {
  // 1.用户进行刷新默认加载数据
  const token = localCache.getCache(LOGIN_TOKEN)
  const userInfo = localCache.getCache('userInfo')
  const userMenus = localCache.getCache('userMenus')
  if (token && userInfo && userMenus) {
    this.token = token
    this.userInfo = userInfo
    this.userMenus = userMenus

    // 2.动态添加路由
    const routes = mapMenusToRoutes(userMenus)
    routes.forEach((route) => router.addRoute('main', route))
  }
}

1.7. 登录进入匹配第一个页面

  • 记录第一个被匹配到的菜单
js
if (!firstMenu && route) firstMenu = submenu;
  • 在路由守卫中添加,如果是进入到 main,则跳转到第一个页面
js
if (to.path === "/main") {
  return firstMenu?.url;
}

1.8. 刷新页面匹配 menu 菜单

  • 根据路径匹配菜单
js
- 根据路径去匹配需要显示的菜单
- @param path 需要匹配的路径
- @param userMenus 所有的菜单
export function mapPathToMenu(path: string, userMenus: any[]) {
  for (const menu of userMenus) {
    for (const submenu of menu.children) {
      if (submenu.url === path) {
        return submenu
      }
    }
  }
}

1.9. 面包屑的功能实现

js
interface IBreadcrumbs {
  name: string
  path: string
}
export function mapPathToBreadcrumbs(path: string, userMenus: any[])
{   // 1.定义面包屑
    const breadcrumbs: IBreadcrumbs[] = []
    // 2.遍历获取面包屑层级
    for (const menu of userMenus) {
    for (const submenu of menu.children) {
      if (submenu.url === path) {
      // 1.顶层菜单
      breadcrumbs.push({ name: menu.name, path: menu.url })
      // 2.匹配菜单
      breadcrumbs.push({ name: submenu.name, path: submenu.url })
      }
    }
  }
}
  • 通过计算属性,当crumbs发生改变时,重新计算(或者使用 watch 函数)
js
const breadcrumbs = computed(() => {
  return mapPathToBreadcrumbs(route.path, userMenus);
});

二. 用户 User 界面

3.1. search 搜索区域的布局

3.2. content 的整体布局

3.3. 获取 user 的数据展示

3.3.1 从 store 的 action 获取的数据因为异步所以需要用storeToRefs进行监听

js
// 1.发起 action,请求 usersList 的数据
const systemStore = useSystemStore();
systemStore.postUsersListAction();
// 2.获取 usersList 数据,进行展示
const { usersList } = storeToRefs(systemStore);
  • el-table 展示
  • 自定义 column

3.4. 自定义 Table 的 Column

  • 作用域插槽
  • enable
js
 <template #default="scope">
  <el-button
    size="small"
    :type="scope.row.enable ? 'primary' : 'danger'"
    plain
  >
    {{ scope.row.enable ? '启用' : '禁用' }}
  </el-button>
</template>
  • createAt/updateAt(时间格式化)
js
<template #default="scope">
  {{ formatUTC(scope.row.createAt) }}
</template>
  • dayjs 封装 utc 转换
js
export function formatUTC(
  utcString: string,
  format: string = "YYYY/MM/DD HH:mm:ss"
) {
  const resultTime = dayjs.utc(utcString).utcOffset(8).format(format);
  return resultTime;
}

3.5. 分页 pagination 组件展示

3.6. 页码改变/点击查询/重置 - 发送网络请求

3.7. 删除某一条数据

3.8. 新建用户的 Modal

  • user-modal.vue 组件
  • 布局组件
  • 点击确定按钮, 创建数据

3.9. 编辑用户的 Modal

  • 编辑的数据, 进行回显
  • 编辑操作网络请求

三. 页面的重构

3.1. 组件进行拷贝

  • 修改它的网络请求的部分
  • store/service

3.2. 组件进行配置

  • page-search 的配置,查询模块的高阶组件
  • 通过 congif,对渲染项进行渲染
js
<el-form
      :model="searchForm"
      ref="formRef"
      :label-width="searchConfig.labelWidth ?? '80px'"
      size="large"
    >
      <el-row :gutter="20">
        <template v-for="item in searchConfig.formItems" :key="item.prop">
          <el-col :span="8">
            <el-form-item :label="item.label" :prop="item.prop">
              <template v-if="item.type === 'input'">
                <el-input
                  v-model="searchForm[item.prop]"
                  :placeholder="item.placeholder"
                />
              </template>
              <template v-if="item.type === 'date-picker'">
                <el-date-picker
                  v-model="searchForm[item.prop]"
                  type="daterange"
                  range-separator="-"
                  start-placeholder="开始时间"
                  end-placeholder="结束时间"
                />
              </template>
              <template v-if="item.type === 'select'">
                <el-select
                  v-model="searchForm[item.prop]"
                  :placeholder="item.placeholder"
                  style="width: 100%"
                >
                  <template v-for="option in item.options" :key="option.value">
                    <el-option :label="option.label" :value="option.value" />
                  </template>
                </el-select>
              </template>
            </el-form-item>
          </el-col>
        </template>
      </el-row>
    </el-form>