init: 初次提交

flying_pig 2 months ago
commit f087978540

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/k-class-roll-call.iml" filepath="$PROJECT_DIR$/.idea/k-class-roll-call.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="b4c20391-644c-4ef8-93ae-166260c047e0" name="更改" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ProjectId" id="2mvSYUAwxILKz9wIrHwcmy17tTE" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"WebServerToolWindowFactoryState": "false",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="b4c20391-644c-4ef8-93ae-166260c047e0" name="更改" comment="" />
<created>1727955603618</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1727955603618</updated>
<workItem from="1727955604708" duration="5000" />
</task>
<servers />
</component>
</project>

33
backend/.gitignore vendored

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.flyingpig</groupId>
<artifactId>uuAttendance</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>uuAttendance</name>
<description>An attendance software that supports roll calling and location check-in.</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version> <!-- 可以根据需要使用最新版本 -->
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- 如果没有这个插件打包出来的jar包没有清单文件-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
<!-- 如果不添加此节点mybatis的mapper.xml文件都会被漏掉。 -->
</build>
</project>

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class KClassRollCallApplication {
public static void main(String[] args) {
SpringApplication.run(KClassRollCallApplication.class, args);
}
}

@ -0,0 +1,35 @@
package com.flyingpig.kclassrollcall.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码
private String msg;//响应信息,描述字符串
private Object data;//返回的数据
//增删改
public static Result success() {
return new Result(200, "success", null);
}
//查询 成功响应
public static Result success(Object data) {
return new Result(200, "success", data);
}
//查询 失败响应
public static Result error(Integer code, String msg) {
return new Result(code, msg, null);
}
public static Result error(String msg) {
return new Result(500, msg, null);
}
}

@ -0,0 +1,16 @@
package com.flyingpig.kclassrollcall.common;
/*
0124
*/
public class RollCallMode {
public static final String WEIGHT = "0";
public static final String EQUAL = "1";
public static final String DOUBLE = "2";
public static final String THURSDAY = "4";
}

@ -0,0 +1,34 @@
package com.flyingpig.kclassrollcall.common;
public class StatusCode {
/**
*
*/
public static final int OK = 200;
/**
*
*/
public static final int PARAMETERERROR = 400;
/**
*
*/
public static final int NOTFOUND = 404;
/**
*
*/
public static final int METHODERROR = 405;
/**
*
*/
public static final int SERVERERROR = 500;
/**
*
*/
public static final int UNAUTHORIZED = 401;
}

@ -0,0 +1,30 @@
package com.flyingpig.kclassrollcall.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
/**
*
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许白名单域名进行跨域调用
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

@ -0,0 +1,30 @@
package com.flyingpig.kclassrollcall.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 全局配置序列化返回 JSON 处理
SimpleModule simpleModule = new SimpleModule();
//JSON Long ==> String
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}

@ -0,0 +1,16 @@
package com.flyingpig.kclassrollcall.config;
import com.flyingpig.kclassrollcall.filter.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.excludePathPatterns("/user/login", "/user/register"); // 排除登录和注册接口
}
}

@ -0,0 +1,97 @@
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.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.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
@Autowired
IStudentService studentService;
@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)));
}
@GetMapping("/roll-call")
public Result rollCall(String mode) {
return studentService.rollCall(mode);
}
@GetMapping("/search")
public Result search(String no, String name, Integer pageNo, Integer pageSize) {
return studentService.search(no, name, pageNo, pageSize);
}
@PostMapping("/import")
public Result importExcel(@RequestParam("file") MultipartFile file) {
// 如果 validateExcelHeader 返回 false则文件类型或格式错误
if (!validateExcelHeader(file)) {
return Result.error("文件类型或格式错误");
}
return studentService.importExcel(file);
}
private boolean validateExcelHeader(MultipartFile file) {
// 检查文件类型
String fileName = file.getOriginalFilename();
if (fileName == null || !(fileName.endsWith(".xls") || fileName.endsWith(".xlsx"))) {
return false; // 文件扩展名不正确
}
// 使用 EasyExcel 检查文件内容
// 自定义监听器用于验证标题
ReadListener<String[]> listener = new ReadListener<String[]>() {
@Override
public void invoke(String[] data, AnalysisContext context) {
// 检查第一行的标题
if (context.readRowHolder().getRowIndex() == 0) { // 仅检查第一行
if (!"学生学号".equals(data[0]) || !"姓名".equals(data[1]) || !"专业".equals(data[2])) {
throw new RuntimeException("标题不匹配");
}
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
};
return true; // 如果没有异常抛出,标题验证通过
}
@DeleteMapping("/{id}")
public Result deleteStudent(@PathVariable String id) {
if (studentService.removeById(id)) {
return Result.success();
} else {
return Result.error("删除失败");
}
}
}

@ -0,0 +1,40 @@
package com.flyingpig.kclassrollcall.controller;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.dto.req.LoginReq;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.flyingpig.kclassrollcall.service.ITeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@RestController
@RequestMapping("/user")
public class TeacherController {
@Autowired
ITeacherService teacherService;
@PostMapping("/login")
public Result login(@RequestBody LoginReq loginReq){
return teacherService.login(loginReq);
}
@PostMapping("/register")
public Result register(@RequestBody Teacher teacher){
return teacherService.register(teacher);
}
}

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall.dto.req;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginReq {
private String username;
private String password;
}

@ -0,0 +1,14 @@
package com.flyingpig.kclassrollcall.dto.req;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentExcelModel {
private String no; // 学号
private String name; // 姓名
private String major; // 专业
}

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall.dto.resp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginResp {
private String name;
private String token;
}

@ -0,0 +1,48 @@
package com.flyingpig.kclassrollcall.entity;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("student")
@ApiModel(value="Student对象", description="")
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer no;
private String major;
private Double score;
private Long teacherId;
}

@ -0,0 +1,40 @@
package com.flyingpig.kclassrollcall.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("teacher")
@ApiModel(value="Teacher对象", description="")
public class Teacher implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private String name;
private String username;
private String password;
}

@ -0,0 +1,65 @@
package com.flyingpig.kclassrollcall.exception;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.common.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.net.BindException;
//全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* /--400
*
* @param e
* @return
*/
@ExceptionHandler({
MissingServletRequestParameterException.class,
MethodArgumentTypeMismatchException.class,
BindException.class}
)
public Result missingServletRequestParameterException(Exception e) {
return Result.error(StatusCode.PARAMETERERROR, "缺少参数或参数错误");
}
/**
* --405
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result httpRequestMethodNotSupportedExceptionHandler(Exception e){
log.error("请求方法错误");
return Result.error(StatusCode.METHODERROR,"请求方法错误");
}
/**
* Redis--500
*/
@ExceptionHandler(RedisConnectionFailureException.class)
public Result edisConnectionFailureExceptionHandler(RedisConnectionFailureException e) {
log.error("redis连接错误啦啦啦啦啦啦");
return Result.error(StatusCode.SERVERERROR,"redis连接错误啦啦啦啦啦啦");
}
/**
* --500
*/
@ExceptionHandler(Exception.class)
public Result exceptionHandler(Exception e) {
e.printStackTrace();
return Result.error(StatusCode.SERVERERROR,"对不起,操作失败,请联系管理员");
}
}

@ -0,0 +1,41 @@
package com.flyingpig.kclassrollcall.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.flyingpig.kclassrollcall.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class AuthInterceptor implements HandlerInterceptor {
// 在控制器方法执行之前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String tokenStr = request.getHeader("Authorization");
// 不为空
if (StringUtils.isBlank(tokenStr)) {
return false;
}
try {
// 判断是否是有效的token,如果是则存储到UserContext
UserContext.setUser(JwtUtil.parseJwt(tokenStr).getSubject());
System.out.println(UserContext.getUser());
} catch (Exception exception) {
exception.printStackTrace();
return false;
}
return true; // 返回 true 继续处理请求false 则中断请求
}
// 在控制器方法调用之后执行,但在视图渲染之前调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, org.springframework.web.servlet.ModelAndView modelAndView) throws Exception {
UserContext.removeUser();
}
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.filter;
public class UserContext {
private static final ThreadLocal<String> tl =new ThreadLocal<>();
//保存当前登录用户信息到ThreadLocal
public static void setUser(String userId){
tl.set(userId);
}
//获取当前登录的用户信息
public static String getUser(){
return tl.get();
}
//移除当前登录用户信息
public static void removeUser(){
tl.remove();
}
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.mapper;
import com.flyingpig.kclassrollcall.entity.Student;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
Student rollCall(String teacherId, int randomIndex);
}

@ -0,0 +1,18 @@
package com.flyingpig.kclassrollcall.mapper;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Mapper
public interface TeacherMapper extends BaseMapper<Teacher> {
}

@ -0,0 +1,23 @@
package com.flyingpig.kclassrollcall.service;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.entity.Student;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
public interface IStudentService extends IService<Student> {
Result rollCall(String mode);
Result search(String no, String name, Integer pageNo, Integer pageSize);
Result importExcel(MultipartFile file);
}

@ -0,0 +1,21 @@
package com.flyingpig.kclassrollcall.service;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.dto.req.LoginReq;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
public interface ITeacherService extends IService<Teacher> {
Result login(LoginReq loginReq);
Result register(Teacher teacher);
}

@ -0,0 +1,137 @@
package com.flyingpig.kclassrollcall.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
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.Result;
import com.flyingpig.kclassrollcall.common.RollCallMode;
import com.flyingpig.kclassrollcall.dto.req.StudentExcelModel;
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 org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements IStudentService {
@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())));
}
}
private Student rollBackStudentBaseScore() {
// 获取符合条件的学生列表
LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<Student>()
.eq(Student::getTeacherId, UserContext.getUser());
List<Student> students = this.baseMapper.selectList(queryWrapper);
// 计算权重
double totalWeight = 0;
Map<Student, Double> weightMap = new HashMap<>();
for (Student student : students) {
double weight;
if (student.getScore() > 0) {
weight = 1.0 / student.getScore(); // 正常权重
} else {
weight = 1; // 给分数为0的学生一个较大的固定权重
}
weightMap.put(student, weight);
totalWeight += weight;
}
// 生成随机数
double randomValue = Math.random() * totalWeight;
// 根据权重选择学生
double cumulativeWeight = 0;
for (Map.Entry<Student, Double> entry : weightMap.entrySet()) {
cumulativeWeight += entry.getValue();
if (cumulativeWeight >= randomValue) {
return entry.getKey();
}
}
return null; // 若无符合条件的学生返回null
}
public Result search(String no, String name, Integer pageNo, Integer pageSize) {
// 构建查询条件
LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<>();
// 如果 no 和 name 不为空,则分别进行模糊查询
queryWrapper.like(StringUtils.isNotBlank(no), Student::getNo, no)
.or()
.like(StringUtils.isNotBlank(name), Student::getName, name)
.eq(Student::getTeacherId, UserContext.getUser());
// 分页查询
Page<Student> page = new Page<>(pageNo, pageSize);
Page<Student> result = page(page, queryWrapper);
result.setTotal(result.getRecords().size());
// 返回分页结果
return Result.success(result);
}
@Override
public Result importExcel(MultipartFile file) {
// 读取数据并转换为 Student 对象
List<Student> students = readStudentsFromExcel(file);
// 保存到数据库
if (!students.isEmpty()) {
saveBatch(students);
}
return Result.success("导入成功,已添加 " + students.size() + " 条记录");
}
private List<Student> readStudentsFromExcel(MultipartFile file) {
List<Student> students = new ArrayList<>();
// 创建 Excel 读取器并指定监听器
try {
EasyExcel.read(file.getInputStream(), StudentExcelModel.class, new ReadListener<StudentExcelModel>() {
@Override
public void invoke(StudentExcelModel data, AnalysisContext context) {
// 直接从第二行开始读取数据,跳过第一行
if (context.readRowHolder().getRowIndex() >= 1) {
Student student = new Student(null, null, null, null, (double) 0, Long.parseLong(UserContext.getUser()));
BeanUtil.copyProperties(data, student);
students.add(student);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
}).sheet().doRead(); // 读取工作表
} catch (IOException e) {
System.out.println("读取错误");
}
return students;
}
}

@ -0,0 +1,50 @@
package com.flyingpig.kclassrollcall.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.flyingpig.kclassrollcall.util.JwtUtil;
import org.apache.catalina.User;
import org.springframework.stereotype.Service;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements ITeacherService {
@Override
public Result login(LoginReq loginReq) {
Teacher teacher = this.getOne(new LambdaQueryWrapper<Teacher>()
.eq(Teacher::getUsername, loginReq.getUsername())
.eq(Teacher::getPassword, loginReq.getPassword()));
if(teacher == null){
return Result.error("账号或密码错误");
}else {
return Result.success(new LoginResp(teacher.getName(), JwtUtil.createJWT(teacher.getId().toString())));
}
}
@Override
public Result register(Teacher teacher) {
try {
save(teacher);
return Result.success("注册成功");
}catch (Exception e){
log.error(e.getMessage());
return Result.error("注册失败,可能存在用户名已被注册等问题");
}
}
}

@ -0,0 +1,98 @@
package com.flyingpig.kclassrollcall.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
public class JwtUtil {
//有效期为
public static final Long JWT_TTL =15*24* 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "Zmx5aW5ncGln";//flyingpig
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* jtw
* @param subject tokenjson
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* jtw
* @param subject tokenjson
* @param ttlMillis token
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("flyingpig") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String jwtKey = "flyingpig";
String encodedKey = Base64.getEncoder().encodeToString(jwtKey.getBytes());
System.out.println(encodedKey);
}
/**
* secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
//解析JWT令牌
public static Claims parseJwt(String jwt) {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}

@ -0,0 +1,11 @@
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/k-class-roll-call
username: root
password: '@Aa123456'
logging:
level:
com.baomidou.mybatisplus: DEBUG # MyBatis-Plus 日志级别

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flyingpig.kclassrollcall.mapper.StudentMapper">
<select id="rollCall" resultType="com.flyingpig.kclassrollcall.entity.Student">
SELECT * FROM student WHERE teacher_id = #{teacherId} LIMIT 1 OFFSET #{randomIndex};
</select>
</mapper>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flyingpig.kclassrollcall.mapper.TeacherMapper">
</mapper>

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class KClassRollCallApplicationTests {
@Test
void contextLoads() {
}
}

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 ROBINRUGAN
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,48 @@
{
"name": "k-calss-rall-call",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.5.1",
"core-js": "^3.8.3",
"element-ui": "^2.15.14",
"jszip": "^3.10.1",
"vue": "^2.6.14",
"vue-router": "^3.6.5"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"babel-plugin-component": "^1.1.1",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

@ -0,0 +1,17 @@
<!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>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

@ -0,0 +1,42 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "App",
components: {},
};
</script>
<style>
* {
margin: 0;
padding: 0;
scroll-behavior: smooth;
font-family: "微软雅黑";
}
.el-submenu__title {
margin: 0px !important;
padding: 0px !important;
color: #fff !important;
background-color: #a6e0fe !important;
font-size: 18px!important;
}
.el-submenu__title.is-active {
padding: 0px !important;
background-color: #6fc5f1 !important;
}
.el-menu-item-group__title {
padding: 0px !important;
}
.el-submenu__title i {
color: #ffffff!important;
}
.el-submenu__icon-arrow {
font-size: 20px!important;
margin-top: -11px!important;
}
</style>

@ -0,0 +1,81 @@
import {
service
} from "@/utils/request";
export function Semester() {
return service.request({
method: "get",
url: `/courseDetails/dataColumn`,
})
}
export function Login(data) {
return service.request({
method: "post",
url: `/user/login`,
data
})
}
export function Register(data) {
return service.request({
method: "post",
url: `/user/register`,
data
})
}
export function Logout() {
return service.request({
method: "post",
url: `/user/logout`,
})
}
export function DeleteStudent(studentId) {
return service.request({
method: "delete",
url: `/student/${studentId}` // 确保 studentId 是字符串
});
}
export function ModifyStudentScore(id, score) {
return service.request({
method: "put",
url: `/student/score?id=${id}&&score=${score}`,
})
}
export function StudentSearch(data) {
const {
no,
name,
pageSize,
pageNo
} = data;
// 使用条件运算符来确保查询参数不是 undefined
const queryParams = [
`no=${no}`,
`name=${name}`,
`pageSize=${pageSize}`,
`pageNo=${pageNo}`
].filter(param => param !== undefined).join('&');
const url = `/student/search?${queryParams}`;
return service.request({
method: "get",
url: url,
});
}
export function GetRollBackStudent(mode) {
const url = `/student/roll-call?mode=${mode}`;
return service.request({
method: "get",
url: url
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@ -0,0 +1,316 @@
<template>
<div id="home-part">
<div id="banner">
<div id="info-time">
<div class="title">当前时间</div>
<div class="content">{{ date }}</div>
</div>
<div id="info-sche">
<div class="title">当前学期</div>
<div class="content">2024.01</div>
</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">
<div class="func-title" id="title2">学生<br />查询</div>
<div class="func-content" id="content2">
搜索获取班级学生名单以及学生积分情况导入学生名单
</div>
</div>
</router-link>
<router-link to="/roll-call">
<div id="cloud3">
<div class="func-title" id="title3">点名<br />提问</div>
<div class="func-content" id="content3">对学生进行点名并对到场的学生进行提问</div>
</div>
</router-link>
<router-link to="/roll-call">
<div id="cloud4">
<div class="func-title" id="title4">点名<br />提问</div>
<div class="func-content" id="content4">对学生进行点名并对到场的学生进行提问</div>
</div>
</router-link>
<router-link to="/roll-call">
<div id="cloud5">
<div class="func-title" id="title5">点名<br />提问</div>
<div class="func-content" id="content5">
对学生进行点名并对到场的学生进行提问
</div>
</div>
</router-link>
</div>
</template>
<script>
export default {
data() {
return {
name: "MEW",
semester: "",
};
},
beforeMount() {
if (localStorage.getItem("name") == null) {
alert("您还未登录或登录已过期,请重新登录!");
this.$router.push("/login");
} else {
this.name = localStorage.getItem("name");
}
},
methods: {},
computed: {
date: () => {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
return `${year}${month}${day}`;
},
},
};
</script>
<style scoped>
#home-part {
box-sizing: border-box;
position: relative;
width: 85.4%;
height: 100vh;
min-width: 910px;
min-height: 700px;
background-image: url("../assets/home/background.png");
background-size: cover;
display: flex;
}
#banner {
box-sizing: border-box;
position: absolute;
margin-left: 5.5%;
margin-top: 15px;
width: 90%;
height: 50px;
background-color: #d0e7f2;
border-radius: 20px;
justify-content: center;
background-size: cover;
display: flex;
justify-content: space-evenly;
align-items: center;
}
#info-sche {
display: flex;
}
#info-time {
display: flex;
}
.title {
font-size: 20px;
text-align: center;
font-weight: bold;
}
.content {
font-size: 20px;
font-weight: 400;
}
#hello {
margin-left: 50px;
margin-top: 90px;
font-size: 20px;
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%);
}
to {
transform: translate(0%, 0%);
}
}
@keyframes move3 {
from {
transform: translate(100%, 10%);
}
to {
transform: translate(0%, 0%);
}
}
@keyframes move4 {
from {
transform: translate(110%, 50%);
}
to {
transform: translate(0%, 0%);
}
}
@keyframes move5 {
from {
transform: translate(20%, 80%);
}
to {
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;
width: 439px;
height: 249px;
top: 50.9%;
left: 0%;
animation: move2 1.5s ease;
}
#cloud3 {
background-image: url("../assets/home/cloud3.png");
background-size: contain;
position: absolute;
z-index: 100;
width: 439px;
height: 279px;
top: 24.9%;
left: 10%;
animation: move3 1.5s ease;
}
#cloud4 {
background-image: url("../assets/home/cloud4.png");
background-size: contain;
position: absolute;
z-index: 100;
width: 333px;
height: 319px;
top: 10.9%;
left: 38%;
animation: move4 1.5s ease;
}
#cloud5 {
background-image: url("../assets/home/cloud5.png");
background-size: contain;
position: absolute;
z-index: 100;
width: 430px;
height: 319px;
top: 12.9%;
left: 61%;
animation: move5 1.5s ease;
}
.func-title {
font-size: 22px;
font-weight: bold;
margin-top: 25px;
margin-left: 20px;
position: absolute;
color: white;
}
.func-content {
font-size: 18px;
font-weight: 550;
margin-top: 10px;
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%;
letter-spacing: 2px;
}
#content2 {
width: 190px;
top: 33%;
left: 14%;
color: #000;
}
#content2:hover {
color: rgb(255, 86, 86);
}
#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%;
letter-spacing: 2px;
}
#content4 {
width: 190px;
top: 21%;
left: 14%;
color: #000;
}
#content4:hover {
color: rgb(255, 86, 86);
}
#title5 {
top: 35%;
left: 15%;
letter-spacing: 2px;
}
#content5 {
width: 190px;
top: 21%;
left: 30%;
color: #000;
}
#content5:hover {
color: rgb(255, 86, 86);
}
</style>

@ -0,0 +1,135 @@
<template>
<div id="check-attendance">
<!-- 添加 "进入提问环节" 文本 -->
<div id="question-phase">
进入提问环节
</div>
<!-- 显示正在回答问题的学生姓名 -->
<div id="current-student">
现在回答问题的是 {{ currentStudent.name || "某某同学" }}
</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>
</template>
<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) {
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");
}
});
},
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
return {
position: 'absolute',
left: `${150 + x}px`, // x
top: `${150 + y}px`, // y
};
},
},
};
</script>
<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-button:hover {
background-color: #1E90FF;
}
#selected-score {
margin-top: 20px;
font-size: 24px;
color: white;
}
</style>

@ -0,0 +1,215 @@
<template>
<div id="check-attendance">
<div id="header">
<button
id="mode-button"
@click="toggleMode"
:style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e', borderRadius: '20px', color: 'white' }"
>
{{ modeName }}
</button>
</div>
<p v-if="randomEvent" style="text-align: center; font-size: 18px;">
目前随机事件为{{ randomEvent }}
</p>
<div id="attendance-button-container">
<button
id="attendance-button"
:style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e' }"
@click="getRandomStudent"
>
点名
</button>
</div>
<div v-if="currentStudent" id="student-info">
<p style="font-size: 24px;">
学号: <span style="margin-right: 50px;">{{ currentStudent.no }}</span>
姓名: {{ currentStudent.name }}
</p>
<div id="action-buttons">
<button id="absentBtn" @click="markAbsent"></button>
<button id="presentBtn" @click="markPresent"></button>
<button id="transferBtn" @click="transferAttendance"></button>
</div>
</div>
</div>
</template>
<script>
import { GetRollBackStudent, ModifyStudentScore } from '@/api/api';
export default {
data() {
return {
modeName: '生成随机事件',
isRandomMode: true, /* true为正常模式false为随机事件模式 */
detailModeType: 0, /* 0为权重模式1为众生平等2为两倍积分模式4为疯狂星期四模式 */
currentStudent: null,
students: [],
randomEvent: null, //
randomEvents: ['双倍加分', '疯狂星期四', '众生平等'], //
};
},
methods: {
toggleMode() {
this.isRandomMode = !this.isRandomMode;
this.modeName = this.isRandomMode ? '生成随机事件' : '返回正常模式';
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;
} else if (this.randomEvent == '众生平等') {
this.detailModeType = 1;
}
} else {
this.randomEvent = null; // 退
}
},
getRandomStudent() {
GetRollBackStudent(
this.detailModeType
).then((res) => {
if (res.code == 500) {
this.$message.error("获取点名学生发生错误");
} else if (res.code == 200) {
this.currentStudent = res.data;
this.$message.success("查询成功");
} else if(res.code == 401){
alert("登录过期,请重新登录!");
this.$router.push("/login");
}
});
},
markAbsent() {
ModifyStudentScore(this.currentStudent.id, -2).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
this.currentStudent = null; //
} else if (res.code == 200) {
alert(`${this.currentStudent.name} 未到教室,已被登记,扣除两分`);
this.currentStudent = null; //
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
this.$router.push("/login");
}
});
},
markPresent() {
ModifyStudentScore(this.currentStudent.id, 1).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 } });
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
this.$router.push("/login");
}
});
},
transferAttendance() {
alert(`${this.currentStudent.name} 转移点名,重新进行点名`);
this.currentStudent = null; //
},
},
};
</script>
<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: flex-start;
}
#header {
display: flex;
justify-content: flex-end;
margin: 10px;
height: 50px;
}
#mode-button {
border: none;
border-radius: 20px; /* 圆角按钮 */
padding: 10px 20px; /* 增加内边距 */
font-size: 16px; /* 调整字体大小 */
cursor: pointer; /* 鼠标指针样式 */
}
#attendance-button-container {
display: flex;
justify-content: center;
align-items: center;
margin: 10px;
}
#attendance-button {
border: none;
border-radius: 50%;
width: 100px;
height: 100px;
font-size: 20px;
color: white;
margin: 10px;
}
#student-info {
text-align: center;
margin-top: 0;
padding-top: 0;
}
#action-buttons {
display: flex;
justify-content: center;
margin-top: 30px;
}
#action-buttons button {
flex: 0 0 100px; /* 增加宽度 */
height: 70px; /* 增加高度 */
margin: 0 15px; /* 增加按钮之间的间距 */
border-radius: 10px;
font-size: 18px; /* 增加字体大小 */
}
#absentBtn {
background-color: #d74e3e; /* 警告红色 */
color: white;
border: none;
}
#presentBtn {
background-color: #87CEEB; /* 蓝色 */
color: white;
border: none;
}
#transferBtn {
background-color: #FFD700; /* 黄色 */
color: white;
border: none;
}
</style>

@ -0,0 +1,176 @@
<template>
<div id="sidebar">
<div id="info">
<img id="logo" src="../assets/home/avatar.png" alt="" />
<h1 id="title">KX点名</h1>
</div>
<el-menu :default-active="$route.path" class="menu">
<router-link to="/home">
<el-menu-item index="/home" >
<div class="menu-item">
<img src="../assets/home/home-icon.png" alt="" />
首页
</div>
</el-menu-item>
</router-link>
<router-link to="/student">
<el-menu-item index="/student">
<div class="menu-item">
<img src="../assets/home/search-menu.png" alt="" />
学生名单与积分查询
</div>
</el-menu-item>
</router-link>
<router-link to="/roll-call">
<el-menu-item index="/roll-call">
<div class="menu-item">
<img src="../assets/home/pass-icon.png" alt="" />
点名与提问
</div>
</el-menu-item>
</router-link>
</el-menu>
<div id="logout" @click="logout">
<img src="../assets/home/logout-icon.png" alt="" />
<span>退出登录</span>
</div>
</div>
</template>
<script>
import { Logout } from "@/api/api";
export default {
methods: {
logout() {
Logout().then(
(res) => {
this.$message.success("退出成功!");
localStorage.removeItem("token");
localStorage.removeItem("name");
this.$router.push("/login");
},
(err) => {
this.$message.error(err.msg);
console.log(err);
}
);
},
},
};
</script>
<style scoped>
#sidebar {
background-color: #a6e0fe;
height: 100vh;
width: 260px;
min-height: 815px;
box-shadow: 8px 0px 11px 0px rgba(0, 0, 0, 0.16);
border-radius: 5px;
display: flex;
flex-direction: column;
}
#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; */
}
#logo {
position: absolute;
left: 10px;
top: 13px;
height: 71px;
width: 71px;
}
#title {
position: absolute;
left: 105px;
top: 26px;
width: 135px;
height: 30px;
font-weight: 600;
}
.menu {
height: 100%;
width: 100%;
flex-direction: column;
justify-content: flex-start;
position: relative;
color: white;
font-size: 20px;
font-weight: 550;
}
.menu-item {
height: 100%;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
padding-left: 40px;
box-sizing: border-box;
}
a {
text-decoration: none;
color: white;
}
.sub-item {
padding-left: 50px;
}
li {
padding: 0px !important;
}
.menu-item img {
height: 30px;
width: 30px;
margin-right: 10px;
}
.el-menu {
background: transparent !important;
border: 0 !important;
}
.el-menu-item {
color: white !important;
background-color: #a6e0fe !important;
font-size: 18px !important;
}
.el-menu-item:hover {
outline: 0;
background-color: #6fc5f1 !important;
}
.el-menu-item.is-active {
background-color: #6fc5f1 !important;
padding: 0px !important;
}
#logout {
bottom: 0;
width: 100%;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 20px;
font-weight: 550;
cursor: pointer;
border-radius: 5px;
}
#logout img {
height: 35px;
width: 35px;
margin-right: 15px;
margin-left: -30px;
}
#logout:hover {
background: #6fc5f1;
}
</style>

@ -0,0 +1,291 @@
<template>
<div id="student">
<div id="search-box">
<div class="search">
学号
<input type="text" id="number" v-model="no" @keyup.enter="search" />
</div>
<div class="search">
姓名
<input type="text" id="number" v-model="name" @keyup.enter="search" />
</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-button id="importbtn" type="primary">导入学生名单</el-button>
</el-upload>
</div>
<div id="banner">
<span id="span-no">学生学号</span>
<span id="span-name">姓名</span>
<span id="span-major">专业</span>
<span id="span-score">积分</span>
<span id="delete">删除按钮</span>
</div>
<div id="list">
<div class="list-item" v-for="(student, i) in pagedOrders" :key="i">
<span>{{ student.no }}</span>
<span>{{ student.name }}</span>
<span>{{ student.major }}</span>
<span>{{ student.score }}</span>
<!-- 按下修改按钮将索引 i 传递给方法 -->
<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>
</div>
</template>
<script>
import { DeleteStudent, StudentSearch } from "@/api/api";
export default {
name: "student",
data() {
return {
id: "",
no: "",
name: "",
course: "",
courseList: [],
studentList: [],
currentPage: 1,
pageSize: 5,
fileList: []
};
},
methods: {
handleSizeChange(val) {
this.pageSize = val;
},
handleCurrentChange(val) {
this.currentPage = val;
},
beforeUpload(file) {
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.type === 'application/vnd.ms-excel';
if (!isExcel) {
this.$message.error('只能上传 Excel 文件!');
}
return isExcel; // false
},
handleSuccess(response, file) {
this.$message.success('文件上传成功');
},
handleError(err, file) {
this.$message.error('文件上传失败');
},
search() {
StudentSearch({
no: this.no,
name: this.name,
pageSize: 10000,
pageNo: 1,
}).then((res) => {
if (res.code == 500) {
this.$message.error("请确保信息填写完整");
} else if (res.code == 200) {
if (res.data.total == 0) {
this.$message.error("没有查询到数据");
} else {
this.studentList = res.data.records;
console.log(this.studentList)
this.$message.success("查询成功");
}
} else if(res.code == 401){
alert("登录过期,请重新登录!");
this.$router.push("/login");
}
});
},
deleteStudent(studentId) {
DeleteStudent(studentId) // studentId
.then((res) => {
if (res.code == 500) {
this.$message.error("删除失败");
} else if (res.code == 200) {
this.$message.success("删除成功");
//
this.studentList.splice(index, 1); //
}
})
.catch((error) => {
console.error(error); //
this.$message.error("请求失败,请稍后重试");
});
}
},
computed: {
pagedOrders() {
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
return this.studentList.slice(startIndex, endIndex);
},
},
};
</script>
<style scoped>
#student {
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: flex-start;
}
#search-box {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: center;
}
.search {
width: 20%;
margin-left: 20px;
font-size: 15px;
font-weight: bold;
}
#number {
width: 70%;
height: 40px;
border-radius: 10px;
border: 1px solid #ccc;
margin-top: 50px;
opacity: 0.7;
font-size: 15px;
text-align: center;
}
#searchbtn {
width: 80px;
height: 40px;
border-radius: 10px;
border: 0px;
margin-top: 50px;
margin-left: 20px;
opacity: 0.74;
font-size: 15px;
font-weight: bold;
background: #229fe0;
}
#searchbtn:hover {
background: #1e90ff;
}
#searchbtn:active {
background: #1c86ee;
}
#importbtn {
width:150px;
height: 40px;
border-radius: 10px;
border: 0px;
margin-top: 50px;
margin-left: 20px;
opacity: 0.74;
font-size: 15px;
font-weight: bold;
background: #229fe0;
color: black;
}
#importbtn:hover {
background: #1e90ff;
}
#importbtn:active {
background: #1c86ee;
}
#banner {
box-sizing: border-box;
position: absolute;
margin-left: 5.5%;
margin-top: 125px;
width: 90%;
height: 50px;
border-radius: 20px;
display: flex;
align-items: center;
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;
margin-left: 5.5%;
margin-top: 200px;
width: 90%;
height: 600px;
border-radius: 20px;
display: flex;
flex-direction: column;
justify-content: flex-start;
background: transparent;
}
.list-item {
box-sizing: border-box;
margin-top: 10px;
width: 100%;
height: 50px;
border-radius: 20px;
display: flex;
justify-content: space-evenly;
align-items: center;
background: #fffffff5;
box-shadow: 0 -3px 3px 0 #d4d2d2 inset;
}
.list-item:hover {
background: #95daff;
}
#page {
margin: auto;
margin-top: -80px;
}
</style>

@ -0,0 +1,13 @@
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.use(VueRouter)
new Vue({
render: h => h(App),
router,
}).$mount('#app')

@ -0,0 +1,54 @@
<template>
<div id="home">
<Sidebar id="sidebar"/>
<router-view></router-view>
<!-- <HomePart id="home-part"/> -->
</div>
</template>
<script>
import Sidebar from '../components/Sidebar'
import HomePart from '../components/HomePart'
export default {
name: 'Home',
components: {
Sidebar,
HomePart
}
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
scroll-behavior: smooth;
font-family: "微软雅黑";
}
#home {
display: flex;
flex-direction: row;
}
#home-part {
margin-left: -10px;
}
#overview {
margin-left: -10px;
}
#check-attendance{
margin-left: -10px;
}
#check-leave{
margin-left: -10px;
}
#empower{
margin-left: -10px;
}
#student{
margin-left: -10px;
}
#sidebar {
z-index: 2;
}
</style>

@ -0,0 +1,165 @@
<template>
<div id="background">
<div id="image">
<div id="cover">
<h1>登录</h1>
<input type="text" id="id" v-model="username" placeholder="用户名" /><br />
<input type="password" id="pwd" v-model="password" placeholder="密码" @keyup.enter="login" />
<button id="loginbtn" @click="login" >登录</button>
<router-link to="/register">
<button id="registerbtn">注册</button>
</router-link>
</div>
</div>
</div>
</template>
<script>
import { Login } from "@/api/api";
export default {
data() {
return {
username: "",
password: "",
};
},
methods: {
login() {
let loginData = {
username: this.username,
password: this.password,
};
Login(loginData).then(
(res) => {
if (res.code == 200) {
localStorage.setItem("token", res.data.token);
localStorage.setItem("name", res.data.name);
this.$message.success("登录成功!");
this.$router.push("/home");
} else {
this.$message.error(res.msg);
console.log(res);
}
},
(err) => {
this.$message.error(err.msg);
console.log(err);
}
);
},
},
};
</script>
<style scoped>
#background {
height: 100vh;
width: 100vw;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-background.png");
background-size: cover;
position: relative;
justify-content: center;
align-items: center;
display: flex;
}
#image {
width: 65.8%;
height: 60%;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-image.png");
background-size: cover;
box-sizing: border-box;
border-radius: 20px;
display: flex;
position: absolute;
justify-content: flex-end;
}
@keyframes move {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0%);
}
}
#cover {
width: 50%;
height: 100%;
min-width: 500px;
background: rgba(255, 255, 255, 1);
background-size: fill;
box-sizing: border-box;
display: block;
justify-content: center;
align-items: center;
position: absolute;
border-radius: 20px;
animation: move 1s ease-in-out;
}
#cover h1 {
position: absolute;
left: 40%;
top: 10%;
font-size: 40px;
font-weight: normal;
line-height: 52px;
text-align: center;
letter-spacing: 0.1em;
color: #000000;
display: block;
}
#id {
box-sizing: border-box;
position: absolute;
padding: 5px 10px;
top: 32%;
left: 11%;
width: 75%;
height: 10%;
font-size: 20px;
border-radius: 15px;
border-width: 1px;
}
#pwd {
box-sizing: border-box;
position: absolute;
padding: 5px 10px;
top: 50%;
left: 11%;
width: 75%;
height: 10%;
font-size: 20px;
border-radius: 15px;
border-width: 1px;
}
button {
top: 70%;
left: 25%;
width: 100px;
height: 50px;
position: absolute;
font-size: 20px;
border-radius: 15px;
cursor: pointer;
border-width: 0px;
background-color: #77b6e1;
color: white;
transition: background-color ease-in-out 0.3s;
}
#registerbtn {
left: 52%;
}
button:hover {
background-color: #d69898;
color: #ffffff;
border-color: #000000;
}
button:active {
border-style: inset;
background-color: #d28686;
}
</style>

@ -0,0 +1,194 @@
<template>
<div id="background">
<div id="image">
<div id="cover">
<h1>注册</h1>
<input type="text" id="id" placeholder="用户名" v-model="username" /><br />
<input
type="password"
id="pwd"
placeholder="密码"
v-model="password"
/><br />
<input
type="password"
id="repwd"
placeholder="确认密码"
v-model="rePassword"
/><br />
<input
type="text"
id="name"
placeholder="真实姓名"
v-model="name"
/><br />
<router-link to="/login">
<button id="loginbtn">登录</button>
</router-link>
<button id="registerbtn" @click="register"></button>
</div>
</div>
</div>
</template>
<script>
import { GetCode, Register } from "@/api/api";
export default {
data() {
return {
id: "",
password: "",
rePassword: "",
name: "",
};
},
methods: {
register() {
if (this.username == "") {
this.$message.error("请输入用户名");
} else if (this.password == "") {
this.$message.error("请输入密码");
} else if (this.rePassword == "") {
this.$message.error("请确认密码");
} else if(this.password != this.rePassword){
this.$message.error("两次密码不一致,请重新输入");
} else {
Register({
username: this.username,
password: this.password,
name: this.name
}).then((res) => {
if (res.code == 200) {
this.$message.success("注册成功");
this.$router.push("/login");
} else {
this.$message.error(res.msg);
}
});
}
},
},
};
</script>
<style scoped>
#background {
height: 100vh;
width: 100vw;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-background.png");
background-size: cover;
position: relative;
justify-content: center;
align-items: center;
display: flex;
}
#image {
width: 65.8%;
height: 60%;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-image.png");
background-size: cover;
box-sizing: border-box;
border-radius: 20px;
display: flex;
position: absolute;
justify-content: flex-start;
}
@keyframes move {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0%);
}
}
#cover {
width: 50%;
height: 100%;
min-width: 500px;
background: rgba(255, 255, 255, 1);
background-size: fill;
box-sizing: border-box;
display: block;
justify-content: center;
align-items: center;
position: absolute;
border-radius: 20px;
animation: move 1s ease-in-out;
}
#cover h1 {
position: absolute;
left: 40%;
top: 10%;
font-size: 40px;
font-weight: normal;
line-height: 52px;
text-align: center;
letter-spacing: 0.1em;
color: #000000;
display: block;
}
input {
box-sizing: border-box;
position: absolute;
padding: 5px 10px;
left: 11%;
width: 75%;
height: 10%;
font-size: 20px;
border-radius: 15px;
border-width: 1px;
}
#id {
top: 24%;
}
#pwd {
top: 36%;
}
#repwd {
top: 48%;
}
#name {
top: 60%;
}
button {
top: 87.5%;
left: 25%;
width: 100px;
height: 50px;
position: absolute;
font-size: 20px;
border-radius: 15px;
cursor: pointer;
border-width: 0px;
background-color: #77b6e1;
color: white;
transition: background-color ease-in-out 0.3s;
}
#codeform {
display: flex;
}
#registerbtn {
left: 52%;
}
#getcode {
width: 30%;
top: 72.5%;
left: 56%;
}
button:hover {
background-color: #d69898;
color: #ffffff;
border-color: #000000;
}
button:active {
border-style: inset;
background-color: #d28686;
}
</style>

@ -0,0 +1,47 @@
import VueRouter from "vue-router";
import Login from "../pages/Login";
import Register from "../pages/Register";
import Home from "../pages/Home";
import HomePart from "../components/HomePart";
import Student from "../components/Student";
import RollCall from "../components/RollCall";
import Question from "../components/Question";
export default new VueRouter({
mode: 'history',
routes: [{
path: '/login',
component: Login
},
{
path: '/register',
component: Register
},
{
path: '/',
component: Home,
children: [
{
path: 'home',
component: HomePart
},
{
path: 'student',
component: Student
},
{
path: 'roll-call',
component: RollCall
},
{
path: '/question',
name: 'question',
component: Question, // 这是你的组件
}
],
redirect: '/home'
}
]
})

@ -0,0 +1,48 @@
import axios from "axios";
// 创建axios赋给变量service
export const service = axios.create({
baseURL: `http://8.210.250.29:9090/`,
// baseURL: `http://localhost:9090/`,
timeout: 150000, // 超时
crossDomain: true,
});
//添加请求拦截器
service.interceptors.request.use(
//在发请求之前打上token和跨域的标记
function (config) {
//在这里,代码将 "Access-Control-Allow-Origin" 的值设置为 "*"
// 意味着允许所有的源(即任意域名)进行跨域访问。这种配置通常用于开发
// 和测试环境,以便允许任何源发起跨域请求。
config.headers['Access-Control-Allow-Origin'] = '*';
config.headers['Access-Control-Allow-Credentials'] = true;
let token = window.localStorage.getItem("token")
if (token) {
//我们headers里面的令牌取名叫Authorization
config.headers.Authorization = token;
}
return config;
},
function (error) {
//返回一个被拒绝的 Promise 对象,并将错误 error 作为拒绝的原因
return Promise.resolve(error.response.data);
}
);
//添加响应拦截器
service.interceptors.response.use(
function (response) {
//使用响应数据返回响应;
const data = response;
// 如果data有东西就返回data.data如果没有就返回response
if (data) {
return Promise.resolve(data.data);
}
return response;
},
function (error) {
//返回一个被拒绝的 Promise 对象,并将错误 error 作为拒绝的原因
return Promise.resolve(error.response.data);
}
);
// 将service 导出
export default service;

@ -0,0 +1,62 @@
import re
from pathlib import Path
from pathlib import WindowsPath
from typing import Optional, List
class DirectionTree:
def __init__(self,
direction_name: str = 'WorkingDirection',
direction_path: str = '.',
ignore_list: Optional[List[str]] = None):
self.owner: WindowsPath = Path(direction_path)
self.tree: str = direction_name + '/\n'
self.ignore_list = ignore_list
if ignore_list is None:
self.ignore_list = []
self.direction_ergodic(path_object=self.owner, n=0)
def tree_add(self, path_object: WindowsPath, n=0, last=False):
if n > 0:
if last:
self.tree += '' + ('' * (n - 1)) + ' └────' + path_object.name
else:
self.tree += '' + ('' * (n - 1)) + ' ├────' + path_object.name
else:
if last:
self.tree += '' + ('──' * 2) + path_object.name
else:
self.tree += '' + ('──' * 2) + path_object.name
if path_object.is_file():
self.tree += '\n'
return False
elif path_object.is_dir():
self.tree += '/\n'
return True
def filter_file(self, file):
for item in self.ignore_list:
if re.fullmatch(item, file.name):
return False
return True
def direction_ergodic(self, path_object: WindowsPath, n=0):
dir_file: list = list(path_object.iterdir())
dir_file.sort(key=lambda x: x.name.lower())
dir_file = [f for f in filter(self.filter_file, dir_file)]
for i, item in enumerate(dir_file):
if i + 1 == len(dir_file):
if self.tree_add(item, n, last=True):
self.direction_ergodic(item, n + 1)
else:
if self.tree_add(item, n, last=False):
self.direction_ergodic(item, n + 1)
if __name__ == '__main__':
i_l = [
'\.git', '__pycache__', 'test.+', 'venv', '.+\.whl', '\.idea', '.+\.jpg', '.+\.png',
'image', 'css', 'admin', 'db.sqlite3'
]
tree = DirectionTree(ignore_list=i_l, direction_path='./')
print(tree.tree)

@ -0,0 +1,16 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
devServer: {
open: false, // 编译完成是否打开网页
host: '0.0.0.0', // 指定使用地址默认localhost,0.0.0.0代表可以被外界访问
port: 9091, // 访问端口
proxy: {
}
},
})
Loading…
Cancel
Save