耗时一个月开发的OJ在线判题系统,文末有项目地址,目前还在更新代码~
现在让我们来自主开发打造一套前端开发项目模版
文章目录
确认环境
nodeJS环境版本:
node-v#我的是20npm-v#我的是10
初始化
使用vue-cli脚手架
https://cli.vuejs.org/zh/
1、安装脚手架
npminstall-g @vue/cli
注意:如果出现报错
则使用以下命令,强制安装覆盖新版本脚手架
npminstall-g @vue/cli --force
2、创建项目(项目名居然不能包含大写字母)
vue create yoj-frontend
3、运行项目,能访问到主页即成功
vscode格式化配置Prettier
因为在创建项目时我们选择了Prettier对代码进行检查,所以代码如果写的不规范会报错
此时,我们需要在vscode中进行设置,按规定的语法进行自动格式化
下载插件prettier
对扩展进行设置
在页面代码处,右键,选择"Format Document",然后选择prettier,alt + shift + f格式化代码,即可按照Prettier的语法规范格式化代码了
太麻烦了,我选择关闭prettier警告
找到项目里的.eslintrc.js文件,在rules里面添加一句"prettier/prettier": “off”,重启项目;
引入组件库
脚手架自动整合了vue-router:URL地址改变时加载对应的界面
组件库:https://arco.design/
(字节跳动开发的一个组件库)
快速上手: https://arco.design/vue/docs/start
执行命令:
npminstall --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">YOJ</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>
现在界面就成了这样分为导航栏,内容,版权信息三部分
导航菜单路由实现
目的是想让用户点击菜单切换后可以跳转到对应的页面
步骤
1)提取通用路由文件
坐标:src/router/routes
import{ RouteRecordRaw }from"vue-router";import HomeView from"../views/HomeView.vue";exportconstroutes: 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,});exportdefault 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>
3)绑定跳转事件
接下来我们需要点击菜单后,跳转到对应的路由,查看官网API,得到点击事件API
修改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();constdoMenuClick=(key:string)=>{
router.push({path:key,});}</script>
这样就实现了点击菜单跳转到对应的页面
4)同步路由的更新到菜单项高亮
现在情况是用户刷新后,路由还是那个路由,但是菜单项就没了
刷新前
刷新后
所以我们需要同步路由的更新到菜单项高亮上
继续修改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这一列,用户放到固定的那一列
给栅格最外面添加align="center"属性,垂直布局,可以让用户名与菜单栏都上下居中对齐
全局状态管理
所有页面共享的变量,而不是局限在某一个页面中。
适合作为全局状态的数据:已登录用户信息(每个页面几乎都要用)
工具:Vuex:https://vuex.vuejs.org/zh/guide/(vue-cli 脚手架已自动引入)
Vuex的本质:给你提供了一套增删改查全局变量的API,只不过可能多了一些功能(比如时间旅行)
state:存储的状态信息,比如用户信息
mutation(尽量同步):定义了对变量进行增删改的方法
action(支持异步):执行异步操作,并且触发mutation的更改,调用mutation
modules(模块):把一个大的state(全局变量)划分为多个小模块,比如user专门存用户的信息
实现
定义store/user.ts ,存储用户信息
// initial stateimport{ StoreOptions }from"vuex";exportdefault{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";exportdefaultcreateStore({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则不能通过过滤returnfalse;}returntrue;});
前面路由遍历改成过滤后的路由数组
<a-menu-item v-for="item in visible":key="item.path">{{ item.name }}</a-menu-item>
注意:v-for和v-if一起用,会先循环所有的元素,导致性能的浪费
根据权限隐藏菜单
需求:只有具有权限的菜单,才对用户可见
思路:类似上面的控制路由显示隐藏,只要判断用户没有这个权限,就直接过滤掉
1)新建access/accessEnum
/**
* 权限定义
*/constACCESS_ENUM={NOT_LOGIN:"notLogin",USER:"user",ADMIN:"admin",};exportdefaultACCESS_ENUM;
2)定义一个公共的权限校验方法
access/checkAccess.ts
importACCESS_ENUMfrom"@/access/accessEnum";/**
* 检查权限(判断当前登录用户是否具有某个权限)
* @param loginUser 当前登录用户
* @param needAccess 需要有的权限
* @return boolean 有无权限
*/constcheckAccess=(loginUser: any, needAccess =ACCESS_ENUM.NOT_LOGIN)=>{// 获取当前登录用户具有的权限(如果没有 loginUser,则表示未登录)const loginUserAccess = loginUser?.userRole ??ACCESS_ENUM.NOT_LOGIN;if(needAccess ===ACCESS_ENUM.NOT_LOGIN){returntrue;}// 如果用户登录才能访问if(needAccess ===ACCESS_ENUM.USER){// 如果用户没登录,那么表示无权限if(loginUserAccess ===ACCESS_ENUM.NOT_LOGIN){returnfalse;}}// 如果需要管理员权限if(needAccess ===ACCESS_ENUM.ADMIN){// 如果不为管理员,表示无权限if(loginUserAccess !==ACCESS_ENUM.ADMIN){returnfalse;}}returntrue;};exportdefault checkAccess;
3)修改GlobalHeader动态菜单组件,根据权限来过滤菜单
注意:要使用计算属性computed,使其当登录用户信息发生变更时,触发菜单栏的重新渲染,展示新增权限的菜单项
const visibleRoutes =computed(()=>{return routes.filter((item, index)=>{if(item.meta?.hideInMenu){returnfalse;}// 根据权限过滤菜单if(!checkAccess(store.state.user.loginUser, item?.meta?.access as string)){returnfalse;}returntrue;});});
全局项目入口
app.vue中预留一个可以编写全局初始化逻辑的代码
/**
* 全局初始化函数,有全局单次调用的代码,都可以写到这里
*/constdoInit=()=>{
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
版权归原作者 诨号无敌鸭 所有, 如有侵权,请联系我们删除。