微服务的编程测评系统-身份认证-管理员登录前端

发布于:2025-07-22 ⋅ 阅读:(18) ⋅ 点赞:(0)

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


前言

身份认证

测试

我们现在测试一下第二步,就是测试一下网关的鉴权功能

我们先给gateway也弄一个logback文件
然后运行一下试一下吧

The bean 'redisTemplate', defined in class path resource [com/ck/common/redis/config/RedisConfig.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.class] and overriding is disabled.

第一个报错
在这里插入图片描述
这个报错的主要原因就是redis服务中的redisconfig中我们定义的
这个错误提示表明,RedisAutoConfiguration 类中已经定义了名为 redisTemplate 的 Bean,而当前配置类 RedisConfig 又尝试定义同名的 Bean,且默认情况下 Bean 覆盖(重写)是禁用的,所以出现冲突无法注册
所以我们在上面加上一个注解就可以解决了

@Configuration
@AutoConfigureBefore(RedisAutoConfiguration.class)

加上注解AutoConfigureBefore的意思就是,优先把我们定义的bean给注册了

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'jwt.secret' in value "${jwt.secret}"

第二个报错,没有在nacos设置秘钥

jwt:
  secret: zxcvbnmasdfghjkl
Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway.

第三个报错,这个报错的原因是springmvc和springcloudgateway是不兼容的,上面我们也说过了
项目中同时存在 Spring MVC 和 Spring Cloud Gateway 的依赖,而它们在设计上是互斥的。这是因为:
Spring MVC 基于 Servlet API,是传统的阻塞式 Web 框架;
Spring Cloud Gateway 基于 Spring WebFlux,是响应式(非阻塞)Web 框架。
两者不能同时存在于同一个应用中,否则会导致类路径冲突和运行时异常。
所以我们不能引入security的依赖了,意思就是springweb和gateway不能同时引入了
但我们的security中使用了springweb,就是使用了全局异常处理,使用了controller,所以会出错,但是我们gateway并没有使用全局异常,只是使用了比如jwtutil,loginuser这些东西,这些东西都没有使用springmvc,所以我们可以把它们提取出来,放在core里面,就不要引入security的依赖了

先把jwtutil放在core中,记得引入依赖
然后在gateway中引入core和redis依赖
记得还要把loginuser也引入core中
记得geteway还要引入redis依赖

spring:
  data:
    redis:
      host: localhost
      password: 123456

现在就可以测试了

在这里插入图片描述
登录是没有问题了

这个是没有设置header的时候
在这里插入图片描述
在这里插入图片描述
现在开始设置header
在这里插入图片描述
这样就成功了
其中Authorization字段是header里面自带的,好像是一个数组

然后我们在测试一下,就是设置过期时间为三分钟,看到了过期时间还可以访问不
在这里插入图片描述
在这里插入图片描述

redis-cli
auth 123456
keys *
ttl **

ttl就是查看过期时间

现在设置的是三分钟的过期时间
在这里插入图片描述
登录之后,现在可以正常使用
在这里插入图片描述
查看一下,还有115s过期

在这里插入图片描述
在这里插入图片描述
过期之后再去访问也会得到我们想要的结果了
现在还有两个问题
第一就是我们可能用着用着,比如在竞赛的时候,突然间登录就过期了,那么这样的话,还要重新登录就很麻烦,所以我们要延长过期时间
第二个就是每次重新登录的话,原来的redis数据没有删除掉,还留着

延长过期时间

先在jwtUtil弄一个获取过期时间的方法

    public Long getExpired(final String key,final TimeUnit timeUnit){
        return redisTemplate.getExpire(key,timeUnit);
    }

我们现在tokenservice里面弄出延长过期时间的方法

    public void extendToken(String  token, String secret){
        Claims claims;
        try {
            claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息
            if (claims == null) {
                log.error("令牌已过期或验证不正确!");
                return;
            }

        } catch (Exception e) {
            log.error("令牌已过期或验证不正确!e:",e);
            return;
        }
        String userKey = JwtUtils.getUserKey(claims); //获取jwt中的key
        Long expired = redisService.getExpired(CacheConstants.LOGIN_TOKEN_KEY + userKey, TimeUnit.MINUTES);
        if (expired < CacheConstants.REFRESH_TOKEN_KEY) {
            redisService.expire(CacheConstants.LOGIN_TOKEN_KEY + userKey, CacheConstants.EXPIRED, TimeUnit.MINUTES);
        }
    }

延长过期时间的方法我们就弄出来了,这些的问题就是该在哪里使用这个方法呢
延长过期方法应该设置在过滤器之后,肯定要满足过滤的条件,比如进行了身份验证才能进行延长
然后肯定要在controller之前执行
在这里插入图片描述
这里我们就要引出拦截器了,所谓拦截器就是位于过滤器和controller之间的一个东西
过滤器在网关,网关把请求过滤,转发给特定的服务servlet,所以拦截器system服务的东西
我们在security模块里面写拦截器的代码,到时候也会被system引入的

@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Autowired
    private TokenService tokenService;
    @Value("${jwt.secret}")
    private String secret;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //先获取token
        String token = request.getHeader(HttpConstants.AUTHENTICATION);
        tokenService.extendToken(token,secret);
        return true;
    }
}

这个就是拦截器,,除了弄拦截器以外,还要给拦截器进行配置,即哪些请求要经过拦截器
配置WebMvcConfig,就重写addInterceptors就可以了

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private TokenInterceptor tokenInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/**/login");
    }

}

这个就是配置,然后就可以测试了

我们测试,就把过期时间设置为5分钟,要延长时间的临界时间设置为3分钟
然后测试一下

在这里插入图片描述

我们先用登录接口
然后是这样的
在这里插入图片描述

注apifox环境变量那里尽量使用本地值,因为远程值在短时间内可能加载不出来
在这里插入图片描述
然后我们等待到了180s的时候,再次调用一下这个test接口,看能不能把过期时间延长

在这里插入图片描述
再次调用test接口,时间就变长了,这个就说明成功了

新增管理员

    @Override
    public R<Void> add(SysUserSaveDTO saveDTO) {
        SysUser sysUser = new SysUser();
        sysUser.setUserAccount(saveDTO.getUserAccount());
        sysUser.setPassword(BCryptUtils.encryptPassword(saveDTO.getPassword()));
        sysUser.setCreateBy(100L);
        sysUser.setCreateTime(LocalDateTime.now());
        int insert = sysUserMapper.insert(sysUser);
        if(insert <= 0){
            return R.fail();
        }
        return R.ok();
    }

对mapper返回值进行优化

我们在core里面新建BaseController

public class BaseController {
    public R<Void> toR(int rows){
        if(rows<=0){
            return R.fail();
        }
        return R.ok();
    }
    
    public R<Void> toR(Boolean b){
        if(!b){
            return R.fail();
        }
        return R.ok();
    }
}

然后SysUserController继承BaseController

    @Override
    public int add(SysUserSaveDTO saveDTO) {
        SysUser sysUser = new SysUser();
        sysUser.setUserAccount(saveDTO.getUserAccount());
        sysUser.setPassword(BCryptUtils.encryptPassword(saveDTO.getPassword()));
        sysUser.setCreateBy(100L);
        sysUser.setCreateTime(LocalDateTime.now());
        return sysUserMapper.insert(sysUser);
    }

这样就OK了

优化2:自动填充字段

        sysUser.setCreateBy(100L);
        sysUser.setCreateTime(LocalDateTime.now());

对于这两行每个类插入都要写的数据,我们可以用mybatisPlus的自动填充功能

官网

然后要在core里面引入mybatis依赖
因为要使用mybatisPlus的注解了

@Data
public class BaseEntity {
    @TableField(fill = FieldFill.INSERT)
    private Long createBy;
    @TableField(fill = FieldFill.UPDATE)
    private Long updateBy;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
}

然后我们创建一个新的模块,oj-common-mybatis,

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        this.strictInsertFill(metaObject, "createBy", Long.class, 100L);
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {

    }
}

这样的话,在调用mybatisPlus的insert方法的时候,这两个字段就可以自动填充了,然后把这个引入到system那里去

    @Override
    public int add(SysUserSaveDTO saveDTO) {
        SysUser sysUser = new SysUser();
        sysUser.setUserAccount(saveDTO.getUserAccount());
        sysUser.setPassword(BCryptUtils.encryptPassword(saveDTO.getPassword()));
        return sysUserMapper.insert(sysUser);
    }

就变成这样了

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

但是gateway报错了,这个是因为,core引入了mybatis
,然后gateway引入了mybatis,而且gateway是一个启动项目,但是1并没有配置数据源,mybatis在启动的时候会自动加载数据源的
所以才会报错

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

在gateway的启动类上加上这个,意思就是表示启动的时候,忽略mybatis的数据源

用户名重复处理

用户名重复我们该怎么处理呢,如果重复的话,肯定不能返回一个整数了
而是直接抛出异常,这个就是相当于我们判断出的异常是一样的

我们在security那里创建一个类ServiceException,用于处理service异常

@Data
public class ServiceException extends RuntimeException{
    private ResultCode resultCode;
    public ServiceException(ResultCode resultCode) {
        this.resultCode = resultCode;
    }
}
    /**
     * 拦截服务异常
     */
    @ExceptionHandler(ServiceException.class)
    public R<?> handleRuntimeException(ServiceException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        ResultCode resultCode = e.getResultCode();
        log.error("请求地址'{}',发⽣异常{} .", requestURI,resultCode.getMsg(), e);
        return R.fail(resultCode);
    }

然后再完善一下全局异常处理,就可以了

    @Override
    public int add(SysUserSaveDTO saveDTO) {
        List<SysUser> sysUsers = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserAccount, saveDTO.getUserAccount()));
        if(CollectionUtil.isNotEmpty(sysUsers)){
            throw new ServiceException(ResultCode.AILED_USER_EXISTS);
        }
        SysUser sysUser = new SysUser();
        sysUser.setUserAccount(saveDTO.getUserAccount());
        sysUser.setPassword(BCryptUtils.encryptPassword(saveDTO.getPassword()));
        return sysUserMapper.insert(sysUser);
    }

这样就OK了

在这里插入图片描述

记得给mybatis添加org.springframework.boot.autoconfigure.AutoConfiguration.imports
在这里插入图片描述
这样就成功了

DTO,VO,Entity

Entity:与数据库表中的字段⼀⼀对应的实体类。
它与数据库表⼀⼀对应,⽤于持久化数据。
• DTO:接收前端的传递的数据。
DTO(Data Transfer Object,数据传输对象),通常是轻量级的,只包含需要传输的数据。
• VO:返回给前端的数据。
VO(View Object,视图对象),⽤于在展⽰层显⽰数据,通常是将表⽰数据的实体对象中的⼀部分属性进⾏选择性的组合形成的⼀个新对象,⽬的是为了满⾜展⽰层数据要求的特定数据结
构。

为什么要这么区分:

• 提⾼代码的可读性和可维护性:每种对象都有其特定的⽤途和职责,使得代码结构更加清晰,开发⼈员能够更容易地理解数据的流动和使⽤⽅式。每个对象采取统⼀的命名,项⽬会变得更加⼀致和可预测,从⽽减少了出错的可能性。
• 解耦:Entity、DTO、VO的划分降低了各个部分的耦合度。修改某⼀层的逻辑或数据时减少对于其它层的影响。
• 优化性能:不同的对象,⽐如DTO和VO可以根据⾃⾝的功能和当前的需求,进⾏裁剪和拼装。合理的利⽤⽹络传输资源,提⾼性能。

除了这些Entity、DTO、VO还有:DO、BO、AO等等。这些对象的创建都是为了更好地组织和管理代码,提⾼系统的可维护性和可扩展性。然⽽,过度细分这些对象可能导致代码复杂性增加、维护成本上升、性能下降以及团队协作的挑战。因此,在使⽤这些对象时,我们需要综合考虑项⽬的实际需求和团队的能⼒,避免过度设计,确保在保持代码清晰和可维护的同时,不引⼊不必要的复杂性和开销。

划分过细的缺点:
• 增加复杂性:过多的概念划分可能使开发者需要花费更多的时间去理解每个对象的作⽤和⽤途,增加了学习和理解的难度。这可能导致开发效率降低,甚⾄引⼊不必要的错误。
• 过度设计/影响性能:过度设计意味着在实现简单功能时引⼊了过多的抽象和复杂性,这不仅增加了开发成本,过多的对象转换和数据处理可能会引⼊额外的性能开销。
• 维护成本上升:随着对象划分的增多,系统中的对象数量也会增加,这可能导致代码库的膨胀。过多的对象意味着更多的类、接⼝和⽅法需要维护,从⽽增加了维护成本。
• 耦合度增加:尽管对象划分的初衷是为了降低耦合度,但过于细致的划分有时反⽽可能导致耦合度上升。因为开发者可能会创建过多的中间层或转换层来处理不同对象之间的交互,这增加了系统各部分之间的依赖关系。
在这里插入图片描述

后端参数校验

spring-boot-starter-validation 是 Spring Boot 提供的⼀个启动器模块,它主要⽤于简化在 Spring Boot 应⽤程序中的验证功能。开发者可以在模型类字段上使⽤注解来定义验证规则。
就是使用注解对参数进行校验

常⽤注解说明

@NotNull:值不能为null;string不能为null,就是不能不传,但是可以为"“,string不传参数的时候就是null,传了为”"也不是null,但是integer不传或者传了为空也是null
• @NotEmpty:字符串、集合或数组的值不能为空,即⻓度⼤于0;
• @NotBlank:字符串的值不能为空⽩,即不能只包含空格;
• @Size:字符串、集合或数组的⼤⼩是否在指定范围内;
• @Min:数值的最⼩值;
• @Max:数值的最⼤值;
• @Pattern:字符串是否匹配指定的正则表达式;
• @Email:字符串是否为有效的电⼦邮件地址;
• @Future:⽇期是否为将来的⽇期;
• @Past:⽇期是否为过去的⽇期

@NotNull用于类
NotEmpty用于数组
NotBlank用于string

使用之前要先引入依赖在common下

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
    @GetMapping("/validationTest")
    public String validationTest(@Validated ValidationTestDTO validationTestDTO )  {
        return "ok";
    }  

要对谁,哪个类进行参数校验,要加上注解Validated

public class ValidationTestDTO {
    @NotBlank(message = "⽤⼾账号不能为空")
    private String userAccount;
    @NotBlank(message = "⽤⼾密码不能为空")
    @Size(min = 5, max = 10, message = "密码⻓度不能少于6位,不能⼤于10位")
    private String password;
    @Min(value = 0, message = "年龄不能⼩于0岁")
    @Max(value = 60, message = "年龄不能⼤于60岁")
    private int age;
    @Email(message = "必须符合邮箱格式")
    private String email;
    @Pattern(regexp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$", message = "⼿机号码格式不正确")
    private String phone;
    @Past(message = "开始⽇期必须是过去的⽇期")
    private LocalDate startDate;
    @Future(message = "结束⽇期必须是未来的⽇期")
    private LocalDate endDate;
}

然后为了让能够捕获到异常,然后打印出详细的信息,我们还要对全局异常进行处理

    @ExceptionHandler(BindException.class)
    public R<Void> handleBindException(BindException e) {
         log.error(e.getMessage());
         String message = join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ",");
         return R.fail(ResultCode.FAILED_PARAMS_VALIDATE.getCode(), message);
     }

     private <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
         if (CollUtil.isEmpty(collection)) {
             return StrUtil.EMPTY;
             }
         return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
    }

这个可以同时打印出多个参数校验的异常
什么意思呢
意思就是参数校验时候的异常,可以多个异常一起抛出
所以我们可以把异常信息一起返回回去

在这里插入图片描述
这样就成功了

git问题

在这里插入图片描述
我们的gitee含有一个无关紧要的东西,比如.idea这种,这种东西我们不用上传的,还有没有上传的log,我们也不用上传的
我们只需要把这两个添加入.gitignore里面就可以了
在这里插入图片描述
这样就可以了
在这里插入图片描述
这样每次上传的时候,就不会上传这两个文件了
但是已经上传的.idea怎么在gite去掉呢

git rm -r --cached ./.idea

输入这个命令就可以去掉gitee上面的.idea了,而不会删除本地的.idea
但是这样还不够,因为还要add,commit,push之后才会生效
在这里插入图片描述
这样gitee就没有.idea了
都是一些有用的东西

管理员登录前端

npm create vue@latest

项目名字为 oj-fe-b

然后选择vue-router

删除示例代码

我们从index.html开始删,删除和我们无关的代码

index.html没有要删除的东西
然后是main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)

app.mount('#app')

然后是app.vue

<script setup>
import { RouterView } from 'vue-router'

</script>

<template>
  <RouterView />
</template>

<style scoped>
</style>

在这里插入图片描述

这样就删的干干净净了

基础知识

src⽬录结构

• assets:⽤于存放项⽬中需要导⼊的静态资源,如图⽚、字体⽂件等。
• views ⽬录: views ⽬录通常⽤于存放那些与路由直接相关的⻚⾯级别的组件。
• components ⽬录: components ⽬录通常⽤于存放那些可复⽤的、⼩巧的、功能单⼀的组件。这些组件通常不会被直接渲染到路由对应的⻚⾯上,⽽是作为其他组件(可能是 views ⽬录下的组件)的⼀部分被引⽤和渲染。
• router:⽤于存放Vue Router的配置⽂件。

src下⽂件命名规则

• 项⽬⼯程名:全部采⽤⼩写⽅式,以短横线分隔,例如:vue-pro。
• ⽬录和⽂件夹:全部采⽤⼩写⽅式,以短横线分隔,有复数结构时使⽤复数形式,例如:views、styles、error-log。
• js⽂件:全部采⽤⼩写⽅式,优先选择单个单词命名,多个单词命名以短横线分隔
• components、views下⽂件夹/⽂件命名:⽂件夹命名使⽤⼤写字⺟开头PascalBase⻛格(⼤写字⺟开头的驼峰命名法)。

创建Login.vue

<template>
    我是登录页
</template>

然后是配置路径

    {
      path: '/oj/login',
      name: 'login',
      component: () => import('../views/Login.vue')
    }

在这里插入图片描述
template和style代码直接拷贝就可以了

<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">
        <div class="right">
          <div class="sys-name">CK-OJ后台管理</div>
          <div class="sys-sub-name">帮助ZL学习</div>
        </div>
      </div>
      <div class="form-box">
        <div class="form-item">
          <img src="../assets/images/shouji.png">
          <el-input v-model="userAccount" placeholder="请输入账号" />
          <!-- <el-input v-model="input" style="width: 240px" placeholder="Please input" /> -->
        </div>
        <div class="form-item">
          <img src="../assets/images/yanzhengma.png">
          <el-input v-model="password"  type="password" show-password placeholder="请输入密码" />
        </div>
        <div class="submit-box" @click="loginFun">
          登录
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
</script>

<style lang="scss" scoped>
.login-page {
  width: 100vw;
  height: 100vh;
  position: relative;
  overflow: hidden;

  .login-box {
    overflow: hidden;

    .logo-box {
      display: flex;
      align-items: center;
      margin-bottom: 30px;

      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;
      }
    }

    :deep(.form-box) {
      .submit-box {
        margin-top: 90px;
        width: 456px;
        height: 48px;
        background: #32C5FF;
        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;
      }

      .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;
        }
      }
    }

    width:456px;
    height:404px;
    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;
  }

  &::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>

在这里插入图片描述

npm install -D sass-embedded

我们安装一下就可以了

<style lang="scss" scoped>

中的scss就是sass中的一种语法格式而已
所以这样安装是可以的
sass比css更好用,以前说过的
在这里插入图片描述
这样就成功了
还差图片的展示了

          <img src="../assets/images/shouji.png">

这就是图片来源

图片可去码云拷贝
在这里插入图片描述
然后就是

          <el-input v-model="userAccount" placeholder="请输入账号" />

这里前端页面没有显示请输入账号,原因就是我们还没有引入elementPlus

element-plus

官网

安装

npm install element-plus --save

在这里插入图片描述
右边的加号,可以新建一个终端

对于elementPlus我们是按需导入的

在这里插入图片描述
找到这里就是按需导入了

npm install -D unplugin-vue-components unplugin-auto-import

执行命令

然后把下列代码插入到你的 Vite 或 Webpack 的配置文件中
我们用的是vite

vite.config.ts

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

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

这样就OK了

在这里插入图片描述
按需导入的配置就完成了

在这里插入图片描述
现在就有文字了

但是现在的输入框连文字都不能输入进来

在这里插入图片描述
这个原因就是v-model的问题了

<script lang="ts" setup>

这里我们使用的是js的语法,不用管这个就是了

<script setup>
import { ref } from 'vue'
const input = ref('')
</script>

在js中弄这个,就可以获取数据了,就可以建立对应关系了,就可以输入了

<script setup>
import { ref } from 'vue'
const userAccount = ref('')
setTimeout(() => {
  console.log(userAccount.value)
}, 5000)
</script>

我们修改代码如上
setTimeout的意思就是执行到这个函数的时候,过5s在去执行方法体
而且这样就可以输入数据了
在这里插入图片描述
这样就成功了

axios

官网

npm install axios

然后是为axios创建唯一实例

src下创建utils包,下面创建requset.js

import axios from "axios";

const service = axios.create({
    baseURL: "127.0.0.1:19090/system",
    timeout: 1000,
})

export default service;

我们要创建一个自己的axios
我们可以使用它提供的axios ,但是呢,这个肯定是没有设置baseURL的
所以我们要创建自己的axios就是service ,并设置baseUrl和timeout
export default service;就是把service导出去,让每个文件夹都可以import

第二就是我们在src下创建apis,然后再在下面创建suser.js,表示管理员的
什么意思呢,意思就是我们把前后端交互的部分写在一些公共的js文件中,为什么呢,因为有些组件,可能会同时使用一些接口
比如查询用户,可能有多个组件都会使用这个方法,所以我们分装到一个js中就很好了

import service from "@/utils/request";

export function loginService(userAccount, password){
    return service({
        url: '/sysuser/login',
        method: 'post',
        data: {
            userAccount,
            password
        }
    })
}

这样就OK了
我们就可以在login.vue中直接引用这个方法了
其中export的作用仍然是让其他人可以import它

function loginFun() {
  loginService(userAccount.value, password.value)
}

这样就OK了

总结


网站公告

今日签到

点亮在社区的每一天
去签到