Compare commits

...

2 Commits

@ -0,0 +1,3 @@
软件工程结对作业前后端实现
> 前端参考之前的UU考勤项目

@ -9,10 +9,10 @@
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.flyingpig</groupId>
<artifactId>uuAttendance</artifactId>
<artifactId>k-class-roll-call</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>uuAttendance</name>
<description>An attendance software that supports roll calling and location check-in.</description>
<name>k-class-roll-call</name>
<description>Roll call system for k class.</description>
<properties>
<java.version>17</java.version>
</properties>
@ -41,6 +41,12 @@
<artifactId>easyexcel</artifactId>
<version>3.1.0</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version> <!-- 替换为你想要的最新版本 -->
<scope>test</scope> <!-- 确保它是在测试范围内 -->
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
@ -123,7 +129,15 @@
<artifactId>easyexcel</artifactId>
<version>3.1.0</version> <!-- 可以根据需要使用最新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
@ -131,7 +145,11 @@
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.3</version>
</dependency>
</dependencies>

@ -0,0 +1,10 @@
package com.flyingpig.kclassrollcall.common;
public class RedisConstant {
public static final String STUDENT_LIST_KEY = "student:list:";
public static final String STUDENT_SCORE_KEY = "student:score:";
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
// 配置
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
// 创建RedissonClient对象
return Redisson.create(config);
}
}

@ -1,20 +1,20 @@
package com.flyingpig.kclassrollcall.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.flyingpig.kclassrollcall.common.RedisConstant;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.entity.Student;
import com.flyingpig.kclassrollcall.filter.UserContext;
import com.flyingpig.kclassrollcall.service.IStudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import javax.swing.text.html.parser.Element;
/**
* <p>
@ -32,10 +32,19 @@ public class StudentController {
@Autowired
IStudentService studentService;
@Autowired
StringRedisTemplate stringRedisTemplate;
@PutMapping("/score")
public Result modifyScore(Long id, Double score) {
return Result.success(studentService.updateById(new Student(id,
null, null, null, studentService.getById(id).getScore() + score, null)));
if (studentService.updateById(new Student(id,
null, null, null, studentService.getById(id).getScore() + score, null))) {
stringRedisTemplate.delete(RedisConstant.STUDENT_SCORE_KEY + id);
return Result.success();
} else {
return Result.error("修改分数出错");
}
}
@GetMapping("/roll-call")
@ -50,6 +59,7 @@ public class StudentController {
@PostMapping("/import")
public Result importExcel(@RequestParam("file") MultipartFile file) {
System.out.println("用户上传文件");
// 如果 validateExcelHeader 返回 false则文件类型或格式错误
if (!validateExcelHeader(file)) {
return Result.error("文件类型或格式错误");
@ -87,6 +97,8 @@ public class StudentController {
@DeleteMapping("/{id}")
public Result deleteStudent(@PathVariable String id) {
if (studentService.removeById(id)) {
stringRedisTemplate.delete(RedisConstant.STUDENT_LIST_KEY + UserContext.getUser());
stringRedisTemplate.delete(RedisConstant.STUDENT_SCORE_KEY + id);
return Result.success();
} else {
return Result.error("删除失败");

@ -37,4 +37,9 @@ public class TeacherController {
return teacherService.register(teacher);
}
@PostMapping("/logout")
public Result logout(@RequestBody Teacher teacher){
return Result.success();
}
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.dto.resp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentInfoInCache {
private Long id;
private String name;
private Integer no;
private String major;
}

@ -0,0 +1,17 @@
package com.flyingpig.kclassrollcall.init;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j(topic = "Initialize DispatcherServlet")
@RestController
public final class InitializeDispatcherServletController {
@GetMapping("/initialize/dispatcher-servlet")
public void initializeDispatcherServlet() {
log.info("Initialized the dispatcherServlet to improve the first response time of the interface...");
}
}

@ -0,0 +1,29 @@
package com.flyingpig.kclassrollcall.init;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
@RequiredArgsConstructor
public final class InitializeDispatcherServletHandler implements CommandLineRunner {
private final RestTemplate restTemplate;
private final ConfigurableEnvironment configurableEnvironment;
@Override
public void run(String... args) throws Exception {
String url = String.format("http://127.0.0.1:%s%s",
configurableEnvironment.getProperty("server.port", "8080") + configurableEnvironment.getProperty("server.servlet.context-path", ""),
"/initialize/dispatcher-servlet");
try {
restTemplate.execute(url, HttpMethod.GET, null, null);
} catch (Throwable ignored) {
}
}
}

@ -7,19 +7,27 @@ import com.alibaba.excel.read.listener.ReadListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.flyingpig.kclassrollcall.common.RedisConstant;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.common.RollCallMode;
import com.flyingpig.kclassrollcall.dto.req.StudentExcelModel;
import com.flyingpig.kclassrollcall.dto.resp.StudentInfoInCache;
import com.flyingpig.kclassrollcall.entity.Student;
import com.flyingpig.kclassrollcall.filter.UserContext;
import com.flyingpig.kclassrollcall.mapper.StudentMapper;
import com.flyingpig.kclassrollcall.service.IStudentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.flyingpig.kclassrollcall.util.cache.ListCacheUtil;
import com.flyingpig.kclassrollcall.util.cache.StringCacheUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* <p>
@ -32,28 +40,67 @@ import java.util.*;
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements IStudentService {
@Autowired
ListCacheUtil listCacheUtil;
@Autowired
StringCacheUtil stringCacheUtil;
@Override
public Result rollCall(String mode) {
if (!mode.equals(RollCallMode.EQUAL)) {
return Result.success(rollBackStudentBaseScore());
} else {
return Result.success(this.baseMapper.rollCall(UserContext.getUser(), new Random().nextInt(count())));
return Result.success(selectStudentByTeacherId().get(new Random().nextInt(count()) - 1));
}
}
private List<Student> selectStudentByTeacherId() {
List<StudentInfoInCache> cachedList = listCacheUtil.safeGetWithLock(
RedisConstant.STUDENT_LIST_KEY + UserContext.getUser(),
StudentInfoInCache.class, // 使用具体的 StudentInfoInCache.class 作为类型
() -> {
// 查询学生的逻辑,转换为 StudentInfoInCache 列表
return this.baseMapper.selectList(new LambdaQueryWrapper<Student>()
.eq(Student::getTeacherId, UserContext.getUser())).stream()
.map(student -> new StudentInfoInCache(student.getId(), student.getName(), student.getNo(), student.getMajor()))
.collect(Collectors.toList());
},
30L,
TimeUnit.MINUTES
);
List<Student> students = new ArrayList<>();
for (StudentInfoInCache studentInfo : cachedList) {
Student newStudent = new Student();
BeanUtil.copyProperties(studentInfo, newStudent);
newStudent.setScore(stringCacheUtil.safeGetWithLock(
RedisConstant.STUDENT_SCORE_KEY + studentInfo.getId(),
Double.class, // 传入正确的类型
() -> {
return this.getBaseMapper().selectById(studentInfo.getId()).getScore();
},
30L,
TimeUnit.MINUTES
));
newStudent.setTeacherId(Long.parseLong(UserContext.getUser()));
students.add(newStudent);
}
return students;
}
private Student rollBackStudentBaseScore() {
// 获取符合条件的学生列表
LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<Student>()
.eq(Student::getTeacherId, UserContext.getUser());
List<Student> students = this.baseMapper.selectList(queryWrapper);
List<Student> students = selectStudentByTeacherId();
// 计算权重
double totalWeight = 0;
Map<Student, Double> weightMap = new HashMap<>();
for (Student student : students) {
double weight;
if (student.getScore() > 0) {
weight = 1.0 / student.getScore(); // 正常权重
weight = 1.0 / student.getScore();
} else {
weight = 1; // 给分数为0的学生一个较大的固定权重
}
@ -81,10 +128,12 @@ public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> impl
LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<>();
// 如果 no 和 name 不为空,则分别进行模糊查询
queryWrapper.like(StringUtils.isNotBlank(no), Student::getNo, no)
.or()
queryWrapper
.like(StringUtils.isNotBlank(no), Student::getNo, no)
.or(StringUtils.isNotBlank(name)) // 仅在 name 不为空时才使用 or
.like(StringUtils.isNotBlank(name), Student::getName, name)
.eq(Student::getTeacherId, UserContext.getUser());
System.out.println(queryWrapper.getTargetSql());
// 分页查询
Page<Student> page = new Page<>(pageNo, pageSize);
@ -104,7 +153,7 @@ public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> impl
if (!students.isEmpty()) {
saveBatch(students);
}
stringCacheUtil.getInstance().delete(RedisConstant.STUDENT_LIST_KEY + UserContext.getUser());
return Result.success("导入成功,已添加 " + students.size() + " 条记录");
}

@ -0,0 +1,14 @@
package com.flyingpig.kclassrollcall.util.cache;
/**
*
*
*/
@FunctionalInterface
public interface CacheLoader<T> {
/**
*
*/
T load();
}

@ -0,0 +1,101 @@
package com.flyingpig.kclassrollcall.util.cache;
import com.alibaba.fastjson.JSON;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Component
public class ListCacheUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
public <T> void set(String key, List<T> values, Long time, TimeUnit unit) {
// 使用事务确保原子性
stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
// 清空现有列表内容
stringRedisTemplate.opsForList().trim(key, 0, -1);
// 将列表中的每个值存储到 Redis 列表中
values.forEach(value -> stringRedisTemplate.opsForList().rightPush(key, JSON.toJSONString(value)));
// 设置列表的过期时间
stringRedisTemplate.expire(key, time, unit);
return null;
});
}
public <T> List<T> get(String key, Class<T> type) {
List<String> value = stringRedisTemplate.opsForList().range(key, 0, -1);
return (value == null || value.isEmpty()) ? null : value.stream()
.map(json -> JSON.parseObject(json, type))
.collect(Collectors.toList());
}
public <T> List<T> safeGetWithLock(String key, Class<T> type, CacheLoader<List<T>> cacheLoader, Long time, TimeUnit unit) {
List<T> cachedValues = get(key, type);
// 1. 命中且不为空,直接返回; 命中却为空,返回 null
if (cachedValues != null) {
return cachedValues;
}
// 获取锁
String lockKey = "lock:" + key;
RLock rLock = redissonClient.getLock(lockKey);
List<T> result = null;
try {
if (rLock.tryLock()) { // 尝试获取锁,避免死锁
// 再次查询 Redis双重判定
cachedValues = get(key, type);
if (cachedValues != null) {
return cachedValues;
}
// 获取锁成功,查询数据库
result = loadAndSet(key, cacheLoader, time, unit);
}
} catch (Exception e) {
// 记录日志
// logger.error("Error occurred while accessing cache with key: {}", key, e);
} finally {
// 确保释放锁
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
return result;
}
private <T> List<T> loadAndSet(String key, CacheLoader<List<T>> cacheLoader, Long time, TimeUnit unit) {
// 获取锁成功,查询数据库
List<T> result = cacheLoader.load(); // 返回 List<T>
if (result == null || result.isEmpty()) {
// 将空值写入 Redis返回 null
stringRedisTemplate.opsForList().rightPush(key, ""); // 存储一个空字符串作为占位符
stringRedisTemplate.expire(key, time, unit);
return null;
}
// 存在,写入 Redis
set(key, result, time, unit);
return result;
}
public ListOperations<String, String> getListOperations() {
return stringRedisTemplate.opsForList();
}
}

@ -0,0 +1,101 @@
package com.flyingpig.kclassrollcall.util.cache;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class StringCacheUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
public ValueOperations<String, String> getValueOperations() {
return stringRedisTemplate.opsForValue();
}
public StringRedisTemplate getInstance() {
return stringRedisTemplate;
}
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value), time, unit);
}
public <T> T get(String key, Class<T> type) {
String value = stringRedisTemplate.opsForValue().get(key);
if (value != null) {
return JSON.parseObject(value, type);
}
return null; // 允许返回 null
}
// 查询时缓存空值防止缓存穿透,互斥锁查询防止缓存击穿
public <T> T safeGetWithLock(
String key, Class<T> type, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
String json = stringRedisTemplate.opsForValue().get(key);
// 命中且不为空字符串,直接返回;命中却为空字符串,返回 null
if (StrUtil.isNotBlank(json)) {
return JSON.parseObject(json, type);
} else if (json != null) {
return null; // 允许返回 null
}
// 获取锁
String lockKey = String.format("lock:%s", key);
T result = null;
RLock rLock = redissonClient.getLock(lockKey);
try {
rLock.lock();
// 再次查询redis双重判定
json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {
return JSON.parseObject(json, type);
} else if (json != null) {
return null; // 允许返回 null
}
// 获取锁成功,查询数据库
result = loadAndSet(key, cacheLoader, time, unit);
} catch (Exception e) {
// 记录日志
log.error("Error occurred while accessing cache with key: {}", key, e);
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
return result;
}
private <T> T loadAndSet(String key, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
T result = cacheLoader.load();
// 不存在将空值写入redis返回 null
if (result == null) {
stringRedisTemplate.opsForValue().set(key, "", time, TimeUnit.MINUTES);
} else {
this.set(key, result, time, unit);
}
return result;
}
}

@ -6,6 +6,17 @@ spring:
url: jdbc:mysql://localhost:3306/k-class-roll-call
username: root
password: '@Aa123456'
hikari:
minimum-idle: 5 # 最小空闲连接数
maximum-pool-size: 10 # 连接池最大连接数
connection-timeout: 30000 # 连接超时时间
idle-timeout: 600000 # 空闲连接的存活时间
max-lifetime: 1800000 # 连接的最长存活时间
redis:
host: localhost
port: 6379
database: 0
logging:
level:
com.baomidou.mybatisplus: DEBUG # MyBatis-Plus 日志级别

@ -0,0 +1,59 @@
/*
Navicat Premium Data Transfer
Source Server : k-class-roll-call
Source Server Type : MySQL
Source Server Version : 80032 (8.0.32)
Source Host : localhost:3306
Source Schema : k-class-roll-call
Target Server Type : MySQL
Target Server Version : 80032 (8.0.32)
File Encoding : 65001
Date: 28/09/2024 11:06:20
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` bigint NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`no` int NULL DEFAULT NULL,
`major` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`score` decimal(10, 2) NULL DEFAULT NULL,
`teacher_id` bigint NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, '朱嘉翔', 102201604, '计算机科学与技术', 25.50, 1);
INSERT INTO `student` VALUES (2, '范国鑫', 102201525, '计算机科学与技术', 34.00, 1);
INSERT INTO `student` VALUES (1839635335860477954, '庄学鹏', 102201605, '计算机科学与技术', 9.00, 1);
-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
`id` bigint NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES (1, '1', '1', '1');
INSERT INTO `teacher` VALUES (1839133469267496961, 'flyingpig', 'flyingpig', 'flyingpig');
SET FOREIGN_KEY_CHECKS = 1;

@ -0,0 +1,72 @@
package com.flyingpig.kclassrollcall;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.flyingpig.kclassrollcall.common.RedisConstant;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.common.RollCallMode;
import com.flyingpig.kclassrollcall.dto.resp.StudentInfoInCache;
import com.flyingpig.kclassrollcall.entity.Student;
import com.flyingpig.kclassrollcall.filter.UserContext;
import com.flyingpig.kclassrollcall.mapper.StudentMapper;
import com.flyingpig.kclassrollcall.service.impl.StudentServiceImpl;
import com.flyingpig.kclassrollcall.util.cache.ListCacheUtil;
import com.flyingpig.kclassrollcall.util.cache.StringCacheUtil;
import com.google.common.cache.CacheLoader;
import org.apache.poi.ss.formula.functions.T;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.any;
@ExtendWith(MockitoExtension.class)
public class StudentServiceImplTest {
@Mock
private StudentMapper studentMapper;
@Mock
private ListCacheUtil listCacheUtil;
@Mock
private StringCacheUtil stringCacheUtil;
@InjectMocks
private StudentServiceImpl studentService;
@Test
public <T>
void testRollCall() {
// 模拟依赖方法
// 调用依赖方法方法
Mockito.doReturn(Result.success()).when(studentService).rollCall(RollCallMode.EQUAL);
List<Student> mockStudents = Arrays.asList(
new Student(1L, "Alice", 1, "Math", 50.0, 100L),
new Student(2L, "Bob", 2, "Science", 0.0, 100L)
);
when(studentMapper.selectList(new LambdaQueryWrapper<Student>().eq(Student::getTeacherId, any()))).thenReturn(mockStudents);
// 模拟 rollCall 调用
Result result = studentService.rollCall(RollCallMode.EQUAL);
// 验证返回结果
assertNotNull(result);
assertTrue(mockStudents.contains(result.getData())); // 随机选择一个学生
}
}

@ -0,0 +1,70 @@
package com.flyingpig.kclassrollcall;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.dto.req.LoginReq;
import com.flyingpig.kclassrollcall.dto.resp.LoginResp;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.flyingpig.kclassrollcall.mapper.TeacherMapper;
import com.flyingpig.kclassrollcall.service.ITeacherService;
import com.flyingpig.kclassrollcall.service.impl.TeacherServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@SpringBootTest
public class TeacherServiceImplTest {
@InjectMocks
private TeacherServiceImpl teacherService; // 替换为你的实际服务类
@Mock
private TeacherMapper teacherMapper; // 替换为你的 TeacherMapper 类
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testLoginSuccess() {
// Arrange
LoginReq loginReq = new LoginReq("flyingpig", "flyingpig");
Teacher teacher = new Teacher();
teacher.setId(1L);
teacher.setName("John Doe");
teacher.setUsername("flyingpig");
teacher.setPassword("flyingpig");
when(teacherMapper.selectOne(any())).thenReturn(teacher); // 模拟数据库返回教师对象
// Act
Result result = teacherService.login(loginReq);
// Assert
assertEquals(200, result.getCode()); // 检查返回代码
assertEquals("John Doe", ((LoginResp) result.getData()).getName()); // 检查返回的教师名称
}
@Test
public void testLoginFailure() {
// Arrange
LoginReq loginReq = new LoginReq("username", "wrongpassword");
when(teacherMapper.selectOne(any())).thenReturn(null);
// Act
Result result = teacherService.login(loginReq);
// Assert
assertEquals(500, result.getCode()); // 检查返回代码
assertEquals("账号或密码错误", result.getMsg()); // 检查返回的错误信息
}
}

@ -0,0 +1,135 @@
function clickEffect() {
let balls = [];
let longPressed = false;
let longPress;
let multiplier = 0;
let width, height;
let origin;
let normal;
let ctx;
const colours = ["#F73859", "#14FFEC", "#00E0FF", "#FF99FE", "#FAF15D"];
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");
const pointer = document.createElement("span");
pointer.classList.add("pointer");
document.body.appendChild(pointer);
if (canvas.getContext && window.addEventListener) {
ctx = canvas.getContext("2d");
updateSize();
window.addEventListener('resize', updateSize, false);
loop();
window.addEventListener("mousedown", function(e) {
pushBalls(randBetween(10, 20), e.clientX, e.clientY);
document.body.classList.add("is-pressed");
longPress = setTimeout(function() {
document.body.classList.add("is-longpress");
longPressed = true;
}, 500);
}, false);
window.addEventListener("mouseup", function(e) {
clearInterval(longPress);
if (longPressed == true) {
document.body.classList.remove("is-longpress");
pushBalls(randBetween(50 + Math.ceil(multiplier), 100 + Math.ceil(multiplier)), e.clientX, e.clientY);
longPressed = false;
}
document.body.classList.remove("is-pressed");
}, false);
window.addEventListener("mousemove", function(e) {
let x = e.clientX;
let y = e.clientY;
pointer.style.top = y + "px";
pointer.style.left = x + "px";
}, false);
} else {
console.log("canvas or addEventListener is unsupported!");
}
function updateSize() {
canvas.width = window.innerWidth * 2;
canvas.height = window.innerHeight * 2;
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
ctx.scale(2, 2);
width = (canvas.width = window.innerWidth);
height = (canvas.height = window.innerHeight);
origin = {
x: width / 2,
y: height / 2
};
normal = {
x: width / 2,
y: height / 2
};
}
class Ball {
constructor(x = origin.x, y = origin.y) {
this.x = x;
this.y = y;
this.angle = Math.PI * 2 * Math.random();
if (longPressed == true) {
this.multiplier = randBetween(14 + multiplier, 15 + multiplier);
} else {
this.multiplier = randBetween(6, 12);
}
this.vx = (this.multiplier + Math.random() * 0.5) * Math.cos(this.angle);
this.vy = (this.multiplier + Math.random() * 0.5) * Math.sin(this.angle);
this.r = randBetween(8, 12) + 3 * Math.random();
this.color = colours[Math.floor(Math.random() * colours.length)];
}
update() {
this.x += this.vx - normal.x;
this.y += this.vy - normal.y;
normal.x = -2 / window.innerWidth * Math.sin(this.angle);
normal.y = -2 / window.innerHeight * Math.cos(this.angle);
this.r -= 0.3;
this.vx *= 0.9;
this.vy *= 0.9;
}
}
function pushBalls(count = 1, x = origin.x, y = origin.y) {
for (let i = 0; i < count; i++) {
balls.push(new Ball(x, y));
}
}
function randBetween(min, max) {
return Math.floor(Math.random() * max) + min;
}
function loop() {
ctx.fillStyle = "rgba(255, 255, 255, 0)";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < balls.length; i++) {
let b = balls[i];
if (b.r < 0) continue;
ctx.fillStyle = b.color;
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2, false);
ctx.fill();
b.update();
}
if (longPressed == true) {
multiplier += 0.2;
} else if (!longPressed && multiplier >= 0) {
multiplier -= 0.4;
}
removeBall();
requestAnimationFrame(loop);
}
function removeBall() {
for (let i = 0; i < balls.length; i++) {
let b = balls[i];
if (b.x + b.r < 0 || b.x - b.r > width || b.y + b.r < 0 || b.y - b.r > height || b.r < 0) {
balls.splice(i, 1);
}
}
}
}
clickEffect();//调用

@ -1,17 +1,22 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>K班点名系统ヾ(≧▽≦*)o</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>K班点名系统ヾ(≧▽≦*)o</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="1.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 212 KiB

@ -11,14 +11,7 @@
</div>
</div>
<div id="hello">{{ name }}老师您好</div>
<router-link to="/student">
<div id="cloud1">
<div class="func-title" id="title1">学生<br />查询</div>
<div class="func-content" id="content1">
搜索获取班级学生名单以及学生积分情况导入学生名单
</div>
</div>
</router-link>
<router-link to="/student">
<div id="cloud2">
@ -29,10 +22,12 @@
</div>
</router-link>
<router-link to="/roll-call">
<router-link to="/student">
<div id="cloud3">
<div class="func-title" id="title3">点名<br />提问</div>
<div class="func-content" id="content3">对学生进行点名并对到场的学生进行提问</div>
<div class="func-title" id="title2">学生<br />查询</div>
<div class="func-content" id="content2">
搜索获取班级学生名单以及学生积分情况导入学生名单
</div>
</div>
</router-link>
@ -59,14 +54,16 @@
export default {
data() {
return {
name: "MEW",
name: "FlyingPig",
semester: "",
};
},
beforeMount() {
if (localStorage.getItem("name") == null) {
alert("您还未登录或登录已过期,请重新登录!");
this.$router.push("/login");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
} else {
this.name = localStorage.getItem("name");
}
@ -90,11 +87,10 @@ export default {
position: relative;
width: 85.4%;
height: 100vh;
min-width: 910px;
min-height: 700px;
background-image: url("../assets/home/background.png");
background-image: url("../assets/home/backgound2.png");
background-size: cover;
display: flex;
overflow: hidden; /* 隐藏溢出内容 */
}
#banner {
box-sizing: border-box;
@ -103,7 +99,7 @@ export default {
margin-top: 15px;
width: 90%;
height: 50px;
background-color: #d0e7f2;
background-color: #b9c1c4;
border-radius: 20px;
justify-content: center;
background-size: cover;
@ -133,14 +129,6 @@ export default {
align-self: flex-start;
font-weight: bold;
}
@keyframes move1 {
from {
transform: translate(100%, -100%);
}
to {
transform: translate(0%, 0%);
}
}
@keyframes move2 {
from {
transform: translate(120%, -80%);
@ -173,19 +161,7 @@ export default {
transform: translate(0%, 0%);
}
}
#cloud1 {
background-image: url("../assets/home/cloud1.png");
background-size: contain;
position: absolute;
z-index: 100;
width: 439px;
height: 259px;
top: 70.9%;
left: 6%;
animation: move1 1.5s ease;
}
#cloud2 {
background-image: url("../assets/home/cloud2.png");
background-size: contain;
position: absolute;
z-index: 100;
@ -196,7 +172,6 @@ export default {
animation: move2 1.5s ease;
}
#cloud3 {
background-image: url("../assets/home/cloud3.png");
background-size: contain;
position: absolute;
z-index: 100;
@ -207,7 +182,6 @@ export default {
animation: move3 1.5s ease;
}
#cloud4 {
background-image: url("../assets/home/cloud4.png");
background-size: contain;
position: absolute;
z-index: 100;
@ -218,7 +192,6 @@ export default {
animation: move4 1.5s ease;
}
#cloud5 {
background-image: url("../assets/home/cloud5.png");
background-size: contain;
position: absolute;
z-index: 100;
@ -234,7 +207,7 @@ export default {
margin-top: 25px;
margin-left: 20px;
position: absolute;
color: white;
color: black;
}
.func-content {
font-size: 18px;
@ -243,20 +216,6 @@ export default {
margin-left: 20px;
position: absolute;
}
#title1 {
top: 23%;
left: 60%;
letter-spacing: 2px;
}
#content1 {
width: 190px;
top: 40%;
left: 12%;
color: #000;
}
#content1:hover {
color: rgb(255, 86, 86);
}
#title2 {
top: 23%;
left: 60%;
@ -269,22 +228,13 @@ export default {
color: #000;
}
#content2:hover {
color: rgb(255, 86, 86);
color: white;
}
#title3 {
top: 38%;
left: 60%;
letter-spacing: 2px;
}
#content3 {
width: 190px;
top: 26%;
left: 14%;
color: #000;
}
#content3:hover {
color: rgb(255, 86, 86);
}
#title4 {
top: 45%;
left: 37%;
@ -294,10 +244,10 @@ export default {
width: 190px;
top: 21%;
left: 14%;
color: #000;
color: black;
}
#content4:hover {
color: rgb(255, 86, 86);
color: white;
}
#title5 {
top: 35%;
@ -311,6 +261,6 @@ export default {
color: #000;
}
#content5:hover {
color: rgb(255, 86, 86);
color: white;
}
</style>

@ -1,5 +1,5 @@
<template>
<div id="check-attendance">
<div id="question">
<!-- 添加 "进入提问环节" 文本 -->
<div id="question-phase">
进入提问环节
@ -11,125 +11,126 @@
</div>
<div id="circle-container">
<button
v-for="(score, index) in scores"
:key="index"
class="circle-button"
:style="getButtonStyle(index)"
@click="selectScore(score)"
>
{{ score }}
</button>
</div>
<div id="selected-score">
选择的分数: {{ selectedScore }}
</div>
<div id="circle-container">
<button v-for="(score, index) in scores" :key="index" class="circle-button" :style="getButtonStyle(index)"
@click="selectScore(score)">
{{ score }}
</button>
</div>
</template>
</div>
</template>
<script>
import { ModifyStudentScore } from "@/api/api"; //
<script>
import { ModifyStudentScore } from "@/api/api"; //
export default {
data() {
return {
scores: [-1, 0.5, 1, 1.5, 2, 2.5, 3],
selectedScore: null,
currentStudent: this.$route.params.student, //
detailModeType: this.$route.params.detailModeType //
};
},
methods: {
selectScore(score) {
this.selectedScore = score; //
if (this.detailModeType != 0 && this.detailModeType != 2) {
score = this.detailModeType * score;
}
// 使 this.detailModeType 访 detailModeType
ModifyStudentScore(this.currentStudent.id, score).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
} else if (res.code == 200) {
this.$message.success(this.currentStudent.name + "分数修改成功, 增加" + score + "分");
if (this.$route.path !== "/roll-call") {
this.$router.push("/roll-call");
}
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
getButtonStyle(index) {
const angle = (index / this.scores.length) * 2 * Math.PI; //
const radius = 150; //
const x = radius * Math.cos(angle); // x
const y = radius * Math.sin(angle); // y
export default {
data() {
return {
scores: [-1, 0.5, 1, 1.5, 2, 2.5, 3],
selectedScore: null,
currentStudent: this.$route.params.student, //
detailModeType: this.$route.params.detailModeType //
position: 'absolute',
left: `${150 + x}px`, // x
top: `${150 + y}px`, // y
};
},
methods: {
selectScore(score) {
this.selectedScore = score; //
if (this.detailModeType != 0) {
score = this.detailModeType * score;
}
// 使 this.detailModeType 访 detailModeType
ModifyStudentScore(this.currentStudent.id, score).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
} else if (res.code == 200) {
this.$message.success(this.currentStudent.name + "分数修改成功, 增加" + score + "分");
this.$router.push("/roll-call");
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
this.$router.push("/login");
}
});
},
},
};
</script>
getButtonStyle(index) {
const angle = (index / this.scores.length) * 2 * Math.PI; //
const radius = 150; //
const x = radius * Math.cos(angle); // x
const y = radius * Math.sin(angle); // y
<style scoped>
#question {
box-sizing: border-box;
position: relative;
width: 85.4%;
height: 100vh;
background-image: url("../assets/login/login-background.png");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
/* 隐藏溢出内容 */
}
return {
position: 'absolute',
left: `${150 + x}px`, // x
top: `${150 + y}px`, // y
};
},
},
};
</script>
#question-phase {
font-size: 28px;
color: black;
margin-bottom: 10px;
}
#current-student {
font-size: 24px;
color: black;
margin-bottom: 30px;
}
<style scoped>
#check-attendance {
box-sizing: border-box;
position: relative;
width: 85.4%;
height: 100vh;
min-width: 910px;
min-height: 700px;
background-image: url("../assets/background.png");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#question-phase {
font-size: 28px;
color: white;
margin-bottom: 10px;
}
#current-student {
font-size: 24px;
color: white;
margin-bottom: 30px;
}
#circle-container {
position: relative;
width: 300px; /* 容器宽度,圆环直径 */
height: 300px;
position: relative;
}
.circle-button {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #87CEEB;
color: white;
border: none;
font-size: 18px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.3s;
}
#circle-container {
position: relative;
width: 300px;
/* 容器宽度,圆环直径 */
height: 300px;
position: relative;
}
.circle-button:hover {
background-color: #1E90FF;
}
.circle-button {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #87CEEB;
color: white;
border: none;
font-size: 18px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.3s;
}
#selected-score {
margin-top: 20px;
font-size: 24px;
color: white;
}
</style>
.circle-button:hover {
background-color: #1E90FF;
}
#selected-score {
margin-top: 20px;
font-size: 24px;
color: white;
}
</style>

@ -1,11 +1,8 @@
<template>
<div id="check-attendance">
<div id="header">
<button
id="mode-button"
@click="toggleMode"
:style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e', borderRadius: '20px', color: 'white' }"
>
<button id="mode-button" @click="toggleMode"
:style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e', borderRadius: '20px', color: 'white' }">
{{ modeName }}
</button>
</div>
@ -15,11 +12,8 @@
</p>
<div id="attendance-button-container">
<button
id="attendance-button"
:style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e' }"
@click="getRandomStudent"
>
<button id="attendance-button" :style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e' }"
@click="getRandomStudent">
点名
</button>
</div>
@ -49,11 +43,11 @@ export default {
return {
modeName: '生成随机事件',
isRandomMode: true, /* true为正常模式false为随机事件模式 */
detailModeType: 0, /* 0为权重模式1为众生平等2为两倍积分模式4为疯狂星期四模式 */
detailModeType: 0, /* 0为权重模式1为众生平等2为两倍积分模式3为幸运星期三模式 */
currentStudent: null,
students: [],
randomEvent: null, //
randomEvents: ['双倍加分', '疯狂星期四', '众生平等'], //
randomEvents: ['双倍扣分', '幸运星期三', '众生平等'], //
};
},
methods: {
@ -63,10 +57,10 @@ export default {
if (!this.isRandomMode) {
this.randomEvent = this.randomEvents[Math.floor(Math.random() * this.randomEvents.length)]; //
if (this.randomEvent == '双倍分') {
this.detailModeType = 1;
} else if (this.randomEvent == '疯狂星期四') {
this.detailModeType = 4;
if (this.randomEvent == '双倍分') {
this.detailModeType = 2;
} else if (this.randomEvent == '幸运星期三') {
this.detailModeType = 3;
} else if (this.randomEvent == '众生平等') {
this.detailModeType = 1;
}
@ -81,40 +75,60 @@ export default {
if (res.code == 500) {
this.$message.error("获取点名学生发生错误");
} else if (res.code == 200) {
this.currentStudent = res.data;
this.$message.success("查询成功");
} else if(res.code == 401){
this.currentStudent = res.data;
this.$message.success("查询成功");
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
this.$router.push("/login");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
markAbsent() {
ModifyStudentScore(this.currentStudent.id, -2).then((res) => {
let score = -2;
if (this.detailModeType == 2) {
score = -4;
}
ModifyStudentScore(this.currentStudent.id, score).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
this.currentStudent = null; //
} else if (res.code == 200) {
alert(`${this.currentStudent.name} 未到教室,已被登记,扣除两分`);
if (score == -2) {
alert(`${this.currentStudent.name} 未到教室,已被登记,扣除两分`);
} else {
alert(`${this.currentStudent.name} 未到教室,已被登记,扣除四分`);
}
this.currentStudent = null; //
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
this.$router.push("/login");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
markPresent() {
ModifyStudentScore(this.currentStudent.id, 1).then((res) => {
let score = 1;
if (this.detailModeType == 3) {
score = score * 3;
}
ModifyStudentScore(this.currentStudent.id, score).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
this.currentStudent = null; //
} else if (res.code == 200) {
this.$message.success(this.currentStudent.name + " 到达教室增加1分进入提问环节");
// alert(`${this.currentStudent.name} 1`);
this.$router.push({ name: 'question', params: { student: this.currentStudent,detailModeType: this.detailModeType } });
this.$message.success(this.currentStudent.name + " 到达教室增加"+score+"分,进入提问环节");
if (this.$route.path !== "/question") {
this.$router.push({ name: 'question', params: { student: this.currentStudent, detailModeType: this.detailModeType } });
}
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
this.$router.push("/login");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
@ -132,13 +146,14 @@ export default {
position: relative;
width: 85.4%;
height: 100vh;
min-width: 910px;
min-height: 700px;
background-image: url("../assets/background.png");
/* background-image: url("../assets/background.png"); */
background-image: url("../assets/login/login-background.png");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow: hidden;
/* 隐藏溢出内容 */
}
#header {
@ -150,10 +165,14 @@ export default {
#mode-button {
border: none;
border-radius: 20px; /* 圆角按钮 */
padding: 10px 20px; /* 增加内边距 */
font-size: 16px; /* 调整字体大小 */
cursor: pointer; /* 鼠标指针样式 */
border-radius: 20px;
/* 圆角按钮 */
padding: 10px 20px;
/* 增加内边距 */
font-size: 16px;
/* 调整字体大小 */
cursor: pointer;
/* 鼠标指针样式 */
}
#attendance-button-container {
@ -186,30 +205,36 @@ export default {
}
#action-buttons button {
flex: 0 0 100px; /* 增加宽度 */
height: 70px; /* 增加高度 */
margin: 0 15px; /* 增加按钮之间的间距 */
flex: 0 0 100px;
/* 增加宽度 */
height: 70px;
/* 增加高度 */
margin: 0 15px;
/* 增加按钮之间的间距 */
border-radius: 10px;
font-size: 18px; /* 增加字体大小 */
font-size: 18px;
/* 增加字体大小 */
}
#absentBtn {
background-color: #d74e3e; /* 警告红色 */
background-color: #d74e3e;
/* 警告红色 */
color: white;
border: none;
}
#presentBtn {
background-color: #87CEEB; /* 蓝色 */
background-color: #87CEEB;
/* 蓝色 */
color: white;
border: none;
}
#transferBtn {
background-color: #FFD700; /* 黄色 */
background-color: #FFD700;
/* 黄色 */
color: white;
border: none;
}
</style>

@ -49,7 +49,9 @@ export default {
this.$message.success("退出成功!");
localStorage.removeItem("token");
localStorage.removeItem("name");
this.$router.push("/login");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
},
(err) => {
this.$message.error(err.msg);
@ -63,10 +65,9 @@ export default {
<style scoped>
#sidebar {
background-color: #a6e0fe;
background-color: #c5d2d8;
height: 100vh;
width: 260px;
min-height: 815px;
box-shadow: 8px 0px 11px 0px rgba(0, 0, 0, 0.16);
border-radius: 5px;
display: flex;
@ -75,24 +76,22 @@ export default {
#info {
height: 95px;
width: 260px;
min-height: 95px;
justify-content: center;
align-items: center;
position: relative;
background: #66c7fe;
/* box-shadow: 5px 0px 4px 0px #66c7fe; */
background: #c5d2d8;
}
#logo {
position: absolute;
left: 10px;
top: 13px;
top: 5%;
height: 71px;
width: 71px;
}
#title {
position: absolute;
left: 105px;
top: 26px;
top: 25%;
width: 135px;
height: 30px;
font-weight: 600;
@ -139,15 +138,15 @@ li {
}
.el-menu-item {
color: white !important;
background-color: #a6e0fe !important;
background-color: #c5d2d8 !important;
font-size: 18px !important;
}
.el-menu-item:hover {
outline: 0;
background-color: #6fc5f1 !important;
background-color: #b8c1c5 !important;
}
.el-menu-item.is-active {
background-color: #6fc5f1 !important;
background-color: #c0cbd0 !important;
padding: 0px !important;
}

@ -14,15 +14,9 @@
</div>
<button id="searchbtn" @click="search"></button>
<el-upload
class="upload-demo"
action="http://localhost:9090/student/import"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:on-error="handleError"
:show-file-list="false"
accept=".xls,.xlsx"
>
<el-upload class="upload-demo" :action="`${baseURL}student/import`" :headers="{ 'Authorization': token }"
:before-upload="beforeUpload" :on-success="handleSuccess" :on-error="handleError" :show-file-list="false"
accept=".xls,.xlsx">
<el-button id="importbtn" type="primary">导入学生名单</el-button>
</el-upload>
@ -43,27 +37,21 @@
<span>{{ student.major }}</span>
<span>{{ student.score }}</span>
<!-- 按下修改按钮将索引 i 传递给方法 -->
<el-button type="danger" icon="el-icon-delete" circle @click="deleteStudent(student.id)" ></el-button>
<el-button type="danger" icon="el-icon-delete" circle @click="deleteStudent(student.id)"></el-button>
</div>
</div>
<el-pagination
id="page"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[1, 2, 5, 10]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="studentList.length"
>
<el-pagination id="page" @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[1, 2, 5, 10]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="studentList.length">
</el-pagination>
</div>
</template>
<script>
import { DeleteStudent, StudentSearch } from "@/api/api";
import service from "@/utils/request.js"; //
export default {
name: "student",
data() {
@ -76,8 +64,9 @@ export default {
studentList: [],
currentPage: 1,
pageSize: 5,
fileList: []
fileList: [],
baseURL: service.defaults.baseURL,
token: window.localStorage.getItem("token")
};
},
methods: {
@ -95,8 +84,13 @@ export default {
return isExcel; // false
},
handleSuccess(response, file) {
this.$message.success('文件上传成功');
if (response.code === 200) {
this.$message.success('文件上传成功');
} else {
this.$message.error('文件上传失败:' + response.message || '未知错误');
}
},
handleError(err, file) {
this.$message.error('文件上传失败');
},
@ -117,9 +111,11 @@ export default {
console.log(this.studentList)
this.$message.success("查询成功");
}
} else if(res.code == 401){
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
this.$router.push("/login");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
@ -129,9 +125,9 @@ export default {
if (res.code == 500) {
this.$message.error("删除失败");
} else if (res.code == 200) {
this.$message.success("删除成功");
//
this.studentList.splice(index, 1); //
this.$message.success("删除成功");
//
this.studentList.splice(index, 1); //
}
})
.catch((error) => {
@ -158,14 +154,16 @@ export default {
position: relative;
width: 85.4%;
height: 100vh;
min-width: 910px;
min-height: 700px;
background-image: url("../assets/background.png");
/* background-image: url("../assets/background.png"); */
background-image: url("../assets/login/login-background.png");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow: hidden;
/* 隐藏溢出内容 */
}
#search-box {
width: 100%;
height: 100%;
@ -181,6 +179,7 @@ export default {
font-size: 15px;
font-weight: bold;
}
#number {
width: 70%;
height: 40px;
@ -191,6 +190,7 @@ export default {
font-size: 15px;
text-align: center;
}
#searchbtn {
width: 80px;
height: 40px;
@ -201,16 +201,19 @@ export default {
opacity: 0.74;
font-size: 15px;
font-weight: bold;
background: #229fe0;
background: #7e8081;
}
#searchbtn:hover {
background: #1e90ff;
background: #666769;
}
#searchbtn:active {
background: #1c86ee;
background: #666769;
}
#importbtn {
width:150px;
width: 150px;
height: 40px;
border-radius: 10px;
border: 0px;
@ -219,15 +222,18 @@ export default {
opacity: 0.74;
font-size: 15px;
font-weight: bold;
background: #229fe0;
background: #7e8081;
color: black;
}
#importbtn:hover {
background: #1e90ff;
background: #79797a;
}
#importbtn:active {
background: #1c86ee;
background: #666769;
}
#banner {
box-sizing: border-box;
position: absolute;
@ -241,21 +247,27 @@ export default {
background: #70707055;
justify-content: space-evenly;
}
#span-no {
margin: 0 0 0 35.5px;
}
#span-name {
margin: 0 0 0 60px;
}
#span-major {
margin: 0 0 0 50px;
}
#span-score {
margin: 0 15px 0 50px;
}
#delete {
margin: 0 30px 0 15px;
}
#list {
box-sizing: border-box;
position: absolute;
@ -269,6 +281,7 @@ export default {
justify-content: flex-start;
background: transparent;
}
.list-item {
box-sizing: border-box;
margin-top: 10px;
@ -281,11 +294,17 @@ export default {
background: #fffffff5;
box-shadow: 0 -3px 3px 0 #d4d2d2 inset;
}
.list-item:hover {
background: #95daff;
}
#page {
margin: auto;
margin-top: -80px;
z-index: 10;
/* 添加此行 */
position: relative;
/* 确保 z-index 生效 */
}
</style>

@ -37,8 +37,6 @@ export default new VueRouter({
name: 'question',
component: Question, // 这是你的组件
}
],
redirect: '/home'

Loading…
Cancel
Save