赞
踩
耗时一个月开发的OJ在线判题系统,文末有项目地址,目前还在更新代码~
现在让我们来自主开发打造一套前端开发项目模版
nodeJS环境版本:
node -v #我的是20
npm -v #我的是10
使用vue-cli脚手架
https://cli.vuejs.org/zh/
1、安装脚手架
npm install -g @vue/cli
注意:如果出现报错
则使用以下命令,强制安装覆盖新版本脚手架
npm install -g @vue/cli --force
2、创建项目(项目名居然不能包含大写字母)
vue create yoj-frontend
3、运行项目,能访问到主页即成功
因为在创建项目时我们选择了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
执行命令:
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');
选择组件库的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>
现在界面就成了这样分为导航栏,内容,版权信息三部分
目的是想让用户点击菜单切换后可以跳转到对应的页面
步骤
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>
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();
const doMenuClick = (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 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。