一. 动态路由
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>