导师怀疑我的毕设前端不是自己写的!我把前端模版开发的过程甩他脸上了!

耗时一个月开发的OJ在线判题系统,文末有项目地址,目前还在更新代码~

现在让我们来自主开发打造一套前端开发项目模版

确认环境

nodeJS环境版本:

node -v  #我的是20
npm -v   #我的是10

初始化

使用vue-cli脚手架
https://cli.vuejs.org/zh/

1、安装脚手架

npm install -g @vue/cli

注意:如果出现报错image.png
则使用以下命令,强制安装覆盖新版本脚手架

npm install -g @vue/cli --force

2、创建项目(项目名居然不能包含大写字母)

vue create yoj-frontend

image.png
3、运行项目,能访问到主页即成功
image.png

image.png

vscode格式化配置Prettier

因为在创建项目时我们选择了Prettier对代码进行检查,所以代码如果写的不规范会报错
此时,我们需要在vscode中进行设置,按规定的语法进行自动格式化
下载插件prettier
image.png
对扩展进行设置
image.pngimage.png
在页面代码处,右键,选择"Format Document",然后选择prettier,alt + shift + f格式化代码,即可按照Prettier的语法规范格式化代码了
image.png
太麻烦了,我选择关闭prettier警告
找到项目里的.eslintrc.js文件,在rules里面添加一句"prettier/prettier": “off”,重启项目;

引入组件库

脚手架自动整合了vue-router:URL地址改变时加载对应的界面

组件库:https://arco.design/
(字节跳动开发的一个组件库)

快速上手: https://arco.design/vue/docs/start

执行命令:

npm install --save-dev @arco-design/web-vue

在入口文件main.js中完整引入

import { createApp } from 'vue'
import ArcoVue from '@arco-design/web-vue';
import App from './App.vue';
import '@arco-design/web-vue/dist/arco.css';

const app = createApp(App);
app.use(ArcoVue);
app.mount('#app');

项目通用布局实现

新建基础布局BasicLayout

选择组件库的Layout组件,在BasicLayout里面写布局

Arco Design Vue
坐标:src/layouts/BasicLayout

<template>
  <div id="Basiclayout">
    <a-layout style="height: 400px">
      <a-layout-header class="header">
        <GlobalHeader/>
      </a-layout-header>
      <a-layout-content class="content">
      <roter-view/>  
      </a-layout-content>
      <a-layout-footer class="footer">
        <a href="https://blog.csdn.net/m0_74870396?type=blog" target="_blank">
          诨号无敌鸭
        </a>
      </a-layout-footer>
    </a-layout>
  </div>
</template>
<script setup>
import GlobalHeader from '@/components/GlobalHeader.vue';
</script>
<style scoped>
#Basiclayout {
}
#Basiclayout .header {
  background: red;
  margin-bottom: 16px;
}

#Basiclayout .content {
    /* 水平方向的渐变背景 */
  background: linear-gradient(to right, #bbb, #fff); 
  margin-bottom: 16px;
}

#Basiclayout .footer {
  background: #efefef;
  padding: 16px;
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
}
</style>

在app.vue中引入,记得要有ts的语法支持并且支持vue3

<template>
  <div id="app">
  <BasicLayout />
  </div>
  </template>
  <script setup lang="ts">
  import BasicLayout from "./layouts/BasicLayout.vue";
</script>
  <style></style>

新建导航菜单栏

https://arco.design/vue/component/menu

导航菜单也是个通用模块,所以我们单独抽象出来GlobalHeader
坐标:src/components/GlobalHeader

<template>
  <div id="globalHeader">
    <a-menu mode="horizontal" :default-selected-keys="['1']">
      <a-menu-item
        key="0"
        :style="{ padding: 0, marginRight: '38px' }"
        disabled
      >
        <div class="title-bar">
          <img class="logo" src="../assets/icon22.jpg" />
          <div class="title">Y OJ</div>
        </div>
      </a-menu-item>
      <a-menu-item key="1">Home</a-menu-item>
      <a-menu-item key="2">Solution</a-menu-item>
      <a-menu-item key="3">Cloud Service</a-menu-item>
      <a-menu-item key="4">Cooperation</a-menu-item>
    </a-menu>
  </div>
</template>

<script setup lang="ts"></script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.title-bar {
  display: flex;
  align-items: center;
}
.logo {
  height: 48px;
}
.title {
  color: #444;
  margin-left: 16px;
}
</style>

现在界面就成了这样分为导航栏,内容,版权信息三部分
image.png

导航菜单路由实现

目的是想让用户点击菜单切换后可以跳转到对应的页面
步骤

1)提取通用路由文件
坐标:src/router/routes

import { RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
export const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "home",
    component: HomeView,
  },
  {
    path: "/about",
    name: "about",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
  },
];

修改src/router/index.ts

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import { routes } from "./routes";


const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

2)菜单组件读取路由,动态渲染菜单项
修改src/components/GlobalHeader 实现能通过遍历路由展示菜单项

 <a-menu-item v-for="item in routes" :key="item.path">
      {{item.name}}
      </a-menu-item>
<script setup lang="ts">
import {routes} from "../router/routes";
</script>

image.png

3)绑定跳转事件
接下来我们需要点击菜单后,跳转到对应的路由,查看官网API,得到点击事件API
image.png
修改src/components/GlobalHeader实现点击菜单跳转到对应的页面

    <a-menu mode="horizontal" :default-selected-keys="['1']" @menu-item-click="doMenuClick">

<script setup lang="ts">
import {routes} from "../router/routes";
import { useRouter } from "vue-router";
const router =  useRouter();
const doMenuClick = (key:string)=>{
  router.push({
    path:key,
  });
}
</script>

这样就实现了点击菜单跳转到对应的页面

4)同步路由的更新到菜单项高亮

现在情况是用户刷新后,路由还是那个路由,但是菜单项就没了

刷新前
image.png

刷新后

image.png

所以我们需要同步路由的更新到菜单项高亮上

继续修改src/components/GlobalHeader

    :selected-keys="secretedKeys"@menu-item-click="doMenuClick">
      
import {ref} from "vue";
const secretedKeys = ref(["/"]);//响应式变量
// 一个生命周期,vue路由改变后再执行该函数
router.afterEach((to, from, failure) => {
  secretedKeys.value = [to.path];
});

这样刷新后菜单项也会跟路由一致

整体流程:点击菜单项 => 更新路由 => 同步菜单项

栅格组件

https://arco.design/vue/component/grid
用里面的flex,因为左侧菜单栏是不固定的,右侧用户信息是固定的
将用户头像放到导航栏的右侧,复制示例代码,把外层div去掉,把菜单栏放到auto这一列,用户放到固定的那一列
image.png
给栅格最外面添加align="center"属性,垂直布局,可以让用户名与菜单栏都上下居中对齐

全局状态管理

所有页面共享的变量,而不是局限在某一个页面中。
适合作为全局状态的数据:已登录用户信息(每个页面几乎都要用)
工具:Vuex:https://vuex.vuejs.org/zh/guide/(vue-cli 脚手架已自动引入)
Vuex的本质:给你提供了一套增删改查全局变量的API,只不过可能多了一些功能(比如时间旅行)
image.png
state:存储的状态信息,比如用户信息
mutation(尽量同步):定义了对变量进行增删改的方法
action(支持异步):执行异步操作,并且触发mutation的更改,调用mutation
modules(模块):把一个大的state(全局变量)划分为多个小模块,比如user专门存用户的信息
实现
定义store/user.ts ,存储用户信息

// initial state
import { StoreOptions } from "vuex";

export default {
  namespaced: true,
  state: () => ({
    loginUser: {
      userName: "未登录",
    },
  }),
  actions: {
    getLoginUser({ commit, state }, payload) {
      commit("updateUser", { userName: "鱼皮" });
    },
  },
  mutations: {
    updateUser(state, payload) {
      state.loginUser = payload;
    },
  },
} as StoreOptions<any>;

在index.ts文件中导入user模块

import { createStore } from "vuex";
import user from "./user";

export default createStore({
  mutations: {},
  actions: {},
  modules: {
    user,
  },
});

在Vue页面中可以获取已存储的状态变量

const store = useStore();
store.state.user?.loginUser

在Vue页面中可以修改状态变量

使用dispatch来调用之前定义好的actions,路径是模块名/方法名,传入参数userName

store.dispatch("user/getLoginUser", {
  userName: "鱼皮",
});

全局权限管理

我们希望能够直接以一套通用的机制,去定义哪个页面需要那些权限,而不用每个页面独立去判断权限,提高效率
思路
1、在路由配置文件,定义某个路由的访问权限
2、在全局页面组件app.vue中,绑定一个全局路由监听,每次访问页面时,根据用户要访问页面的路由信息,先判断用户是否有对应的访问权限,(vue生命周期的跳转路由前)
3、如果有,跳转到原页面,如果没有权限,拦截或跳转到401或登录页
步骤
1,route.ts中配置页面的访问权限,meta.access

 {
    path: "/noAuth",
    name: "无权限",
    component: NoAuthView,
  },
  {
    path: "/admin",
    name: "管理员可见",
    component: AdminView,
    meta: {
      access: "canAdmin",
    },
  },

2、app.Vue中设置监听函数,在路由跳转之前校验

const router = useRouter();

const store = useStore();
router.beforeEach((to, from, next) => {
  if (to.meta?.access === "canAdmin") {
    if (store.state.user.loginUser?.role !== "admin") {
      next("/noAuth");
      return;
    }
  }
    next();
});

这样以后,如果用户不是管理员并且还访问了需要管理员权限的界面,就会被重定向到无权限界面

根据配置控制菜单项的显隐

1)routes.ts给路由新增一个标志位,用于判断路由是否显隐

  {
    path: "/hide",
    name: "隐藏页面",
    component: HomeView,
    meta: {
      hideInMenu: true,
    },
  },

2)先过滤只需要展示的元素数组

/ 展示在菜单的路由数组
const visibleRoutes = routes.filter((item, index) => {
  if (item.meta?.hideInMenu) { //这个属性为true则不能通过过滤
    return false;
  }
  return true;
});

前面路由遍历改成过滤后的路由数组

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

注意:v-for和v-if一起用,会先循环所有的元素,导致性能的浪费

根据权限隐藏菜单

需求:只有具有权限的菜单,才对用户可见
思路:类似上面的控制路由显示隐藏,只要判断用户没有这个权限,就直接过滤掉
1)新建access/accessEnum

/**
 * 权限定义
 */
const ACCESS_ENUM = {
  NOT_LOGIN: "notLogin",
  USER: "user",
  ADMIN: "admin",
};

export default ACCESS_ENUM;

2)定义一个公共的权限校验方法
access/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;

3)修改GlobalHeader动态菜单组件,根据权限来过滤菜单
注意:要使用计算属性computed,使其当登录用户信息发生变更时,触发菜单栏的重新渲染,展示新增权限的菜单项

const visibleRoutes = computed(() => {
  return routes.filter((item, index) => {
    if (item.meta?.hideInMenu) {
      return false;
    }
    // 根据权限过滤菜单
    if (
      !checkAccess(store.state.user.loginUser, item?.meta?.access as string)
    ) {
      return false;
    }
    return true;
  });
});

全局项目入口

app.vue中预留一个可以编写全局初始化逻辑的代码

/**
 * 全局初始化函数,有全局单次调用的代码,都可以写到这里
 */
const doInit = () => {
  console.log("hello 欢迎来到我的项目");
};

onMounted(() => {
  doInit();
});

项目地址

(求求大佬们赏个star~)

前端:https://github.com/IMZHEYA/yoj-frontend
后端:https://github.com/IMZHEYA/yoj-backend
代码沙箱:https://github.com/IMZHEYA/yoj-code-sandbox

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值