导师怀疑我的毕设前端不是自己写的!我把前端模版开发的过程甩他脸上了!
诨号无敌鸭 2024-07-15 12:33:03 阅读 59
耗时一个月开发的OJ在线判题系统,文末有项目地址,目前还在更新代码~
现在让我们来自主开发打造一套前端开发项目模版
文章目录
确认环境初始化image.pngvscode格式化配置Prettier引入组件库项目通用布局实现新建基础布局BasicLayout新建导航菜单栏导航菜单路由实现栅格组件
全局状态管理全局权限管理根据配置控制菜单项的显隐根据权限隐藏菜单全局项目入口项目地址
确认环境
nodeJS环境版本:
node -v #我的是20
npm -v #我的是10
初始化
使用vue-cli脚手架
https://cli.vuejs.org/zh/
1、安装脚手架
npm install -g @vue/cli
注意:如果出现报错
则使用以下命令,强制安装覆盖新版本脚手架
<code>npm install -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
执行命令:
<code>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">code>
<a-layout style="height: 400px">code>
<a-layout-header class="header">code>
<GlobalHeader/>
</a-layout-header>
<a-layout-content class="content">code>
<roter-view/>
</a-layout-content>
<a-layout-footer class="footer">code>
<a href="https://blog.csdn.net/m0_74870396?type=blog" target="_blank">code>
诨号无敌鸭
</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">code>
<BasicLayout />
</div>
</template>
<script setup lang="ts">code>
import BasicLayout from "./layouts/BasicLayout.vue";
</script>
<style></style>
新建导航菜单栏
https://arco.design/vue/component/menu
导航菜单也是个通用模块,所以我们单独抽象出来GlobalHeader
坐标:src/components/GlobalHeader
<template>
<div id="globalHeader">code>
<a-menu mode="horizontal" :default-selected-keys="['1']">code>
<a-menu-item
key="0"code>
:style="{ padding: 0, marginRight: '38px' }"code>
disabled
>
<div class="title-bar">code>
<img class="logo" src="../assets/icon22.jpg" />code>
<div class="title">Y OJ</div>code>
</div>
</a-menu-item>
<a-menu-item key="1">Home</a-menu-item>code>
<a-menu-item key="2">Solution</a-menu-item>code>
<a-menu-item key="3">Cloud Service</a-menu-item>code>
<a-menu-item key="4">Cooperation</a-menu-item>code>
</a-menu>
</div>
</template>
<script setup lang="ts"></script>code>
<!-- 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
<code>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">code>
{ { item.name}}
</a-menu-item>
<script setup lang="ts">code>
import { routes} from "../router/routes";
</script>
3)绑定跳转事件
接下来我们需要点击菜单后,跳转到对应的路由,查看官网API,得到点击事件API
修改src/components/GlobalHeader实现点击菜单跳转到对应的页面
<code> <a-menu mode="horizontal" :default-selected-keys="['1']" @menu-item-click="doMenuClick">code>
<script setup lang="ts">code>
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
<code> :selected-keys="secretedKeys"@menu-item-click="doMenuClick">code>
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 ,存储用户信息
<code>// 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">code>
{ { 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
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。