侧边栏壁纸
  • 累计撰写 27 篇文章
  • 累计创建 42 个标签
  • 累计收到 34 条评论

目 录CONTENT

文章目录

前端项目通用模板开发(基于vue cli)

miykah
2023-09-25 / 0 评论 / 0 点赞 / 46 阅读 / 15110 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-10-13,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前端项目初始化

初始化前端项目,并开发一个通用模板。

使用的前端脚手架是 Vue Cli:https://cli.vuejs.org/zh/

初始化

  1. 安装Vue Cli

    npm install -g @vue/cli
  2. 检测版本

    vue -V

image-20230925144316920

  1. 创建项目

    vue create ojsystem-frontend
  1. 手动选择

    按照图中的选择。

    image-20230925145140060

  2. 尝试运行项目

    image-20230925145708400

  3. 运行成功界面

    image-20230925145735657

前端项目工程化配置

Vue Cli 脚手架已经配置了代码美化、校验、格式化等插件,无需自行配置,但是需要自己在编辑器WebStorm中开启代码美化插件,否则使用快捷键格式化代码后,vue cli 脚手架 会提示格式错误,是因为 vue cli 和 目前编辑器的格式不一样。

  1. prettier

image-20230925150344592

  1. eslint

image-20230925150503343

自己整合 代码规范:https://eslint.org/docs/latest/use/getting-started 代码美化:https://prettier.io/docs/en/install.html 直接整合:https://github.com/prettier/eslint-plugin-prettier#recommended-configuration(包括了 https://github.com/prettier/eslint-config-prettier#installation)

Git 配置

vue cli 初始化这个项目的时候已经初始化了本地仓库,只需要在 github/gitee 上创建对应的远程仓库,然后配置一下就好。

  1. 在 gitee 上创建对应的 ojsystem-frontend 远程仓库

  2. 给本地仓库添加远程仓库

git remote add origin git@gitee.com:miykah/ojsystem-frontend.git

引入组件

这次组件库使用字节开源的 Acro Design:https://arco.design/

使用其中的vue组件库:https://arco.design/vue/docs/start

  1. 参考文档安装

    npm install --save-dev @arco-design/web-vue
  2. 引入项目

    可以选择全局引入(项目需要瘦身优化的时候,可以选择按需引入)

    image-20230925152242690

    image-20230925152302212

  3. 测试是否引入成功

    随便找一个组件引入到页面。

    引入一个日历组件到 App.vue

    image-20230925152541073

    image-20230925152618196

    image-20230925152551663

项目初始化模板设计

项目通用布局

  1. 创建一个layouts目录,及一个BasicLayout.vue文件。引入 Acro Design 中最基础的 layout组件。

image-20230925164141779

<template>
  <div id="basicLayout">
    <a-layout style="height: 400px">
      <a-layout-header class="header">
        <GlobalHeader />
      </a-layout-header>
      <a-layout-content class="content">
        <router-view />
      </a-layout-content>
      <a-layout-footer class="footer">
        <a href="https://blog.miykah.top" target="_blank">miykah's blog</a>
      </a-layout-footer>
    </a-layout>
  </div>
</template>
​
<style>
#basicLayout {
}
​
#basicLayout .header {
  margin-bottom: 16px;
  box-shadow: #eee 1px 1px 5px;
}
​
#basicLayout .content {
  background: linear-gradient(to right, #fefefe, #fff);
  margin-bottom: 16px;
  padding: 20px;
}
​
#basicLayout .footer {
  /*background: #efefef;*/
  padding: 12px;
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
  box-shadow: #aaa 1px 1px 5px;
}
</style>
<script>
import GlobalHeader from "@/components/GlobalHeader";
​
export default {
  components: { GlobalHeader },
};
</script>
  1. 删除 App.vue 中多余的代码。引入这个 BasicLayout

<template>
  <div id="app">
    <BasicLayout />
  </div>
</template>
​
<style>
#app {
}
</style>
<script>
import BasicLayout from "@/layouts/BasicLayout";
​
export default {
  components: { BasicLayout },
};
</script>

通用的路由菜单栏

  1. 提取通用的路由文件

image-20230925164258701

vue cli 的路由默认是写在了 index.ts 中,可以单独抽取出其中的路由部分,新建一个routes.ts 文件。

import { RouteRecordRaw } from "vue-router";
import HomeView from "@/views/HomeView.vue";
​
export const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "题库",
    component: HomeView,
  },
  {
    path: "/about",
    name: "关于",
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
  },
];
  1. 创建 GlobalHeader.vue

在 components 目录创建GlobalHeader.vue ,作为通用的菜单栏,里面引入 Acro 的菜单栏组件。并实现动态路由,可以动态高亮菜单。

<template>
  <div id="globalHeader">
    <a-menu
      mode="horizontal"
      :selected-keys="selectedKeys"
      @menu-item-click="doMenuClick"
    >
      <!--这里是logo以及title-->
      <a-menu-item
        key="0"
        :style="{ padding: 0, marginRight: '38px' }"
        disabled
      >
        <div class="title-bar">
          <img class="logo" src="../assets/oj-logo.png" />
          <div class="title">OJ-System</div>
        </div>
      </a-menu-item>
      <!--菜单项,动态的,使用v-for遍历-->
      <a-menu-item v-for="item in routes" :key="item.path">
        {{ item.name }}
      </a-menu-item>
    </a-menu>
  </div>
</template>
​
<script setup lang="ts">
import { routes } from "../router/routes";
import { useRouter } from "vue-router";
import { ref } from "vue";
​
const router = useRouter();
// 选中的菜单项,默认选中主页"/"
const selectedKeys = ref(["/"]);
// 路由跳转后,高亮对应的菜单项
router.afterEach((to, from, failure) => {
  selectedKeys.value = [to.path];
});
// 点击菜单,跳转
const doMenuClick = (key: string) => {
  router.push({
    path: key,
  });
};
</script>
​
<style scoped>
.title-bar {
  display: flex;
  align-items: center;
}
​
.title {
  color: #444;
  margin-left: 16px;
}
​
.logo {
  height: 48px;
}
</style>

全局状态管理

保存页面全局共享的一些变量,比如用户登陆了,需要保存用户的一些基本信息。

使用 vuex,vue cli 已经整合了 vuex

state:存储的状态信息,比如用户信息 mutation(尽量同步):定义了对变量进行增删改(更新)的方法 actions(支持异步):执行异步操作,并且触发 mutation 的更改(actions 调用 mutation) modules(模块):把一个大的 state(全局变量)划分为多个小模块,比如 user 专门存用户的状态信息

  1. 在 store 目录下 新建 user.ts 文件,保存用户信息。

// initial state
import { StoreOptions } from "vuex";
​
export default {
  namespaced: true,
  state: () => ({
    loginUser: {
      userName: "未登录",
      role: "not login",
    },
  }),
  actions: {
    getLoginUser({ commit, state }, payload) {
      commit("updateUser", payload);
    },
  },
  mutations: {
    updateUser(state, payload) {
      state.loginUser = payload;
    },
  },
} as StoreOptions<any>;
​
  1. 在 store 目录下定义 index.ts 文件,导入 user 模块

import { createStore } from "vuex";
import user from "./user";
​
export default createStore({
  mutations: {},
  actions: {},
  modules: {
    user,
  },
});
  1. 在 Vue 页面中可以获取已存储的状态变量

const store = useStore();
store.state.user?.loginUser
  1. 在 Vue 页面中可以修改状态变量

使用 dispatch 来调用之前定义好的 actions

store.dispatch("user/getLoginUser", {
    userName: "miykah",
    role: "admin",
});

权限管理

有的页面,必须要管理员才能访问,如果不是管理员,则需要拦截或跳转到指定页面。

  1. 给保存用户信息的全局状态添加一个 role 字段

image-20230925200147152

  1. routes.ts 中,需要管理员访问的页面,添加一个 meta,里面指定权限 access 为 admin

image-20230925200252326

  1. 在全局 App.vue 中,每次路由前,判断路由的页面是否需要管理员权限 (后面优化了,不放在 App.vue中)

<template>
  <div id="app">
    <BasicLayout />
  </div>
</template>
​
<style>
#app {
}
</style>
<script setup lang="ts">
import BasicLayout from "@/layouts/BasicLayout";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
​
const router = useRouter();
const store = useStore();
​
router.beforeEach((to, from, next) => {
  // 判断路由的页面是否需要管理员权限
  if (to.meta?.access === "admin") {
    if (store.state.user.loginUser?.role !== "admin") {
      next("/no-auth"); // 无权限,跳转到 no auth 页面
      return;
    }
    next();
  }
  next();
});
</script>

菜单显隐

目前的效果是:

image-20230925211223579

但有一些菜单,比如管理员的菜单,当用户是管理员时,才会显示出来菜单按钮,不是管理员则隐藏。

  1. 给路由增加一个meta属性,标志该路由菜单是否显示

image-20230925212025299

  1. GlobalHeader.vue 中,菜单项不在遍历 routes, 而是遍历过滤之后的 routes

<a-menu-item v-for="item in routes" :key="item.path">
  {{ item.name }}
</a-menu-item>

改为:

<a-menu-item v-for="item in visibleRoutes" :key="item.path">
  {{ item.name }}
</a-menu-item>

其中:

// 未被隐藏的菜单路由
const visibleRoutes = routes.filter((item, index) => {
  return !item.meta?.hideInMenu;
});

根据权限隐藏菜单

和上一节类似,过滤掉用户没有权限的路由就可以。

这里同时优化一下权限管理,不放在App.vue中,单独抽取出来。

  1. 创建一个 access 目录

  2. 创建 accessEnum.ts

/**
 * 权限定义
 */
const ACCESS_ENUM = {
  NOT_LOGIN: "not login",
  USER: "user",
  ADMIN: "admin",
};
​
export default ACCESS_ENUM;
  1. 定义通用的权限校验方法,而不放在 App.vue 中。抽离成 checkAccess.ts

import ACCESS_ENUM from "@/access/accessEnum";
​
/**
 * 检查权限(判断当前登录用户是否具有某个权限)
 * @param loginUser 当前登录用户
 * @param needAccess 需要有的权限
 * @return boolean 有无权限
 */
const checkAccess = (loginUser: any, needAccess = ACCESS_ENUM.NOT_LOGIN) => {
  // 获取当前登录用户具有的权限(如果没有 loginUser,则表示未登录)
  const loginUserAccess = loginUser?.userRole ?? ACCESS_ENUM.NOT_LOGIN;
  if (needAccess === ACCESS_ENUM.NOT_LOGIN) {
    return true;
  }
  // 如果用户登录才能访问
  if (needAccess === ACCESS_ENUM.USER) {
    // 如果用户没登录,那么表示无权限
    if (loginUserAccess === ACCESS_ENUM.NOT_LOGIN) {
      return false;
    }
  }
  // 如果需要管理员权限
  if (needAccess === ACCESS_ENUM.ADMIN) {
    // 如果不为管理员,表示无权限
    if (loginUserAccess !== ACCESS_ENUM.ADMIN) {
      return false;
    }
  }
  return true;
};
​
export default checkAccess;
  1. 根据权限隐藏菜单

// 未被隐藏的菜单路由,过滤掉被隐藏的,且没权限的菜单
const visibleRoutes = computed(() => {
  return routes.filter((item, index) => {
    // 路由是隐藏的
    if (item.meta?.hideInMenu) {
      return false;
    }
    // 用户无权限访问的菜单,也隐藏
    if (!checkAccess(loginUser, item?.meta?.access as string)) {
      return false;
    }
    return true;
  });
});

0

评论区