当前位置: 首页 > news >正文

微服务的编程测评系统10-竞赛删除发布-用户管理-登录注册

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. 竞赛删除
    • 1.1 后端
    • 1.2 前端
  • 2. 竞赛发布
    • 2.1 竞赛发布后端
    • 2.2 竞赛撤销发布-后端
    • 2.3 竞赛发布-撤销发布-前端
  • 3. C端用户管理
    • 3.1 C端用户表结构设计
    • 3.2 用户列表功能-后端
    • 3.3 mybatis-plus打印sql语句
    • 3.4 拉黑功能-后端
    • 3.5 用户列表-拉黑-前端
  • 4. C端用户登录注册
    • 4.1 业务分析
    • 4.2 集成阿里云短信服务
    • 4.3 验证码发送
    • 4.4 登录注册-后端开发
    • 4.4 前端项目初始化
    • 4.5 前端
  • 总结


前言

1. 竞赛删除

1.1 后端

    @DeleteMapping("/delete")public R<Void> delete(Long examId){log.info("删除竞赛examId:{}",examId);return toR(iExamService.delete(examId));} 
    @Overridepublic int delete(Long examId) {Exam exam = getExamById(examId);if(LocalDateTime.now().isAfter(exam.getStartTime())){throw new ServiceException(ResultCode.EXAM_HAVE_STARED);}examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId,examId));return examMapper.deleteById(exam);}

因为没有题目的时候examQuestionMapper.delete会返回0,所以返回examMapper.deleteById
这样就OK了

1.2 前端

export function deleteExamService(examId) {return service({url: "/exam/delete",method: "delete",params: { examId },});
}
async function onDelete(examId){await deleteExamService(examId)ElMessage.success("删除竞赛成功")params.pageNum = 1;getExamList();
}

这样就OK了

2. 竞赛发布

2.1 竞赛发布后端

竞赛发布,在添加竞赛,编辑竞赛,还有竞赛列表中都可以发布
发布竞赛,第一要存在这个竞赛,第二要有题目才可以发布竞赛
只有发布了的竞赛才可以显示在c端的竞赛列表中

    @PutMapping("/publish")public R<Void> publish(Long examId){log.info("点击了竞赛的发布按钮examId:{}",examId);return toR(iExamService.publish(examId));}
    public static final int TRUE = 1;public static final int FALSE = 0;
    @Overridepublic int publish(Long examId) {Exam exam = getExamById(examId);//查看这个竞赛中是否有题目Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));if(count==null|| count<=0){throw new ServiceException(ResultCode.EXAM_NOT_HAVE_QUESTION);}exam.setStatus(Constants.TRUE);return examMapper.updateById(exam);}

这样就可以了,然后就是注意一下,就死已经到了开始时间的竞赛,可以中途发布,让c端用户看到,或者去练习竞赛,不用参加竞赛

2.2 竞赛撤销发布-后端

前提:竞赛存在,竞赛还没开始,已经开始的竞赛,不能撤销发布
但是没有发布的竞赛,到了开始时间,可以发布

    @Overridepublic int publish(Long examId) {Exam exam = getExamById(examId);//查看这个竞赛中是否有题目Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));if(count==null|| count<=0){throw new ServiceException(ResultCode.EXAM_NOT_HAVE_QUESTION);}exam.setStatus(Constants.TRUE);return examMapper.updateById(exam);}@Overridepublic int cancelPublish(Long examId) {Exam exam = getExamById(examId);if(LocalDateTime.now().isAfter(exam.getStartTime())){throw new ServiceException(ResultCode.EXAM_HAVE_STARED);}exam.setStatus(Constants.FALSE);return examMapper.updateById(exam);}

2.3 竞赛发布-撤销发布-前端

export function publishExamService(examId) {return service({url: "/exam/publish",method: "put",params: { examId },});
}export function cancelPublishExamService(examId) {return service({url: "/exam/cancelPublish",method: "put",params: { examId },});
}

在添加和编辑中

//发布竞赛
async function publishExam(){await publishExamService(formExam.examId)ElMessage.success("竞赛发布成功")router.push("/oj/layout/exam")
}

在题目列表中

async function publishExam(examId){await publishExamService(examId)ElMessage.success("发布竞赛成功")getExamList();
}async function cancelPublishExam(examId){await cancelPublishExamService(examId)ElMessage.success("取消发布竞赛成功")getExamList();
}

这样就成功了

3. C端用户管理

在这里插入图片描述
定时任务我们最后来设计

3.1 C端用户表结构设计

B端:C端用户列表功能
拉黑用户操作

C端:登录注册,修改个人信息,退出登录

create table tb_user(
user_id  bigint unsigned NOT NULL COMMENT '用户id(主键)',
nick_name varchar(20) comment '用户昵称',
head_image varchar(100) comment '用户头像',
sex tinyint comment '用户状态1: 男  2:女',
phone char(11) not null comment '手机号',
code  char(6) comment '验证码',
email varchar(20) comment '邮箱',
wechat varchar(20) comment '微信号', 
school_name  varchar(20) comment '学校',
major_name  varchar(20) comment '专业',
introduce varchar(100) comment '个人介绍',
status tinyint not null comment '用户状态0: 拉黑  1:正常',
create_by    bigint unsigned not null  comment '创建人',
create_time  datetime not null comment '创建时间',
update_by    bigint unsigned  comment '更新人',
update_time  datetime comment '更新时间',
primary key(`user_id`)
)

注意就是登录和注册都是一个接口
在这里插入图片描述
验证码我们存入数据库中,但是这个是不太合适的,先这样处理
手机号作为唯一标识
登录的时候,有头像和昵称,点击了退出登录的话,头像和昵称就没了

head_image 是存储的图片地址
用户的修改人,创建人都是自己

3.2 用户列表功能-后端

用户id不能模糊查询,但是用户昵称可以模糊查询

@JsonSerialize(using = ToStringSerializer.class) 是 Jackson 框架中的一个注解,主要作用是将 Java 对象序列化为 JSON 时,强制将指定字段的值转换为字符串(String)类型。

@Getter
@Setter
@TableName("tb_user")
public class User extends BaseEntity {@JsonSerialize(using = ToStringSerializer.class)@TableId(value = "USER_ID", type = IdType.ASSIGN_ID)private Long userId;private String nickName;private String headImage;private Integer sex;private String phone;private String code;private String email;private String wechat;private String schoolName;private String majorName;private String introduce;private Integer status;
}
@Data
public class UserQueryDTO extends PageQueryDTO {private Long userId;private String nickName;
}
@Getter
@Setter
public class UserVO {@JsonSerialize(using = ToStringSerializer.class)private Long userId;private String nickName;private Integer sex;private String phone;private String email;private String wechat;private String schoolName;private String majorName;private String introduce;private Integer status;
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ck.system.mapper.user.UserMapper"><select id="selectUserList" resultType="com.ck.system.domain.user.vo.UserVO">SELECTuser_id,nick_name,sex,phone,email,wechat,school_name,major_name,introduce,statusFROMtb_user<where><if test="userId !=null ">user_id = #{userId}</if><if test="nickName !=null and nickName != ''">nick_name like concat('%', #{nickName}, '%')</if></where>ORDER BYcreate_time DESC</select>
</mapper>
    @Overridepublic List<UserVO> list(UserQueryDTO userQueryDTO) {PageHelper.startPage(userQueryDTO.getPageNum(),userQueryDTO.getPageSize());return userMapper.selectUserList(userQueryDTO);}

3.3 mybatis-plus打印sql语句

mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

加上这个配置, mybatis-plus就可以打印sql语句了

3.4 拉黑功能-后端

拉黑,第一需要被拉黑的用户id,然后是status,是拉黑还是解禁

被拉黑的用户,就不能提交代码,就不能报名参赛了

@Data
public class UserUpdateStatusDTO {private Long userId;private Integer status;
}
    @Overridepublic int updateStatus(UserUpdateStatusDTO userUpdateStatusDTO) {User user = userMapper.selectById(userUpdateStatusDTO.getUserId());if(user == null){throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);}user.setStatus(userUpdateStatusDTO.getStatus());return userMapper.updateById(user);}
    @PutMapping("/updateStatus")public R<Void> updateStatus(@RequestBody UserUpdateStatusDTO userUpdateStatusDTO){log.info("修改用户状态,拉黑或者解禁userUpdateStatusDTO:{}", userUpdateStatusDTO);return toR(userService.updateStatus(userUpdateStatusDTO));}

3.5 用户列表-拉黑-前端

                <el-tag type="error" v-else>拉黑</el-tag>

tag 标签表示状态

import service from '@/utils/request'export function getUserListService(params) {return service({url: "/user/list",method: "get",params,});
}export function updateStatusService(params = {}) {return service({url: "/user/updateStatus",method: "put",data: params,});
}

有data对应post或者put
直接params,或者,params+{参数}对应get

<template><!-- 表单 --><el-form inline="true"><el-form-item label="用户id"><el-input v-model="params.userId" placeholder="请您输入要搜索的用户id" /></el-form-item><el-form-item label="用户昵称"><el-input v-model="params.nickName" placeholder="请您输入要搜索的用户昵称" /></el-form-item><el-form-item><el-button @click="onSearch" plain>搜索</el-button><el-button @click="onReset" plain type="info">重置</el-button></el-form-item></el-form><!-- 表格 --><el-table height="526px" :data="userList"><el-table-column prop="userId" label="用户id" width="180px" /><el-table-column prop="nickName" label="用户昵称" /><el-table-column prop="sex" label="用户性别"><template #default="{ row }"><div v-if="row.sex === 1" style="color:#3EC8FF;"></div><div v-if="row.sex === 2" style="color:#FD4C40;"></div></template></el-table-column><el-table-column prop="phone" width="120px" label="手机号" /><el-table-column prop="email" width="120px" label="邮箱" /><el-table-column prop="wechat" width="120px" label="微信号" /><el-table-column label="学校/专业" width="150px"><template #default="{ row }"><span class="block-span"> 学校: {{ row.schoolName }}</span><span class="block-span"> 专业: {{ row.majorName }}</span></template></el-table-column><el-table-column prop="introduce" label="个人介绍" /><el-table-column prop="status" width="90px" label="用户状态"><template #default="{ row }"><el-tag type="success" v-if="row.status">正常</el-tag><el-tag type="error" v-else>拉黑</el-tag></template></el-table-column><el-table-column label="操作" width="80px" fixed="right"><template #default="{ row }"><el-button class="red" v-if="row.status === 1" type="text" plain@click="onUpdateUserStatus(row.userId, 0)">拉黑</el-button><el-button v-if="row.status === 0" type="text" plain@click="onUpdateUserStatus(row.userId, 1)">解禁</el-button></template></el-table-column></el-table><!-- 分页区域 --><el-pagination background size="small" layout="total, sizes, prev, pager, next, jumper" :total="total"v-model:current-page="params.pageNum" v-model:page-size="params.pageSize" :page-sizes="[5, 10, 15, 20]"@size-change="handleSizeChange" @current-change="handleCurrentChange" />
</template><script setup>
import { reactive, ref } from 'vue';
import { getUserListService, updateStatusService } from '@/apis/cuser'const params = reactive({pageNum: 1,pageSize: 10,userId: '',nickName: '',
})const userList = ref([])
const total = ref(0)async function getUserList() {const ref = await getUserListService(params)userList.value = ref.rowstotal.value = ref.total
}
getUserList()function onSearch() {params.pageNum = 1getUserList()
}function onReset() {params.pageNum = 1params.pageSize = 10params.userId = ''params.nickName = ''getUserList()
}function handleSizeChange(newSize) {params.pageNum = 1getUserList()
}function handleCurrentChange(newPage) {getUserList()
}const updateStatusParams = reactive({userId: '',status: '',
})async function onUpdateUserStatus(userId, status) {console.log("userId:",userId)updateStatusParams.userId = userIdupdateStatusParams.status = statusconsole.log("updateStatusParams:",updateStatusParams)await updateStatusService(updateStatusParams)getUserList()
}
</script>

这样就OK了,很简单

4. C端用户登录注册

在这里插入图片描述

4.1 业务分析

采用的是手机号验证码的登录方式
在这里插入图片描述
然后就是登录注册两个逻辑合在一起

老用户的手机号,就是直接登录
新用户的手机号的话,就是直接注册了 ,然后自动登录了

现在我们要在friend下面写代码了

@Data
public class UserSendCodeDTO {private String phone;
}
    @PostMapping("/sendCode")public R<Void> sendCode(@RequestBody UserSendCodeDTO userSendCodeDTO) {log.info("发送验证码,UserSendCodeDTO:{}",userSendCodeDTO);return toR(userService.sendCode(userSendCodeDTO));}

因为手机号比较隐私,所以不用get请求来暴露,post比较隐秘

    @Overridepublic int sendCode(UserSendCodeDTO userSendCodeDTO) {//先校验手机号格式对不对if(!checkPhone(userSendCodeDTO.getPhone())){throw  new ServiceException(ResultCode.PHONE_STYLE_ERR);}//生成六位随机数String code = RandomUtil.randomNumbers(6);return 0;}public static boolean checkPhone(String phone) {Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");Matcher m = regex.matcher(phone);return m.matches();}

RandomUtil.randomNumbers也是hutool生成随机数的方法
checkPhone是用来检查手机号格式的
然后就是该如何发送手机号验证码的问题了

4.2 集成阿里云短信服务

阿里云官网

但是现在短信服务个人都无法使用了,所以我们这里就不做文档操作了,可以根据上面的官网来操作
我们就不采用发送手机验证码给用户的操作了
现在开始写代码了
先给friend增加nacos配置文件

server:port: 9202
spring:data:redis:host: localhostpassword: 123456datasource:url: jdbc:mysql://localhost:3306/ckoj_dev?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8username: ojtestpassword: 123456hikari:minimum-idle: 5 # 最⼩空闲连接数maximum-pool-size: 20 # 最⼤连接数idle-timeout: 30000 # 空闲连接存活时间(毫秒)connection-timeout: 30000 # 连接超时时间(毫秒)
jwt:secret: zxcvbnmasdfghjkl

然后就是网关的nacos配置文件

        - id: oj-frienduri: lb://oj-friendpredicates:- Path=/friend/**filters:- StripPrefix=1

反正friend的配置和system差不多

4.3 验证码发送

第一获取验证码比较频繁,而且每次获取验证码都不一样,第三就是验证码有有效时间的
所以我们可以在redis中存储验证码
前缀可以是phone:code:手机号

每天限发50次,记录发送次数,第二天清零,还是用redis
key是phone:code:times:手机号
发送次数超过50次就不能发送了

然后还有就是发送之后,60s内不能再次发送


@Service
public class UserServiceImpl implements IUserService {@Value("${sms.phone-code-time-minute:5}")private Long phoneCodeTime;//验证码过期时间@Value("${sms.phone-code-limit-times:3}")private Long codeLimitTimes;//每天发送限制次数@Autowiredprivate RedisService redisService;@Overridepublic boolean sendCode(UserSendCodeDTO dto) {//先校验手机号格式对不对if(!checkPhone(dto.getPhone())){throw  new ServiceException(ResultCode.PHONE_STYLE_ERR);}//生成六位随机数String code = RandomUtil.randomNumbers(6);log.info("手机号发送验证码为,code:{}",code);String phoneCodeKey = getPhoneCodeKey(dto.getPhone());//获取上一次发送的剩余缓存时间,发送时间相差60s的话,就不能发送来了Long codeExpiredTime = redisService.getExpired(phoneCodeKey, TimeUnit.SECONDS);if(codeExpiredTime!=null && phoneCodeTime * 60 - codeExpiredTime <60){//expiredTime!=null说明不是第一次发送, <60说明发送太快throw new ServiceException(ResultCode.PHONE_CODE_NOT_SEND_QUICKLY);}else{//要么第一次发送,要么是过了很长时间了redisService.setCacheObject(phoneCodeKey,code,phoneCodeTime, TimeUnit.MINUTES);}String phoneCodeTimesKey = getPhoneCodeTimesKey(dto.getPhone());//获取redis中存储的次数Long sendTimes = redisService.getCacheObject(phoneCodeTimesKey, Long.class);if(sendTimes!=null && sendTimes >= codeLimitTimes){throw new ServiceException(ResultCode.PHONE_CODE_SEND_TIMES_LIMITED);}//要么第一次发送,要么没有超过次数限制if(sendTimes == null){//第一次发送的话,那么就设置过期时间为到零点//先获取过期时间Long timesExpiredTime = ChronoUnit.SECONDS.between(LocalDateTime.now(),LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));redisService.setCacheObject(phoneCodeTimesKey,0,timesExpiredTime,TimeUnit.SECONDS);}//走到这里就是没有超过限制,如果超过了,肯定不为null,肯定走不到这里redisService.increment(phoneCodeTimesKey);return true;}private String getPhoneCodeTimesKey(String phone) {return CacheConstants.PHONE_CODE_TIMES_KEY + phone;}private String getPhoneCodeKey(String phone) {return CacheConstants.PHONE_CODE_KEY + phone;}public static boolean checkPhone(String phone) {Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");Matcher m = regex.matcher(phone);return m.matches();}
}

这样就成功了

    public Long increment(final String key){return redisTemplate.opsForValue().increment(key);}

这个increment就是对value进行加1,然后返回加1后的value的值

    public static final String PHONE_CODE_KEY = "phone:code:key:";public static final String PHONE_CODE_TIMES_KEY = "phone:code:times:key:";
sms:phone-code-time-minute: 5phone-code-limit-times: 3

还有就是网关要对这个发送验证码的接口进行过滤,不要登录拦截了

security:ignore:whites: - /**/login- /friend/user/sendCode

我们把手机号打印出来,就当做是发送了手机号验证码了

这样我们就成功了
@Value(“${sms.phone-code-time-minute:5}”)
这里写个5的原因就是如果nacos没有配置的话,就会使用默认值5

4.4 登录注册-后端开发

判断验证码是否正确

删除redis验证码

注册

返回token

    @PostMapping("/loginOrRegister")public R<String> loginOrRegiter(@RequestBody LoginOrRegisterDTO dto){log.info("用户登录或注册LoginOrRegisterDTO:{}",dto);return R.ok(userService.loginOrRegister(dto));}
@Data
public class LoginOrRegisterDTO {private String phone;private String code;
}
@AllArgsConstructor
@Getter
public enum UserStatus {Block(0),Normal(1);private Integer status;
}
    @Overridepublic String loginOrRegister(LoginOrRegisterDTO dto) {//先检查验证码对不对String phone =dto.getPhone();String code =dto.getCode();checkPhoneCode(phone,code);User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));if(user==null){//说明是注册user = new User();user.setPhone(phone);user.setStatus(UserStatus.Normal.getStatus());userMapper.insert(user);}//然后是创建tokenreturn tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(),user.getNickName() );}private void checkPhoneCode(String phone, String code) {if(!checkPhone(phone)){throw  new ServiceException(ResultCode.PHONE_STYLE_ERR);}String cacheCode = redisService.getCacheObject(getPhoneCodeKey(phone), String.class);if(cacheCode == null){throw new ServiceException(ResultCode.PHONE_CODE_NO_SEND_OR_EXPIRED);}if(!code.equals(cacheCode)){throw new ServiceException(ResultCode.PHONE_CODE_ERR);}redisService.deleteObject(getPhoneCodeKey(phone));}

这样就可以了,因为为null的user不能set,然后就是插入数据之后,会自动返回id的

其中创建token的函数中是可以缓存数据的,设置过期时间
注意nacos上修改了配置,服务可以不用重新启动
然后我们在设置一个开关,什么意思呢,意思就是开关打开正常发送,开关关闭,验证码就一直为123456

    @Value("${sms.is-send:true}")private Boolean isSend;
    public static final String DEFAULT_PHONE_CODE = "123456"; 
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_PHONE_CODE;
sms:phone-code-time-minute: 5phone-code-limit-times: 3is-send: false

这样就OK了

4.4 前端项目初始化

创建项目oj-fe-c
项目和oj-fe-b是差不多的
比如拷贝utils

# 使用 npm 安装 安装element-plus
npm install element-plus --save
# 安装elementplus的按需导入
npm install -D unplugin-vue-components unplugin-auto-import# 使用 npm 安装
npm install -D sass-embeddednpm install axios
npm install js-cookie@3.0.5

还有代理服务器配置
在vite.vonfig.js里面

  server: {proxy: {"/dev-api": {target: "http://127.0.0.1:19090/friend",rewrite: (p) => p.replace(/^\/dev-api/, ""),},},}
  plugins: [vue(),vueDevTools(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],

这个是elementplus的按需导入的配置,在vite.vonfig.js里面

然后main.js也和oj-fe-b一样的

4.5 前端

首页点击登录按钮,就可以跳转过来到登录页

    {path: '/c-oj/home',name: 'home',component: () => import('@/views/Home.vue')},{path: '/c-oj/login',name: 'login',component: () => import('@/views/Login.vue')},{path: '/',redirect: '/c-oj/home'},

先配置两个路由,还有默认路由

<template><div class="login-page"><div class="orange"> </div><div class="blue"></div><div class="blue small"></div><div class="login-box"><div class="logo-box"><img src="@/assets/logo.png"><div><div class="sys-name">CK-OJ</div><div class="sys-sub-name">帮助ZL学习</div></div></div><div class="form-box-title"><span>验证码登录</span></div><div class="form-box"><div class="form-item"><img src="@/assets/images/shouji.png"><el-input v-model="mobileForm.phone" type="text" placeholder="请输入手机号" /></div><div class="form-item"><img src="@/assets/images/yanzhengma.png"><el-input style="width:134px" v-model="mobileForm.code" type="text" placeholder="请输入验证码" /><div class="code-btn-box" @click="getCode"><span>{{ txt }}</span></div></div><div class="submit-box" @click="loginFun">登录/注册</div></div><div class="gray-bot"><p>注册或点击登录代表您同意 <span>服务条款</span><span>隐私协议</span></p></div></div></div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { setToken } from '@/utils/cookie'
import { sendCodeService, codeLoginService } from '@/apis/user'
import router from '@/router'// 验证码登录表单
let mobileForm = reactive({phone: '',code: ''
})
let txt = ref('获取验证码')
let timer = null
async function getCode() {await sendCodeService(mobileForm)txt.value = '59s'let num = 59timer = setInterval(() => {num--if (num < 1) {txt.value = '重新获取验证码'clearInterval(timer)} else {txt.value = num + 's'}}, 1000)
}async function loginFun() {const loginRef = await codeLoginService(mobileForm)setToken(loginRef.data)router.push('/c-oj/home/question')}
</script>
<style lang="scss" scoped>
.login-page {width: 100vw;height: 100vh;position: relative;margin-top: -60px;margin-left: -20px;overflow: hidden;.login-box {width: 600px;height: 604px;background: #FFFFFF;box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.1);border-radius: 10px;opacity: 0.9;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 2;padding: 0 72px;padding-top: 50px;overflow: hidden;.logo-box {display: flex;align-items: center;&.refister-logo {margin-bottom: 56px;}img {width: 68px;height: 68px;margin-right: 16px;}.sys-name {height: 33px;font-family: PingFangSC, PingFang SC;font-weight: 600;font-size: 24px;color: #222222;line-height: 33px;margin-bottom: 13px;}.sys-sub-name {height: 22px;font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 16px;color: #222222;line-height: 22px;}}.form-box-title {height: 116px;display: flex;align-items: center;span {font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 24px;color: #000000;line-height: 33px;display: block;height: 33px;margin-right: 40px;position: relative;letter-spacing: 1px;cursor: pointer;&.active {font-weight: bold;&::before {position: absolute;content: '';bottom: -13px;left: 0;width: 100%;height: 5px;background: #32C5FF;border-radius: 10px;}}}}.gray-bot {position: absolute;left: 0;text-align: center;margin-top: 56px;width: 100%;height: 50px;background: #FAFAFA;font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 14px;color: #666666;line-height: 50px;p {margin: 0;}span {color: #32C5FF;cursor: pointer;}}:deep(.form-box) {.submit-box {margin-top: 90px;width: 456px;height: 48px;background: #96E1FE;border-radius: 8px;cursor: pointer;display: flex;justify-content: center;align-items: center;font-family: PingFangSC, PingFang SC;font-weight: 600;font-size: 16px;color: #FFFFFF;letter-spacing: 1px;&.refister-submit {margin-top: 72px;}&:hover {background: #32C5FF;}}.form-item {display: flex;align-items: center;width: 456px;height: 48px;background: #F8F8F8;border-radius: 8px;margin-bottom: 30px;position: relative;.code-btn-box {position: absolute;right: 0;width: 151px;height: 48px;background: #32C5FF;border-radius: 8px;top: 0;display: flex;align-items: center;justify-content: center;cursor: pointer;span {font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 16px;color: #FFFFFF;}}.error-tip {position: absolute;width: 140px;text-align: right;padding-right: 12px;height: 20px;font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 14px;color: #FD4C40;line-height: 20px;right: 0;&.bottom {right: 157px;}}.el-input {width: 380px;font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 16px;color: #222222;}.el-input__wrapper {border: none;box-shadow: none;background: transparent;width: 230px;padding-left: 0;}img {width: 24px;height: 24px;margin: 0 18px;}}}}&::after {position: absolute;top: 0;left: 0;height: 100vh;bottom: 0;right: 0;background: rgba(255, 255, 255, .8);z-index: 1;content: '';}.orange {background: #F0714A;width: 498px;height: 498px;border-radius: 50%;background: #F0714A;opacity: 0.67;filter: blur(50px);left: 14.2%;top: 41%;position: absolute;}.blue {width: 334px;height: 334px;background: #32C5FF;opacity: 0.67;filter: blur(50px);left: 14.2%;top: 42%;position: absolute;top: 16.3%;left: 80.7%;&.small {width: 186px;height: 186px;top: 8.2%;left: 58.2%;}}
}
</style>

然后是Login.vue
然后是图片资源,就是asset目录下的,我们直接全部拷贝就可以了

在这里插入图片描述
直接拷贝整个项目需要的assets
main.scss是公共样式,在main.js中引入

import service from "@/utils/request";export function sendCodeService(params = {}) {return service({url: "/user/sendCode",method: "post",data: params,});
}export function codeLoginService(params = {}) {return service({url: "/user/loginOrRegister",method: "post",data: params,});
}

然后是在apis下面创建user.js

然后就成功了

在这里插入图片描述
但是我们这里启动失败了,因为elementplus的按需导入,有些东西没有import,可以去看官网

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

这样就成功了

在这里插入图片描述

          <div class="code-btn-box" @click="getCode"><span>{{ txt }}</span></div>

这个就是获取验证码的按钮

let txt = ref('获取验证码')

是一个响应式数据

async function getCode() {await sendCodeService(mobileForm)txt.value = '59s'let num = 59timer = setInterval(() => {num--if (num < 1) {txt.value = '重新获取验证码'clearInterval(timer)} else {txt.value = num + 's'}}, 1000)
}

txt.value = '59s’这里改了,前端也会改了
setInterval是一个定时函数,每隔一秒就会执行一次
这里进行了限制,第一sendCodeService在60s之内不会发两次,所以再次点击不会重置倒计时的

const TokenKey = "Oj-c-Token";

然后是在cookie.js里面要改一下cookie的名字,不然会把cookie覆盖的
不然就会一边登录,另一边就不能登录了

总结

http://www.lryc.cn/news/610054.html

相关文章:

  • 雷达系统工程学习:自制极化合成孔径雷达无人机
  • Flask全栈入门:打造区块链艺术品交易所
  • Oracle 定时任务相关
  • Tomcat虚拟主机配置详解和多实例部署
  • k8s的毫核
  • 太阳光模拟器塑料瓶暴晒试验
  • Vue2实现docx,xlsx,pptx预览
  • P1002 [NOIP 2002 普及组] 过河卒
  • ubuntu22.04系统实践 linux基础入门命令(三) 用户管理命令
  • SpringMVC实战指南:从环境搭建到功能实现全解析
  • 先知模型或者说从容的模型
  • RTOS如何保证实时性
  • React 入门:环境搭建、JSX、组件、事件与状态管理
  • 云原生攻防6(Kubernetes扩展知识)
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第五天(jQuery函数库)
  • 官宣朱珠成为集团品牌代言人,转转推动二手消费新风尚
  • Linux-Day02.Linux指令
  • 如何设计和实施高效的向量化数据检索解决方案
  • Apache IoTDB(3):时序数据库 IoTDB Docker部署实战
  • 大模型部署、nvidia-smi、token数
  • Linux服务器管理MySQL数据库的常见命
  • 09 Linux基础(8.4)
  • git 项目拉取 SSH密钥配置
  • ESDocValues机制
  • CCES软件的Workspace设置问题
  • 牛客网之华为机试题:HJ24 合唱队(动态规划)
  • HFSS许可监控与分析
  • 向量空间模型
  • day23-线程篇(一)
  • 什么是内容管理系统?