【sduoj】首页布局

2021SC@SDUSC

App.vue

该项目是一个由Vue.js框架所搭建的单页面应用(SPA),我们所看见的所有页面实际上均是在App.vue的基础上通过组件之间的切换完成的。以下是App.vue的源代码:

<template>
	<div id="app">
		<router-view />
	</div>
</template>

<script>
export default {
	created() {
		console.log(
			"███████╗██████╗ ██╗   ██╗ ██████╗      ██╗\n██╔════╝██╔══██╗██║   ██║██╔═══██╗     ██║\n███████╗██║  ██║██║   ██║██║   ██║     ██║\n╚════██║██║  ██║██║   ██║██║   ██║██   ██║\n███████║██████╔╝╚██████╔╝╚██████╔╝╚█████╔╝\n╚══════╝╚═════╝  ╚═════╝  ╚═════╝  ╚════╝ \n"
		);
		if (localStorage.getItem("darkTheme") == null) {
			localStorage.setItem("darkTheme", false);
		}
		if (localStorage.getItem("token") != null) {
			let jwt = require("jsonwebtoken");
			const TOKEN = jwt.decode(localStorage.getItem("token"));
			let exp = TOKEN.exp * 1000;
			if (exp <= new Date().getTime()) {
				this.$message({
					message: "您的身份认证已过期,请重新登陆",
					type: "warning",
					duration: 1000,
					onClose: () => {
						localStorage.removeItem("token");
						this.$router.push("/login");
					},
				});
			} else {
				// 此处判定方式仅供参考
				this.$store.dispatch("setNoToken", false);
				if (TOKEN.ROLE.indexOf("ADMINISTRATOR") != -1) {
					this.$store.dispatch("setIsAdmin", true);
				} else if (TOKEN.ROLE.indexOf("ROLE_TUTOR") != -1) {
					this.$store.dispatch("setIsTeacher", true);
				}
			}
		}
	},
};
</script>

<style lang="less">
#app {
	font-family: Avenir, Helvetica, Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-align: center;
	color: #2c3e50;
}
</style>

模板<template />

作为整个单页应用的核心,App.vue的视图模板似乎并不复杂,只有一个<router-view />标签展示在页面当中。实际上正式这个标签,承担了展示整个项目99%的页面的功能。这是vue-router中携带的组件之一,用来渲染通过Vue路由映射过来的组件,当路由路径更改时,其内部所展示的内容也会发生更改。

脚本<script />

作为整个项目的启动页,App.vue需要完成几件事,最重要的就是对全局所需要的数据进行初始化。比如created()中的:


if (localStorage.getItem("token") != null) {
	let jwt = require("jsonwebtoken");
	const TOKEN = jwt.decode(localStorage.getItem("token"));
	let exp = TOKEN.exp * 1000;
	if (exp <= new Date().getTime()) {
		this.$message({
			message: "您的身份认证已过期,请重新登陆",
			type: "warning",
			duration: 1000,
			onClose: () => {
				localStorage.removeItem("token");
				this.$router.push("/login");
			},
		});
	} else {
		this.$store.dispatch("setNoToken", false);
		if (TOKEN.ROLE.indexOf("ADMINISTRATOR") != -1) {
			this.$store.dispatch("setIsAdmin", true);
		} else if (TOKEN.ROLE.indexOf("ROLE_TUTOR") != -1) {
			this.$store.dispatch("setIsTeacher", true);
		}
	}
}

此处是用来判断用户登录状态及身份的功能代码。首先从localStorage中取出存有用户身份信息的token,使用jsonwebtoken进行解析,判断用户token是否过期,并根据用户的不同身份向Vuex分发对应的action,以达到初始化数据的目的。

首页布局

sduoj首页主要由三部分构成:

  • ojheader:首页头部栏。根据用户登录状态呈现不同的样式,为系统提供用户登录注册以及注销等功能的入口。
  • ojaside:首页侧边栏。主要用于系统的页面导航。根据用户登录状态及通过jwt解析出的用户身份选择性展示不同的导航项。
  • layout:主窗口。用于放置<router-view />组件,根据不同的嵌套路由在主窗口中展示不同的页面组件。
    首页布局

Home.vue

首页Vue文件源代码:

<template>
	<div class="home">
		<ojheader />
		<el-container>
			<ojaside :pageIndex="$route.path" />
			<el-main>
				<layout />
			</el-main>
		</el-container>
	</div>
</template>

<script>
import Layout from "@/components/layout.vue";
import Ojaside from "@/components/ojaside.vue";
import Ojheader from "@/components/ojheader.vue";
export default {
	components: {
		Ojheader,
		Ojaside,
		Layout,
	},
};
</script>

该vue文件路径为@/pages/Home.vue,用作整个项目首页的Vue文件。结构比较简单,主要由上述三个组件构成。由于ojheaderojaside在许多页面都会重复出现,故此处将这两个组件直接放置在Home.vue中。而每个页面的页面组件则通过嵌套路由,在<layout />组件中的<router-view />中展示。
在这个vue文件中,我们将$route.path作为名为pageIndexprops传给了<ojaside />组件,是为了让侧边栏根据当前的路由状态自动更新侧边栏所选中的条目。下面让我们来依次分析三个组件的源码。

ojheader

<template>
	<div>
		<el-header style="text-align: right; background: rgba(215, 215, 215); height: 10vh; line-height: 10vh; padding-left: 0px">
			<div v-if="noToken">
				<el-button plain @click="Register">注册</el-button>
				<el-button @click="Login" type="primary">登录</el-button>
			</div>
			<div v-else>
				<el-dropdown class="drop" :show-timeout="0">
					<div style="margin-right: 30px" @mouseenter="nameColor='dodgerblue';" @mouseleave="nameColor='#000';">
						<el-avatar style="vertical-align: middle" :src="avatar"><i style="font-size: 25px; vertical-align: middle;" class="el-icon-loading"></i></el-avatar>
						<el-link :underline="false" :style="{'font-size': '25px', 'font-family': 'KaiTi', 'margin-left': '5px', 'color': nameColor}">{{ username }}</el-link>
					</div>
					<el-dropdown-menu>
						<el-dropdown-item @click.native="myInfo"><i class="el-icon-user"></i>个人信息</el-dropdown-item>
						<el-dropdown-item @click.native="Logout"><i class="el-icon-switch-button"></i>退出登录</el-dropdown-item>
					</el-dropdown-menu>
				</el-dropdown>
			</div>
		</el-header>
	</div>
</template>

<script>
export default {
	name: "ojheader",
	data() {
		return {
			noToken: this.$store.state.noToken,
			username: localStorage.getItem("nickname"),
			avatar: require("@/assets/icon/student.svg"),
			nameColor: "#000",
			isTeacher: this.$store.state.isTeacher,
			isAdmin: this.$store.state.isAdmin,
		};
	},
	methods: {
		Logout() {
			Promise.all([
				this.$store.dispatch("setNoToken", true),
				this.$store.dispatch("setIsAdmin", false),
			]).then(() => {
				localStorage.removeItem("token");
				this.$router.go(0);
			});
		},
		myInfo() {
			this.$router.push({ path: "/info" });
		},
		Login() {
			this.$router.push({ path: "/login" });
		},
		Register() {
			this.$router.push({ path: "/register" });
		},
	},
};
</script>

<style>
.drop:hover {
	cursor: pointer;
}
</style>

以上是ojheader.vue文件的源码,其路径为@/components/ojheader.vue
在视图层,使用ElementUI中的el-header作为顶栏布局容器。在容器中根据用户的登录状态动态选择渲染某个div。由于用户的登录状态不会经常性地发生改变,故此处使用v-ifv-else属性进行选择。登录后会在右侧创建一个包括用户头像及用户名的下拉列表,目前可用来进行注销登录以及查看个人信息的功能。
在上述代码中,我们看见了this.$store的字样,这是该项目中用来储存全局状态的一个仓库,使用Vuex作为全局状态管理容器,目前暂不做具体分析。我们在项目的入口文件main.js中引入了该文件,作为Vue.prototype.$store使用。
在执行注销功能的代码中,通过使用Promise.all同时向Vuex分发两条actions,在两条dispatch语句返回的Promise对象状态均变为fulfilled时,清空本地储存的token数据。

ojaside

<template>
	<div class="ojaside">
		<el-menu :uniqueOpened="true" :default-active="pageIndex" :router="true" class="el-menu-vertical-demo" active-text-color="#3898FF">
			<el-menu-item index="/">
				<template #title>
					<i class="el-icon-menu" />
					<span>首页</span>
				</template>
			</el-menu-item>

			<el-submenu index="student" :disabled="noToken">
				<template #title>
					<i class="el-icon-location" />
					<span>学生相关</span>
				</template>
				<el-menu-item index="/student/problems">题目列表</el-menu-item>
				<el-menu-item index="/student/userList">用户组列表</el-menu-item>
			</el-submenu>

			<el-submenu index="teacher" v-if="!noToken&&(isTeacher||isAdmin)">
				<template #title>
					<i class="el-icon-location" />
					<span>教师相关</span>
				</template>
				<el-menu-item index="/teacher/newProblem">出题</el-menu-item>
				<el-menu-item index="/teacher/myProblems">教师查题</el-menu-item>
				<el-menu-item index="/teacher/myProblemSets">查看题目集</el-menu-item>
				<el-menu-item index="/teacher/userList">用户组列表</el-menu-item>
			</el-submenu>

			<el-submenu index="admin" v-if="!noToken&&isAdmin">
				<template #title>
					<i class="el-icon-location" />
					<span>管理员相关</span>
				</template>
				<el-menu-item index="/admin/excel">学生信息上传</el-menu-item>
				<el-menu-item index="/admin/bind">学生信息绑定</el-menu-item>
				<el-menu-item index="/admin/role">教师权限管理</el-menu-item>
			</el-submenu>
		</el-menu>
	</div>
</template>

<script>
export default {
	name: "ojaside",
	props: {
		pageIndex: String,
	},
	data() {
		return {
			noToken: this.$store.state.noToken,
			isTeacher: this.$store.state.isTeacher,
			isAdmin: this.$store.state.isAdmin,
		};
	},
};
</script>

<style scoped>
.el-menu {
	text-align: left;
}
.ojaside {
	position: relative;
	display: inline-block;
	width: 240px;
	overflow: hidden;
	height: 100%;
}
</style>

以上是@/components/ojaside.vue的代码。可以看到,在Home.vue中,我们传入了一个pageIndex,在该组件的props中进行了接收,并将其用在了el-menudefault-active属性中,用来指向当前的导航项目。
el-menu中指定了几个属性:

  • :uniqueOpened="true":保证导航页是单栏展开。
  • :router="true":通过路由模式进行跳转。

第一个属性很好理解,那么什么叫做通过路由模式进行跳转呢?其实就是使用vue-router的模式,启用该模式会在激活导航时以index作为path进行路由跳转。也就是在点击对应的el-menu-item时,会将<el-menu-item />标签中的index属性作为路由路径进行跳转。

layout

<template>
	<div class="layout">
		<router-view />
	</div>
</template>

<script>
export default {
	name: "layout",
};
</script>

这个组件非常简单,仅仅作为一个<router-view />的容器,用以根据不同的嵌套路由渲染不同的页面组件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小栗帽今天吃什么

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值