配置MySQL与登录模块

 使用技术

MySQL,Mybatis-plus,spring-security,jwt验证,vue   

1. 配置Mysql

1.1 下载

MySQL :: Download MySQL Installer
1.2 安装

     
其他页面全选默认即可

1.3 配置环境变量
将C:\Program Files\MySQL\MySQL Server 8.0\bin(如果安装到了其他目录,填写相应目录的地址即可)添加到环境变量PATH中,这样就可以在任意目录的终端中执行mysql命令了。

1.4 mysql服务的关闭与启动(默认开机自动启动,如果想手动操作,可以参考如下命令)

关闭:net stop mysql80
启动:net start mysql80
1.5 mysql的常用操作连接用户名为root,密码为123456的数据库服务:mysql -uroot -p123456

show databases;:列出所有数据库
create database kob;:创建数据库
drop database kob;:删除数据库
use kob;:使用数据库kob
show tables;:列出当前数据库的所有表
create table user(id int, username varchar(100)):创建名称为user的表,表中包含id和username两个属性。
drop table user;:删除表
insert into user values(1, 'yxc');:在表中插入数据
select * from user;:查询表中所有数据
delete from user where id = 2;:删除某行数据

2. 配置SpringBoot

Maven仓库地址:https://mvnrepository.com/

MyBatis-Plus官网:MyBatis-Plus
在pom.xml文件中添加依赖:
Spring Boot Starter JDBC
Project Lombok
MySQL Connector/J
mybatis-plus-boot-starter
mybatis-plus-generator
spring-boot-starter-security
jjwt-api
jjwt-impl
jjwt-jackson
在application.properties中添加数据库配置:

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver


SpringBoot中的常用模块

pojo层:将数据库中的表对应成Java中的Class
mapper层(也叫Dao层):将pojo层的class中的操作,映射成sql语句
service层:写具体的业务逻辑,组合使用mapper中的操作
controller层:负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面

3. 修改Spring Security

传统方式:session登录认证

实现security与数据库对接 

实现service.impl.UserDetailsServiceImpl类,继承自UserDetailsService接口,用来接入数据库信息
实现config.SecurityConfig类,用来实现用户密码的加密存储

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

jwt可实现跨域认证,不需要在服务器端存储

加入jwt的三个依赖:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.12.5</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.12.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.12.5</version><scope>runtime</scope>
</dependency>

实现utils.JwtUtil类

为jwt工具类(实现加密信息,解析token),用来创建、解析jwt token

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;@Component
public class JwtUtil {public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";public static String getUUID() {return UUID.randomUUID().toString().replaceAll("-", "");}public static String createJWT(String subject) {JwtBuilder builder = getJwtBuilder(subject, null, getUUID());return builder.compact();}private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;SecretKey secretKey = generalKey();long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if (ttlMillis == null) {ttlMillis = JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);return Jwts.builder().setId(uuid).setSubject(subject).setIssuer("sg").setIssuedAt(now).signWith(signatureAlgorithm, secretKey).setExpiration(expDate);}public static SecretKey generalKey() {byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");}public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(jwt).getBody();}
}

实现config.filter.JwtAuthenticationTokenFilter类

用来验证jwt token,如果验证成功,则将User信息注入上下文中

import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate UserMapper userMapper;@Overrideprotected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("Authorization");if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {filterChain.doFilter(request, response);return;}token = token.substring(7);String userid;try {Claims claims = JwtUtil.parseJWT(token);userid = claims.getSubject();} catch (Exception e) {throw new RuntimeException(e);}User user = userMapper.selectById(Integer.parseInt(userid));if (user == null) {throw new RuntimeException("用户名未登录");}UserDetailsImpl loginUser = new UserDetailsImpl(user);UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request, response);}
}

配置config.SecurityConfig类

package com.kob.backend.config;import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/user/account/token/", "/user/account/register/").permitAll().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}
}

放行登录、注册等接口

4. 编写API

将数据库中的id域变为自增
在数据库中将id列变为自增
在pojo.User类中添加注解:@TableId(type = IdType.AUTO)
实现/user/account/token/:验证用户名密码,验证成功后返回jwt token(令牌)
实现/user/account/info/:根据令牌返回用户信息
实现/user/account/register/:注册账号

5.前端登录与注册

创建页面:
在 views 目录下创建 user ,新建 UserAccountLoginView.vue 和 UserAccountRegisterView.vue

UserAccountLoginView.vue

<template><ContentField><!-- div.row>div.col-3 --><div class="row justify-content-center"><div class="col-3"><!-- 绑定默认函数,提交触发login函数,并阻止掉默认行为 --><form @submit.prevent="login"><div class="mb-3"><label for="username" class="form-label">用户名</label><!-- 绑定username用 v-modle --><input v-model="username" type="text" class="form-control" id="username" aria-describedby="请输入用户名"></div><div class="mb-3"><label for="password" class="form-label">密码</label><input v-model="password" type="password" class="form-control" id="password"aria-describedby="请输入密码"></div><div class="error-message"><!-- 密码错误  -->{{ error_message }}</div><button type="submit" class="btn btn-primary">提交</button></form></div></div></ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
import { useStore } from "vuex";
import { ref } from 'vue';
import router from "../../../router/index"
export default {comments: {ContentField},setup() {const store = useStore();let username = ref('');let password = ref('');let error_message = ref('');const login = () => {//清空 error_messageerror_message.value = "";store.dispatch("login", {username: username.value,password: password.value,success() {// console.log(resp);//成功后调用回调函数,先获取用户信息,getinfo为store.user下自定义函数名store.dispatch("getinfo", {success() {//登录成功,跳转主页面router.push({ name: 'home' });console.log(store.state.user);}})},error() {error_message.value = "用户名或密码有错误";}})}return {username,password,error_message,login,}}
}
</script><style scoped>
button {width: 100%;
}div.error-message {color: red;
}
</style>

UserAccountRegisterView.vue

<template><ContentField>注册</ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
export default {comments: {ContentField}
}
</script><style scoped></style>

修改store--->user.js


import $ from "jquery"export default ({state: {id: "",username: "",photo: "",token: "",is_login: false,},getters: {},//修改数据mutations: {updateUser(state, user) {state.id = user.id;state.username = user.username;state.photo = user.photo;state.is_login = user.is_login;},updateToken(state, token) {state.token = token;},//退出登录只需在前端删除token,退出登录的辅助函数logout(state) {state.id = "";state.username = "";state.photo = "";state.token = "";state.is_login = false;}},// 修改state的辅助函数一般写在actionsactions: {login(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/token/",type: "post",data: {username: data.username,password: data.password,},//获取tokensuccess(resp) {//在LoginServiceImpl中定义的error_message,tokenif (resp.error_message === "success") {//actions调用mutations中的函数需要用commit+字符串context.commit("updateToken", resp.token);data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}});},getinfo(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/info/",type: "get",headers: {Authorization: "Bearer " + context.state.token,},success(resp) {if (resp.error_message === "success") {context.commit("updateUser", {...resp,is_login: true,});data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}});},logout(context) {//logout(3)调用 logout(4)context.commit("logout");}},modules: {}
})

修改rount.js 


import { createRouter, createWebHistory } from 'vue-router'
import NotFound from "../views/error/NotFound"
import PkIndexView from "../views/pk/PkIndexView"
import RanklistIndexView from "../views/ranklist/RanklistIndexView"
import RecordIndexView from "../views/record/RecordIndexView"
import UserBotIndexView from "../views/user/bot/UserBotIndexView"
import UserAccountLoginView from "../views/user/account/UserAccountLoginView"
import UserAccountRegisterView from "../views/user/account/UserAccountRegisterView"
import store from '../store/index'const routes = [{path: "/",name: "home",redirect: "/pk/",meta: {requestAuth: true}},{path: "/pk/",name: "pk_index",component: PkIndexView,meta: {requestAuth: true,}},{path: "/error/",name: "404",component: NotFound,meta: {requestAuth: false}},{path: "/record/",name: "record_index",component: RecordIndexView,meta: {requestAuth: true}},{path: "/ranklist/",name: "ranklist_index",component: RanklistIndexView,meta: {requestAuth: true}},{path: "/user/bot/",name: "user_bot_index",component: UserBotIndexView,meta: {requestAuth: true}},{path: "/user/account/login/",name: "user_account_login",component: UserAccountLoginView,meta: {requestAuth: false}},{path: "/user/account/register/",name: "user_account_register",component: UserAccountRegisterView,meta: {requestAuth: false}},// 重定向到404{path: "/:catchAll(.*)",redirect: "/error/"}]const router = createRouter({history: createWebHistory(),routes
})//router在起作用之前执行的一个函数
router.beforeEach((to, from, next) => {// 如果发现去的没有授权和登录,重定向到login,否则跳转默认页面if (to.meta.requestAuth && store.state.is_login) {next({ name: "user_account_login" });} else {next();}
})
export default router

 store--->index.js

import { createStore } from 'vuex'
import ModuleUser from './user'export default createStore({state: {},getters: {},mutations: {},actions: {},modules: {user: ModuleUser,}
})

修改components-->NavBar.vue

<!-- html -->
<template><nav class="navbar navbar-expand-lg  navbar-dark bg-dark"><div class="container"><!-- 刷新 --><!-- <a class="navbar-brand" href="/">King Of Bots</a> --><!-- 点击页面不刷新用router-link --><router-link class="navbar-brand" :to="{ name: 'pk_index' }">King Of Bots</router-link><div class="collapse navbar-collapse" id="navbarText"><ul class="navbar-nav me-auto mb-2 mb-lg-0"><li class="nav-item"><!-- active高亮 --><!-- <router-link class="nav-link active " :to="{ name: 'pk_index' }">对战</router-link> --><!-- 选中的高亮 --><router-link :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'pk_index' }">对战</router-link></li><li class="nav-item"><router-link :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'record_index' }">对局列表</router-link></li><li class="nav-item"><router-link :class="route_name == 'ranklist_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'ranklist_index' }">排行榜</router-link></li></ul><ul class="navbar-nav" v-if="$store.state.user.is_login"><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"aria-expanded="false">{{ $store.state.user.username }}</a><ul class="dropdown-menu"><li><router-link class="dropdown-item" :to="{ name: 'user_bot_index' }">my bot</router-link></li><!--  @click="logout"点击调用logout(1)函数 --><li><a class="dropdown-item" href="#" @click="logout">exit</a></li></ul></li></ul><ul class="navbar-nav" v-else><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_login' }" role="button">登录</router-link></li><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_register' }" role="button">注册</router-link></li></ul></div></div></nav>
</template><!-- js -->
<script >
// 实现选中的页面高亮
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {setup() {const store = useStore();const route = useRoute();let route_name = computed(() => route.name)//触发函数logout(1) 调用logout(2)const logout = () => {// 调用user.js中的logout(3)store.dispatch("logout");}return {route_name,logout}}
}
</script><!-- css -->
<!-- scoped 作用:写的css会加上一个随机字符串,使得样式不会影响组件以外的部分 -->
<style scoped></style>

 

 前端页面授权

修改router-->index.js

import { createRouter, createWebHistory } from 'vue-router'
import NotFound from "../views/error/NotFound"
import PkIndexView from "../views/pk/PkIndexView"
import RanklistIndexView from "../views/ranklist/RanklistIndexView"
import RecordIndexView from "../views/record/RecordIndexView"
import UserBotIndexView from "../views/user/bot/UserBotIndexView"
import UserAccountLoginView from "../views/user/account/UserAccountLoginView"
import UserAccountRegisterView from "../views/user/account/UserAccountRegisterView"
import store from '../store/index'const routes = [{path: "/",name: "home",redirect: "/pk/",meta: {requestAuth: true}},{path: "/pk/",name: "pk_index",component: PkIndexView,meta: {requestAuth: true}},{path: "/error/",name: "404",component: NotFound,meta: {requestAuth: false}},{path: "/record/",name: "record_index",component: RecordIndexView,meta: {requestAuth: true}},{path: "/ranklist/",name: "ranklist_index",component: RanklistIndexView,meta: {requestAuth: true}},{path: "/user/bot/",name: "user_bot_index",component: UserBotIndexView,meta: {requestAuth: true}},{path: "/user/account/login/",name: "user_account_login",component: UserAccountLoginView,meta: {requestAuth: false}},{path: "/user/account/register/",name: "user_account_register",component: UserAccountRegisterView,meta: {requestAuth: false}},// 重定向到404{path: "/:catchAll(.*)",redirect: "/error/"}]const router = createRouter({history: createWebHistory(),routes
})//router在起作用之前执行的一个函数
router.beforeEach((to, from, next) => {// 如果发现去的没有授权和登录,重定向到login,否则跳转默认页面if (to.meta.requestAuth && !store.state.is_login) {next({ name: "user_account_login" });} else {next();}
})
export default router

注册页面

<template><ContentField><div class="row justify-content-center"><div class="col-3"><!-- 绑定默认函数,提交触发login函数,并阻止掉默认行为 --><form @submit.prevent="register"><div class="mb-3"><label for="username" class="form-label">用户名</label><input v-model="username" type="text" class="form-control" id="username" placeholder="请输入用户名"></div><div class="mb-3"><label for="password" class="form-label">密码</label><input v-model="password" type="password" class="form-control" id="password" placeholder="请输入密码"></div><div class="mb-3"><label for="confirmedPassword" class="form-label">确认密码</label><input v-model="confirmedPassword" type="password" class="form-control" id="confirmedPassword"placeholder="请再次输入密码"></div><div class="error-message">{{ error_message }}</div><button type="submit" class="btn btn-primary">提交</button></form></div></div></ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
import { ref } from 'vue'
import router from "../../../router/index";import $ from 'jquery'export default {comments: {ContentField},setup() {let username = ref('');let password = ref('');let confirmedPassword = ref('');let error_message = ref('');const register = () => {$.ajax({url: "http://127.0.0.1:8080/user/account/register/",//如果是修改数据库用post,只获取数据用gettype: "post",data: {username: username.value,password: password.value,confirmedPassword: confirmedPassword.value,},// "success" : function(resp){//     console.log(resp);// },//简化版,关键字中的引号可以去掉,函数可以省略简写success(resp) {if (resp.error_message === "suceess") {router.push({ name: "user_account_login" });} else {//不成功,显示错误信息error_message.value = resp.error_message;}},"error": function (resp) {console.log(resp);}})}return {username,password,confirmedPassword,error_message,register,}}}
</script><style scoped>
button {width: 100%;
}div.error-message {color: red;
}
</style>

登录的持久化

UserAccountLoginView.vue

<template><!-- <ContentField v-if="show_content"> --><ContentField v-if="!$store.state.user.pulling_info"><!-- div.row>div.col-3 --><div class="row justify-content-center"><div class="col-3"><!-- 绑定默认函数,提交触发login函数,并阻止掉默认行为 --><form @submit.prevent="login"><div class="mb-3"><label for="username" class="form-label">用户名</label> --><!-- 绑定username用 v-modle --><input v-model="username" type="text" class="form-control" id="username" placeholder="请输入用户名"></div><div class="mb-3"><label for="password" class="form-label">密码</label><input v-model="password" type="password" class="form-control" id="password" placeholder="请输入密码"></div><div class="error-message"><!-- 密码错误  -->{{ error_message }}</div><button type="submit" class="btn btn-primary">提交</button></form></div></div></ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
import { useStore } from "vuex";
import { ref } from 'vue';
import router from "../../../router/index"
export default {comments: {ContentField},setup() {const store = useStore();let username = ref('');let password = ref('');let error_message = ref('');// let show_content = ref(false);//取出token,如果不为空,调用updateToken,getinfo//如果没有满足已经获取token,则不需要再调用登录页面,否则更新时会闪过loginconst jwt_token = localStorage.getItem("jwt_token");if (jwt_token) {store.commit("updateToken", jwt_token);store.dispatch("getinfo", {success() {router.push({ name: "home" });store.commit("updatePullingInfo", false);},error() {//如果token过期了,展示出登录页面// show_content.value = true;store.commit("updatePullingInfo", false);}})} else {//如果本地没有jwt_token的话也要展示出来// show_content.value = true;//拉取结束store.commit("updatePullingInfo", false);}const login = () => {//清空 error_messageerror_message.value = "";store.dispatch("login", {username: username.value,password: password.value,success() {// console.log(resp);//成功后调用回调函数,先获取用户信息,getinfo为store.user下自定义函数名store.dispatch("getinfo", {success() {//登录成功,跳转主页面router.push({ name: "home" });// 调// console.log(store.state.user);}})},error() {error_message.value = "用户名或密码有错误";}})}return {username,password,error_message,login,// show_content,}}
}
</script>

 修改store--->user.js

import $ from 'jquery'export default {state: {id: "",username: "",photo: "",token: "",is_login: false,pulling_info: true,  // 是否正在从云端拉取信息},getters: {},mutations: {updateUser(state, user) {state.id = user.id;state.username = user.username;state.photo = user.photo;state.is_login = user.is_login;},updateToken(state, token) {state.token = token;},logout(state) {state.id = "";state.username = "";state.photo = "";state.token = "";state.is_login = false;},updatePullingInfo(state, pulling_info) {state.pulling_info = pulling_info;}},actions: {login(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/token/",type: "post",data: {username: data.username,password: data.password,},success(resp) {if (resp.error_message === "success") {localStorage.setItem("jwt_token", resp.token);context.commit("updateToken", resp.token);data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}});},getinfo(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/info/",type: "get",headers: {Authorization: "Bearer " + context.state.token,},success(resp) {if (resp.error_message === "success") {context.commit("updateUser", {...resp,is_login: true,});data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}})},logout(context) {localStorage.removeItem("jwt_token");context.commit("logout");}},modules: {}
}

修改NavBar.vue

<!-- html -->
<template><nav class="navbar navbar-expand-lg  navbar-dark bg-dark"><div class="container"><!-- 刷新 --><!-- <a class="navbar-brand" href="/">King Of Bots</a> --><!-- 点击页面不刷新用router-link --><router-link class="navbar-brand" :to="{ name: 'pk_index' }">King Of Bots</router-link><div class="collapse navbar-collapse" id="navbarText"><ul class="navbar-nav me-auto mb-2 mb-lg-0"><li class="nav-item"><!-- active高亮 --><!-- <router-link class="nav-link active " :to="{ name: 'pk_index' }">对战</router-link> --><!-- 选中的高亮 --><router-link :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'pk_index' }">对战</router-link></li><li class="nav-item"><router-link :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'record_index' }">对局列表</router-link></li><li class="nav-item"><router-link :class="route_name == 'ranklist_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'ranklist_index' }">排行榜</router-link></li></ul><ul class="navbar-nav" v-if="$store.state.user.is_login"><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"aria-expanded="false">{{ $store.state.user.username }}</a><ul class="dropdown-menu"><li><router-link class="dropdown-item" :to="{ name: 'user_bot_index' }">my bot</router-link></li><!--  @click="logout"点击调用logout(1)函数 --><li><a class="dropdown-item" href="#" @click="logout">exit</a></li></ul></li></ul><!-- 没有拉取信息时再展示 --><ul class="navbar-nav" v-else-if="!$store.state.user.pulling_info"><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_login' }" role="button">登录</router-link></li><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_register' }" role="button">注册</router-link></li></ul></div></div></nav>
</template><!-- js -->
<script >
// 实现选中的页面高亮
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {setup() {const store = useStore();const route = useRoute();let route_name = computed(() => route.name)//触发函数logout(1) 调用logout(2)const logout = () => {// 调用user.js中的logout(3)store.dispatch("logout");}return {route_name,logout}}
}
</script><!-- css -->
<!-- scoped 作用:写的css会加上一个随机字符串,使得样式不会影响组件以外的部分 -->
<style scoped></style>

项目实战——配置MySQL与Spring Security模块_springsecurity数据库mysq设计-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/504778.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式学习31-指针和函数知识回顾

1.指针&#xff1a; 1.提供一种间接访问数据的方法 2.空间没有名字,只有一个地址编号 2.指针: 1.地址:区分不同内存空间的编号 2.指针:指针就是地址,地址就是指针 3.指针变量:存放指针的变量称为指针变量,简称为指针 3.指针的定义: int *p NULL; …

学生云服务器_学生云主机_学生云数据库_云+校园特惠套餐

2024年腾讯云学生服务器优惠活动「云校园」&#xff0c;学生服务器优惠价格&#xff1a;轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年&#xff0c;轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年&#xff0c;CVM云服务器2核4G配置842.4元一年&…

‘conda‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件

如果你在运行 conda 命令时收到了 ‘conda’ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 的错误消息&#xff0c;这可能意味着 Anaconda 并没有正确地添加到你的系统路径中。 1.你可以尝试手动添加 Anaconda 到系统路径中。以下是在 Windows 系统上添加…

【风格迁移】DSM-GANs:为不同的域(照片和绘画风格)创建特定的映射函数,以改善风格转换的质量和准确性

DSM-GANs&#xff1a;为不同的域&#xff08;照片和绘画风格&#xff09;创建特定的映射函数&#xff0c;以改善风格转换的质量和准确性 提出背景DSM-GANs 域特定映射 域特定内容空间 针对性损失函数设计模型如何进行风格转换和图像到图像翻译 提出背景 论文&#xff1a;ht…

超详细的 pytest 钩子函数 之初始钩子和引导钩子来啦

前几篇文章介绍了 pytest 点的基本使用&#xff0c;学完前面几篇的内容基本上就可以满足工作中编写用例和进行自动化测试的需求。从这篇文章开始会陆续给大家介绍 pytest 中的钩子函数&#xff0c;插件开发等等。 仔细去看过 pytest 文档的小伙伴&#xff0c;应该都有发现 pyt…

前端学习第一天-html基础

达标要求 网页的形成过程 常用的浏览器及常见的浏览器内核 web 标准三层组成 什么是HTML 熟练掌握HTML文档结构 熟练掌握HTML常用标签 1. 初识web前端 Web前端是创建Web页面或App等前端界面呈现给用户的过程。 Web前端开发是从网页制作演变而来&#xff0c;早期网站主…

【计算复杂性理论】证明复杂性(九):命题鸽巢原理的指数级归结下界——更简短的证明

往期文章&#xff1a; 【计算复杂性理论】证明复杂性&#xff08;Proof Complexity&#xff09;&#xff08;一&#xff09;&#xff1a;简介 【计算复杂性理论】证明复杂性&#xff08;二&#xff09;&#xff1a;归结&#xff08;Resolution&#xff09;与扩展归结&#xff…

什么是VR数字文化遗产保护|元宇宙文旅

VR数字文化遗产保护是指利用虚拟现实&#xff08;VR&#xff09;技术来保护和传承文化遗产。在数字化时代&#xff0c;许多珍贵的文化遗产面临着自然衰退、人为破坏或其他因素造成的威胁。通过应用VR技术&#xff0c;可以以全新的方式记录、保存和展示文化遗产&#xff0c;从而…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:栅格设置)

说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 栅格布局的列宽、列间距由距离最近的GridContainer父组件决定。使用栅格属性的组件树上至少需要有1个GridContainer容器组件。 gridSpan、gridOffset属性…

Python列表中添加删除元素不走弯路

1.append() 向列表中添加单个元素&#xff0c;一般用于尾部追加 list1 ["香妃", "乾隆", "贾南风", "赵飞燕", "汉武帝"]list1.append("周瑜") print(list1) # [香妃, 乾隆, 贾南风, 赵飞燕, 汉武帝, 周瑜]…

猫头虎分享已解决Bug || AttributeError: ‘str‘ object has no attribute ‘decode‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Vue3_2024_1天【Vue3创建和响应式,对比Vue2】

前言&#xff1a; Vue3对比Vue2版本&#xff0c;它在性能、功能、易用性和可维护性方面都有显著的提升和改进。 性能优化&#xff1a;模板编译器的优化、对Proxy的支持以及使用了更加高效的Virtual DOM算法等。这使得Vue3的打包大小减少了41%&#xff0c;初次渲染提速55%&#…